[{"content":"绘图 python matplotlib style Julia绘图，Makie.jl，makie的示例 matplotlib to tikz julia的tikz绘图 图标、插画网站：阿里巴巴、花瓣 控制绘图字号与正文一致的办法 DeTikZify：手绘图转tikz 实验管理 MLflow：MLflow 是一个开源平台，专为帮助机器学习从业者和团队应对机器学习流程的复杂性而构建。MLflow 专注于机器学习项目的完整生命周期，确保每个阶段都可管理、可追溯且可复现。 swanlab：一个开源、现代化设计的深度学习训练跟踪与可视化工具，同时支持云端/离线使用，适配30+主流框架，与实验代码轻松集成 DearDiary.jl: Julia 的轻量级但功能强大的机器学习实验跟踪工具。 pytorch lighting + hydra 文档 Sphinx: 可以从python函数注释中生成文档 **Documenter.jl：**可以从julia函数注释中生成文档 buzz: 离线语音转写 写作 \u0026amp; slide Quarto: 一个markdown语言的扩展版，可以嵌入python、julia、r语言的执行，并且可以方便的把文件输出为pdf、latex、html、website等 Pluto.jl: 一个julia的notebook，相较于jupyter最大的区别是它会保持变量的全局一致性。在jupyter的不同cell中我们可以对一个变量赋予不同的值，甚至出现cel的交叉执行，这使得执行结果依赖cel的执行顺序。pluto避免了这一点，笔记本的输出是一致的。 tex-diary：这是一个基于 LaTeX 的研究日志系统，用于创建、整理和编译每日 LaTeX 笔记，并可将其生成 PDF 文档和 HTML 博客。该系统采用极简的标签系统，方便用户进行内容管理。 Overleaf-Workshop：在 VSCode 中打开 Overleaf (ShareLatex) 项目，并支持完全协作。 pdfpc：pdfpc 是一款基于 GTK 的演示应用程序，它使用类似 Keynote 的多显示器输出功能，在演示过程中为演讲者提供元信息。它可以在一个屏幕上显示普通的演示窗口，同时在另一个屏幕上显示更全面的概览信息，例如下一张幻灯片的图片、演示剩余时间等等。pdfpc 处理的输入文件是 PDF 文档，而大多数现代演示软件都可以创建 PDF 文档。 pympress：与pdfpc功能差不多 BeamerQt：BeamerQT 是一款用户友好的图形界面，旨在简化 Beamer 演示文稿的创建过程，无需手动编辑与幻灯片关联的 LaTeX 代码。它提供了一套全面的功能，允许用户定义布局、插入内容（包括文本、代码块和图像）以及配置主题的一些高级设置。BeamerQT 使 LaTeX 初学者和高级用户都能轻松创建精彩的演示文稿，并将注意力集中在内容创作而非代码编写上。 Fei’s beamer theme 计算库 **sionna：**Sionna™ 是一个基于 Python 的开源库，用于通信系统研究。\n它由以下软件包组成：\nSionna RT——一款速度极快的独立式射线追踪器，用于无线电传播建模。 Sionna PHY——无线和光通信系统的链路级仿真器 Sionna SYS——基于物理层抽象的系统级模拟器 Turing.jl：Julia 中的概率编程和贝叶斯推理\nMLJ.jl：MLJ（Julia 中的机器学习）是一个用 Julia 编写的工具箱，它提供了一个通用的接口和元算法，用于选择、调整、评估、组合和比较200 多个用 Julia 和其他语言编写的机器学习模型。\nLux.jl：JuliaLang 中的优雅且高性能的深度学习，兼具 Julia 的优雅外形和 XLA 的性能\nAssociations.jl：用于量化关联、独立性测试和数据因果推断的算法。\nJuMP.jl：JuMP 是一种嵌入在Julia中的领域特定数学优化建模语言。您可以访问jump.dev了解更多信息。\nsciml: 可组合的开源软件，用于科学机器学习，支持微分编程。运用最新技术构建基于物理学的AI，使用便捷.\n公开数据集 Deepsense: DeepSense 6G is a real-world multi-modal dataset that comprises coexisting multi-modal sensing and communication data, such as mmWave wireless communication, Camera, GPS data, LiDAR, and Radar, collected in realistic wireless environments Capture24 The purpose of the CAPTURE-24 dataset is to serve as a training dataset for developing Human Activity Recognition (HAR) classifiers. physionet 复杂生理信号数据集 科研观点 杨铮老师：想法是怎样炼成的 ","date":"2025-11-23T00:00:00Z","image":"https://zaoanhh.github.io/research_blog/p/hello-world/cover_hu_e95a4276bf860a84.jpg","permalink":"https://zaoanhh.github.io/research_blog/p/hello-world/","title":"Awesome for research"},{"content":"本文介绍了fmcw测距的julia仿真。\nFFCW 测距 雷达通常也被叫做调频连续波（FMCW）雷达是一个使用频率调制来测量目标的距离的系统。在频率调制中，电磁波的频率随时间线性增加。或者说，发射频率会以恒定速率改变。这种频率随着时间线性增加的信号被称为 chirp。FMCW 系统测量发射信号和反射信号频率的瞬时差异δf，这直接和反射的 chirp 的时间差成比例。这个时间差能用来计算目标的距离。\n上图（左）显示了一个 chirp 频率随时间变化的表现，右边显示频率随时间线性增加的 chirp 的幅度随时间变化图。\n雷达前面的单目标产生的中频信号是一个固定频率音调，这个频率由下式给出：\n$$ IF = \\frac{2 \\beta d}{c} $$这里 $d$ 是目标到雷达的距离，单位 m，$c$ 是光速，m/s，$\\beta$ 是 chirp 斜率，由 chirp 持续时间内带宽的变化率得到。因此，我们可以对中频信号做 FFT 得到频率，再通过测量频率来计算距离。\n测距仿真 设置雷达参数 1 2 3 4 5 6 7 8 9 10 11 # 导入包 using FFTW, SignalAnalysis using CairoMakie, LinearAlgebra using DSP CairoMakie.activate!(type=\u0026#34;svg\u0026#34;) function myfig(;length_div_width=4/2,linewidth=28,fig_width_div_linewidth=0.5,scale=2.0) cm_to_pt = 28.3464566929134 fig_length = floor(Int,linewidth * fig_width_div_linewidth * cm_to_pt * scale) fig_width = floor(Int,fig_length / length_div_width) return Figure(size =(fig_length,fig_width),pt_per_unit = 1) end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #| output: false # 定义常量 maxR = 200.0 # 最大范围 (m) rangeRes = 1.0 # 范围分辨率 (m) maxV = 70.0 # 最大速度 (m/s) # const fc = 77e9 # 载波频率 (Hz) fc = 0.0 # 载波频率 (Hz) c = 3e8 # 光速 (m/s) # 目标参数 r0 = 100.0 # 目标距离 (m) v0 = 70.0 # 目标速度 (m/s) # 计算带宽和其他参数 B = c / (2 * rangeRes) # 带宽 (Hz) Tchirp = 2.5 * 2 * maxR / c # Chirp时间 (s) endle_time = 6.3e-6 # 结束时间 (s) slope = B / Tchirp # Chirp斜率 (Hz/s) f_IFmax = (slope * 2 * maxR) / c # 最大IF频率 (Hz) f_IF = (slope * 2 * r0) / c # 当前IF频率 (Hz) # ADC采样点数和其他参数 Nd = 1 # Chirp数量 Nr = 1024 # ADC采样点数 vres = (c / fc) / (2 * Nd * (Tchirp + endle_time)) # 速度分辨率 (m/s) Fs = Nr / Tchirp # 采样率 (samples/s) B 发射信号, 假设发射的是 cos 信号，频率随时间线性变化，假设初始相位为0。发射信号为 $cos(2 \\pi \\int_0^t f(t))$，其中 $f(t)$ 是随时间变化的频率, $f = f_c + \\beta t$ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 t = range(0, stop=Nd*Tchirp, length=Nr*Nd) # 时间向量 dt = t[2] - t[1] # 时间步长 freq = fc .+ mod.(slope .* t,B) # 发射信号的频率 angle_freq = [sum(freq[1:i].*dt) for i in axes(t,1)] # 发射信号的角度频率 # 生成发射波形 Tx = cos.(2 * pi * angle_freq) # 发射波形 fig_tx = myfig(length_div_width=3,linewidth=28) ax_tx = Axis(fig_tx[1, 1], xlabel=\u0026#34;Time (s)\u0026#34;, ylabel=\u0026#34;Amplitude\u0026#34;,title=\u0026#34;发射信号\u0026#34;) lines!(ax_tx, t[1:Nr], Tx[1:Nr]) ax_freq = Axis(fig_tx[1, 2], xlabel=\u0026#34;Time (s)\u0026#34;, ylabel=\u0026#34;Frequency (GHz)\u0026#34;, title=\u0026#34;频率随时间的变化\u0026#34;) lines!(ax_freq, t[1:Nr], freq[1:Nr]./1e9) fig_tx 3.接收信号, 接收波形可以从发射波形和时延计算。对于目标距离 $r_0$，信号时延为 $t_d=2r_0/c$，接收信号为 $cos(2 \\pi \\int_0^t f(t - t_d))$。\n1 2 3 4 5 6 7 8 9 10 11 td = 2 * r0 / c # 目标延迟时间 freq_rx = [t_now \u0026gt; td ? fc .+ mod.(slope .* (t_now.-td),B) : fc for t_now in t] # 接收信号的频率 angle_freq_rx = [sum(freq_rx[1:i].*dt) for i in axes(t,1)] # 接收信号的角度频率 Rx = cos.(2 * pi .* angle_freq_rx) # 接收信号 fig_rx = myfig(length_div_width=3,linewidth=28) ax_rx = Axis(fig_rx[1, 1], xlabel=\u0026#34;Time (s)\u0026#34;, ylabel=\u0026#34;Amplitude\u0026#34;,title=\u0026#34;接收信号\u0026#34;) lines!(ax_rx, t[1:Nr], Rx[1:Nr]) ax_freq = Axis(fig_rx[1, 2], xlabel=\u0026#34;Time (s)\u0026#34;, ylabel=\u0026#34;Frequency (GHz)\u0026#34;, title=\u0026#34;频率随时间的变化\u0026#34;) lines!(ax_freq, t[1:Nr].+td, freq_rx[1:Nr]./1e9) fig_rx 发射信号和接收信号的时频图对比如下：\n1 2 3 4 5 6 7 8 9 10 # TX_stft = tfd(Rx[1:Nr], Spectrogram(nfft=64, window=hanning,noverlap=63),fs=Fs) TX_stft = tfd(Tx[1:Nr], Wigner(nfft=256, smooth=10, method=:CM1980, window=hamming),fs=Fs) RX_stft = tfd(Rx[1:Nr], Wigner(nfft=256, smooth=10, method=:CM1980, window=hamming),fs=Fs) fig_stft = myfig(length_div_width=3,linewidth=28) ax_tx_stft = Axis(fig_stft[1, 1], xlabel=\u0026#34;Time (s)\u0026#34;, ylabel=\u0026#34;Frequency (GHz)\u0026#34;,title=\u0026#34;发射信号\u0026#34;) heatmap!(ax_tx_stft,TX_stft.time,TX_stft.freq ./1e9,TX_stft.power\u0026#39;,colormap=:roma) ax_rx_stft = Axis(fig_stft[1, 2], xlabel=\u0026#34;Time (s)\u0026#34;, ylabel=\u0026#34;Frequency (GHz)\u0026#34;,title=\u0026#34;接收信号\u0026#34;) heatmap!(ax_rx_stft,RX_stft.time,RX_stft.freq ./ 1e9,RX_stft.power\u0026#39;,colormap=:roma) fig_stft 计算中频信号。中频信号是接收信号与发射信号相乘，然后低通滤波的结果，为 $\\text{IF} = cos(2\\pi \\int_0^t (f(t-t_d)-f(t)))$，注意到 在实际的毫米波设备上，一个chirp信号发完之后会留出很长的空闲时间，在这里我们只关注一个chirp信号的情况 1 2 3 4 5 6 7 8 9 10 11 12 end_index = round(Int,(maximum(t)-td)/maximum(t)*Nr) IF_angle_freq_all = [sum((freq[1:i].-freq_rx[1:i]).*dt) for i in axes(t,1)] IF_angle_freq = IF_angle_freq_all[end_index+1:Nr] # 中频信号的角度频率 IFx = cos.(2 * pi .* IF_angle_freq) # 中频信号 fig_ifx = myfig(length_div_width=3,linewidth=28) ax_ifx = Axis(fig_ifx[1, 1], xlabel=\u0026#34;Time (s)\u0026#34;, ylabel=\u0026#34;Amplitude\u0026#34;,title=\u0026#34;接收信号\u0026#34;) lines!(ax_ifx, t[end_index+1:Nr], IFx) IFX_fft = (abs.(fft([i\u0026gt;length(IFx) ? 0.0 : IFx[i] for i in 1:Nr]))) ax_ifx = Axis(fig_ifx[1, 2], xlabel=\u0026#34;频率 (GHz)\u0026#34;, ylabel=\u0026#34;Amplitude\u0026#34;, title=\u0026#34;FFT \u0026#34;) lines!(ax_ifx, IFX_fft[1:round(Int,Nr/2)]./Nr) fig_ifx 1 2 3 freq_res = findfirst(x-\u0026gt;x==maximum(IFX_fft[1:round(Int,Nr/2)]),IFX_fft[1:round(Int,Nr/2)]) res = (freq_res / Nr ) * Tchirp *c println(\u0026#34;目标距离为: \u0026#34;,res,\u0026#34; m\u0026#34;) 多天线测角度 1 2 3 4 5 6 7 8 xx = zeros(ComplexF64, 181) θ = π /3 num = 30 xx[1:num] .= exp.((1:num) .* 1im .* sin(θ) .* π ) fig_test = myfig(length_div_width=3,linewidth=28) ax_test = Axis(fig_test[1, 1], xlabel=\u0026#34;Real\u0026#34;, ylabel=\u0026#34;Imaginary\u0026#34;,title=\u0026#34;复数\u0026#34;) lines!(ax_test, collect(-90:90),abs.(fftshift(fft(xx)))) fig_test 1 2 res_aoa=asin((findfirst(abs.(fftshift(fft(xx))) .== maximum(abs.(fftshift(fft(xx)))))-90)/90) / π *180 println(\u0026#34;目标角度为: \u0026#34;,res_aoa,\u0026#34; °\u0026#34;) ","date":"2025-12-04T20:43:40+08:00","permalink":"https://zaoanhh.github.io/research_blog/p/julia-fmcw-radar-simulation/","title":"基于 Julia 的 FMCW 雷达工作原理仿真"},{"content":" Fei Shang, Panlong Yang, Dawei Yan, and 2 more authors. LiquImager: Fine-grained Liquid Identification and Container Imaging System with COTS WiFi Devices\n为什么射线追踪模型不适用于近波长目标 在空间中传播的电磁波（EM 波）由麦克斯韦方程组描述，其解取决于介质。传统的材料感知方法往往将麦克斯韦方程组近似为几何模型（如射线），然后求解通过待定系数建立模型以获得材质信息。 然而当目标尺寸不能远大于波长时，很难寻求精确的几何近似。 如果不准确描述介质的形状、材质和位置对电场的影响，就很难实现介质成像和材料识别。 幸运的是，我们注意到空间中传输的电磁场可以用麦克斯韦方程组来完整描述。 因此，我们尝试从麦克斯韦方程组出发，探索新的解。\n电磁波的传播特性因材料而异。 在空间中传播的电磁波满足麦克斯韦方程组，即\n$$ \\left\\{ \\begin{aligned} \u0026\\nabla \\cdot \\boldsymbol{E} = \\frac{1}{\\epsilon_0} \\rho \\quad \u0026\\nabla\u0026 \\cdot \\boldsymbol{B} = 0\\\\ \u0026\\nabla \\times \\boldsymbol{E} = -\\frac{\\partial \\boldsymbol{B}}{\\partial t} \\quad \u0026\\nabla\u0026 \\times \\boldsymbol{B} = \\mu_0 \\boldsymbol{J}+\\mu_0\\epsilon_0 \\frac{\\partial \\boldsymbol{E}}{\\partial t} \\end{aligned}\\right. $$其中$\\boldsymbol{E}$和$\\boldsymbol{B}$分别是电场和磁场。 $\\epsilon_0$ 和 $\\mu_0$ 分别是自由空间的介电常数和磁导率。 $\\rho$ 是电荷密度，$\\boldsymbol{J}$ 是电流密度，两者都与分子和/或原子的性质有关。 因此，方程 @eq-Maxwell 的解随材料的不同而变化。\n传统的无线感知方法通常使用几何模型来近似麦克斯韦方程。 最常见的模型是射线追踪模型，它将波等同于射线，并利用几何关系来确定波的反射和折射路径。 特别地，如上图所示，对于宽度为$d$的介质，出射波$E_o$和入射波$E_i$之间的关系可以表示为： $$ E_o = \\alpha(d) e^{-j k d}E_i, $$ 其中$\\alpha$是衰减系数，$k$是介质中的波数。当传输距离$d$固定时，$(\\alpha,k)$对可以充分描述物质对波的影响。对于尺寸为$10$乘以波长的圆柱体，其周围的电场如上图左图所示。在宽度为 20 cm 的域中，波可以被认为是平面波。因此，对于接收到的信号，我们可以用射线来描述波的传输。\n然而，当利用Wi-Fi信号感知厘米级目标时，很难用几何模型来近似它们。当目标较小时，信号会向各个方向散射。对于尺寸与波长相似的圆柱体，其周围的电场如上图右侧所示。散射波是非常明显的非平面波。导致此时光线追踪模型的近似结果存在较大误差。对于波长大于 5 cm 的Wi-Fi信号，与容器尺寸接近，光线追踪模型不适合。\n由于目标的类型、形状和位置都会影响接收到的信号，为了对厘米级目标进行成像和识别，我们希望尽可能准确地描述目标的散射信号。这将为我们利用CSI数据完成感知任务提供基础。因此，我们尝试基于麦克斯韦方程组建立更精确的传感模型，以扩展Wi-Fi材料传感的边界。\n电场散射模型的建立与求解 介电特性随材料而变化（不同材料具有不同的复介电常数），这使得它们以独特的方式散射入射波。因此，接收到的信号将受到目标材料、形状和位置的影响。为了完成图像，我们尝试从接收到的信号中解析目标的位置、材料和形状信息。\n与射线追踪模型不同，我们直接从麦克斯韦方程构建信号散射模型，然后求解传感域中的介电分布。\n电场散射传感模型 和之前很多优秀的作品类似，具体来说，如上所示，电场垂直于波的传播方向，传感域$D$所在的平面平行于波的传播方向。类似的场景很常见。例如，我们可以用这个模型来描述当我们把接收和发射天线垂直放在桌子上，来感应桌子上的目标时。\n建立模型的关键思想来自于这样一个事实：我们接收到的散射场可以看作是传感域中等效电流的辐射场。等效电流取决于复介电常数的分布。我们首先引入模型的积分形式，然后将其离散化以方便计算。\n根据电场叠加原理，因此，位置 $\\mathbf{r}$ 处的总电场 $E_t(\\mathbf{r})$ 由以下公式给出： $$ E_t(\\mathbf{r}) = E_i(\\mathbf{r}) + E_s(\\mathbf{r})， $$ 其中 $E_i(\\mathbf{r})$ 和 $E_s(\\mathbf{r})$ 分别表示入射场和散射场。\n对于二维空间中的 TM 波，方程如下： $$\\left\\{ \\begin{aligned} E_t(\\mathbf{r}) \u0026= E_i(\\mathbf{r}) + k_0^2 \\int_D G(\\mathbf{r},\\mathbf{r'}) I(\\mathbf{r'}) d\\mathbf{r'} \\quad \\text{for\\ } \\mathbf{r} \\in D \\\\ E_s(\\mathbf{r}) \u0026= k_0^2 \\int_D G(\\mathbf{r},\\mathbf{r'}) I(\\mathbf{r'}) d\\mathbf{r'} \\quad \\text{otherwise}, \\end{aligned}\\right. $$ 其中 $k_0$ 是空气的波数。$G(\\mathbf{r},\\mathbf{r'}) = -\\frac{j}{4} H_0^2 (k_0 |\\mathbf{r}-\\mathbf{r'}|)$ 是二维自由空间格林函数，其中 $H_0^2(.)$ 是第二类 0 阶 Hankel 函数，$j^2=-1$。等效电流密度 $I(\\mathbf{r})$ 为$I(\\mathbf{r}) = [\\epsilon(\\mathbf{r})-1]E_t(\\mathbf{r})$。由于等效电流满足复介电常数分布，结合 @eq-Et1 和 @eq-4 ，我们得到了接收信号和复介电常数之间的精确关系。\n在域 $D$ 中，总场 $\\mathbf{E}_t$ 由下式给出： $$\\mathbf{E}_t = \\mathbf{E}_i + \\mathbf{G}_D \\mathbf{I} $$ 其中 $\\mathbf{E}_t$、$\\mathbf{E}_i$ 和 $\\mathbf{I}$ 是 $M^2$ 维向量，其第 $i$ 个元素分别为 $\\mathbf{E}_t(i) = E_t(\\mathbf{r}_{di})$、$\\mathbf{E}_i(i) = E_i(\\mathbf{r}_{di})$ 和 $\\mathbf{I}(i) = I(\\mathbf{r}_{di})$。等效电流密度由下式给出 $$\\mathbf{I} = \\mathbf{\\Lambda} \\mathbf{E}_t, $$ 其中 $\\mathbf{\\Lambda}$ 是对角矩阵， $\\mathbf{\\Lambda}(i,i) = \\epsilon(\\mathbf{r}_{di})-1$。系数矩阵 $\\mathbf{G}_D$ 的维度为 $M^2\\times M^2$ $\\mathbf{G}_D(m,n) = k_0^2 \\iint_{D_{n}} G(\\mathbf{r}_{dm},\\mathbf{r})d\\mathbf{r}$, 其中 $D_{n}$ 是第 $n$ 个子单元。同样，在 $Q$ 接收处天线，散射场为 $$\\mathbf{E}_s = \\mathbf{G}_S \\mathbf{I} $$ 其中 $\\mathbf{E}_s$ 是 $Q$ 维度向量，其中第 $i$ 个元素为 $\\mathbf{E}_s(i) = E_s(\\mathbf{r}_{rxi})$。这格林系数矩阵 $\\mathbf{G}_S$ 的维度为 $Q \\times M^2$ 和 $\\mathbf{G}_S(q,n) = k_0^2 \\iint_{D{n}} G(\\mathbf{r}_{rxq},\\mathbf{r})d \\mathbf{r}$ , 其中 $D_{n}$ 是域 $D$ 的第 $n$ 个子单元。\n上述方程准确描述TM波在液体中的散射。由于不同的介质（包括空气和各种液体）具有不同的复介电常数，如果我们能够通过求解模型获得每个离散子单元中的复介电常数，那么液体的识别和成像就成为可能。\n利用反向传播方案获取介电常数分布 我们首先考虑一个简单的情况：感知域 $D$ 的散射场 $\\mathbf{E}_s$ 和入射场 $\\mathbf{E}_i$ 已知。在本小节中，我们介绍如何利用此假设估计复介电常数的分布。然后我们将其推广到特定的 Wi-Fi 感知任务。\n困难来自两个方面。一方面，格林算子（$\\mathbf{G}_S$和$\\mathbf{G}_D$）具有滤波性质，这使得原始问题病态性。另一方面，波在介质中的多重散射效应使得问题具有强非线性。\n因此，可以避免矩阵求逆、奇异值分解等计算，适用于任意入射场、近场和远场的求解。散射场 $\\mathbf{E}_s$ 可以看作是等效电流 $\\mathbf{I}$ 的函数。我们首先利用最小二乘法估计具有散射场的等效电流。然后类似地估计复介电常数的分布。步骤如下。\n\\(1\\) 假设散射域中的等效电流 $\\mathbf{I}$ 与散射场成线性关系， $$\\tilde{\\mathbf{I}} = \\xi \\bar{G}_S^{\\rm H} \\mathbf{E}_s $${#eq-Ib} 其中 $\\bar{G}_S^{\\rm H}$ 是 $\\bar{G}_S$ 的共轭转置。系数 $\\xi$ 可以通过最小二乘法获得，其公式为 $$\\xi = \\mathop{\\arg\\min}\\limits_{\\xi}\\Vert\\mathbf{E}_s - \\bar{G}_S \\tilde{\\mathbf{I}} \\Vert = \\frac{(\\mathbf{E}_s)^{\\rm T} (\\bar{G}_S (\\bar{G}_S^{\\rm H} \\mathbf{E}_s))^*}{\\Vert\\bar{G}_S (\\bar{G}_S^{\\rm H} \\mathbf{E}_s)\\Vert^2}, $${#eq-xi} 其中 $A^{\\rm T}$ 和 $A^*$ 分别是矩阵 $A$ 的转置和共轭。\n\\(2\\) 将方程 @eq-Ib 和方程 @eq-xi 代入方程 @eq-E_t2 ，总场估计为 $$\\tilde{\\mathbf{E}}_t = \\mathbf{E}_i + \\bar{G}_D \\tilde{\\mathbf{I}} = \\mathbf{E}_i + \\xi \\bar{G}_D \\bar{G}_S^{\\rm H} \\mathbf{E}_s $$ 利用最小二乘法, 可以得到第 $i$ 个子单元的复介电常数 $\\epsilon(\\mathbf{r}_{di})$ 的解析解，其中 $$\\epsilon(\\mathbf{r}_{di})-1 = \\bar{\\Lambda}(i) =\\frac{\\sum_{p=1}^{P}\\tilde{\\mathbf{I}}^{p}(i)\\left[\\tilde{\\mathbf{E}}_t^{p}(i)\\right]^*}{\\sum_{p=1}^{P}\\Vert\\tilde{\\mathbf{E}}_t^{p}(i)\\Vert^2} $$一旦我们获得了每个格点的复介电系数，我们就可以完成成像。\n","date":"2025-12-04T20:33:55+08:00","image":"https://zaoanhh.github.io/research_blog/p/maxwell-in-wifi-sensing-beyond-ray-tracing-model/17ae533fe55873c77952e2b0e47a012a05546a6b6fff515370fdda2619e01389_hu_4ea647bf94eed7a0.png","permalink":"https://zaoanhh.github.io/research_blog/p/maxwell-in-wifi-sensing-beyond-ray-tracing-model/","title":"Maxwell in WiFi sensing: beyond ray-tracing model"},{"content":"这个架构利用 PyTorch Lightning 处理训练循环，利用 Hydra 处理复杂的参数配置和实验管理。\n前置准备 安装必要的库：\nBash\n1 pip install pytorch-lightning hydra-core tensorboard 项目目录结构 Hydra 严格依赖文件结构，请确保你的项目符合以下层级：\nPlaintext\n1 2 3 4 5 6 7 8 9 10 11 my_project/ ├── conf/ # [配置文件夹] │ ├── config.yaml # 主入口配置 │ ├── model/ # 模型参数组 │ │ └── default_model.yaml │ └── train/ # 训练参数组 │ └── default_train.yaml ├── src/ # [源码文件夹] │ ├── __init__.py │ └── model.py # LightningModule 定义 └── main.py # [主入口] 连接 Hydra 和 Lightning 配置文件编写 (YAML) conf/model/default_model.yaml 定义模型的超参数。\nYAML\n1 2 3 4 # 模型相关参数 input_dim: 28 hidden_dim: 64 learning_rate: 0.01 conf/train/default_train.yaml 定义 Trainer 的参数。\nYAML\n1 2 3 4 5 # 训练器相关参数 max_epochs: 10 accelerator: \u0026#34;auto\u0026#34; devices: 1 batch_size: 32 conf/config.yaml 主配置文件，用于组合各个模块。\nYAML\n1 2 3 4 5 6 7 8 defaults: - model: default_model # 默认使用 conf/model/default_model.yaml - train: default_train # 默认使用 conf/train/default_train.yaml - _self_ # 固定写法，表示覆盖顺序 # 全局通用配置 seed: 42 project_name: \u0026#34;hydra_lightning_demo\u0026#34; 模型代码 (Python) src/model.py 保持模型代码纯净，只在 __init__ 接收参数。\nPython\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import pytorch_lightning as pl import torch import torch.nn as nn import torch.nn.functional as F class LitModel(pl.LightningModule): def __init__(self, input_dim, hidden_dim, learning_rate): super().__init__() # 关键：保存超参数，这会将参数写入 hparams.yaml 并记录到日志中 self.save_hyperparameters() self.layer1 = nn.Linear(input_dim * input_dim, hidden_dim) self.layer2 = nn.Linear(hidden_dim, 10) def forward(self, x): x = x.view(x.size(0), -1) return self.layer2(F.relu(self.layer1(x))) def training_step(self, batch, batch_idx): x, y = batch logits = self(x) loss = F.cross_entropy(logits, y) # 记录日志 self.log(\u0026#34;train_loss\u0026#34;, loss, prog_bar=True) return loss def configure_optimizers(self): # 使用 self.hparams 访问保存的参数 return torch.optim.Adam(self.parameters(), lr=self.hparams.learning_rate) 主程序 (Python) main.py 这是整个系统的控制中心。\nPython\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 import hydra from omegaconf import DictConfig, OmegaConf import pytorch_lightning as pl from pytorch_lightning.loggers import TensorBoardLogger import torch from torch.utils.data import DataLoader, TensorDataset from src.model import LitModel # 1. 设置 Hydra 装饰器，指向 conf 目录 @hydra.main(version_base=None, config_path=\u0026#34;conf\u0026#34;, config_name=\u0026#34;config\u0026#34;) def main(cfg: DictConfig): print(f\u0026#34;--- Current Configuration ---\\n{OmegaConf.to_yaml(cfg)}\u0026#34;) # 设置随机种子 pl.seed_everything(cfg.seed) # 2. 实例化模型 # 从 cfg.model 中读取参数 model = LitModel( input_dim=cfg.model.input_dim, hidden_dim=cfg.model.hidden_dim, learning_rate=cfg.model.learning_rate ) # 模拟数据 (通常这里会使用 LightningDataModule) fake_data = torch.randn(100, 1, 28, 28) fake_labels = torch.randint(0, 10, (100,)) train_loader = DataLoader(TensorDataset(fake_data, fake_labels), batch_size=cfg.train.batch_size) # 3. 配置 Logger # Hydra 会自动切换工作目录到 outputs/日期/时间， # 所以 save_dir 设置为 \u0026#34;.\u0026#34; 表示当前 Hydra 生成的目录 logger = TensorBoardLogger(save_dir=\u0026#34;.\u0026#34;, name=\u0026#34;\u0026#34;, version=\u0026#34;\u0026#34;) # 4. 实例化 Trainer trainer = pl.Trainer( max_epochs=cfg.train.max_epochs, accelerator=cfg.train.accelerator, devices=cfg.train.devices, logger=logger, enable_progress_bar=True ) # 5. 开始训练 trainer.fit(model, train_loader) if __name__ == \u0026#34;__main__\u0026#34;: main() 如何运行实验 在终端中执行以下命令，体验 Hydra 的强大功能。\n基础运行 使用默认配置运行：\n1 python main.py 注：日志会自动保存在 outputs/YYYY-MM-DD/HH-MM-SS/ 目录下。\n动态修改参数 (Override) 不修改代码，直接在命令行改变学习率和 Epoch：\n1 python main.py model.learning_rate=0.001 train.max_epochs=20 多重运行 (Multirun / Grid Search) 这是对比实验的核心功能。一条命令运行 4 组实验（2种 hidden_dim * 2种 learning_rate）：\nBash\n1 python main.py -m model.hidden_dim=64,128 model.learning_rate=0.01,0.001 注：Hydra 会在 multirun/ 目录下为每组实验生成独立的子文件夹和日志。\n进阶技巧：使用 instantiate (更优雅的写法) 上面的 main.py 中，我们手动把 cfg.model.xxx 传给了 LitModel。如果你参数很多，这很麻烦。 Hydra 提供了一个高级功能：Object Instantiation。\n修改 YAML，加入 _target_ 指向类路径：YAML\n1 2 3 4 5 # conf/model/small.yaml _target_: src.model.LitModel # \u0026lt;--- 关键 input_dim: 28 hidden_dim: 64 learning_rate: 0.01 修改 main.py，使用 hydra.utils.instantiate：\n1 2 3 4 5 6 7 8 9 10 import hydra @hydra.main(...) def main(cfg: DictConfig): # 这行代码会自动寻找 _target_ 指向的类， # 并把 yaml 里剩下的参数全部传给这个类的 __init__ model = hydra.utils.instantiate(cfg.model) trainer = pl.Trainer(...) trainer.fit(model, ...) 查看结果 使用 TensorBoard 查看所有实验的对比：\nBash\n1 2 3 4 5 # 如果是单次运行 tensorboard --logdir outputs # 如果使用了 -m 多重运行 tensorboard --logdir multirun 在 TensorBoard 的 HPARAMS 标签页中，你可以直观地看到 Parallel Coordinates 图，展示 learning_rate 和 hidden_dim 如何影响最终的 Loss。\n修改log位置 修改保存地址和文件名主要涉及到两个层面：\n文件夹结构（Hydra 控制）：决定了整个实验的输出目录在哪里（例如从默认的 outputs/日期/时间 改为 experiments/实验名/）。 模型文件名（PyTorch Lightning 控制）：决定了 .ckpt 权重文件叫什么名字（例如从 epoch=0-step=100.ckpt 改为 best-acc=0.98.ckpt）。 以下是具体的修改方法：\n1. 修改输出文件夹路径 (Hydra 配置) 默认情况下，Hydra 会在 outputs/${now:%Y-%m-%d}/${now:%H-%M-%S} 创建目录。要自定义它，你需要在 conf/config.yaml 中添加 hydra 配置节点。\n修改 conf/config.yaml：\nYAML\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 defaults: - model: default_model - train: default_train - _self_ # 定义一个项目名或实验名 experiment_name: \u0026#34;my_custom_experiment\u0026#34; # --- Hydra 配置核心 --- hydra: run: # 修改单次运行的输出路径 # 示例：./logs/my_custom_experiment/2023-10-27_10-30/ dir: ./logs/${experiment_name}/${now:%Y-%m-%d_%H-%M-%S} sweep: # 修改多重运行 (-m) 的根目录和子目录 dir: ./multirun_logs/${experiment_name}/${now:%Y-%m-%d_%H-%M-%S} subdir: ${hydra.job.num} # 子文件夹名为 0, 1, 2... 这样设置后，你的实验文件就不会散落在 outputs 里，而是整齐地存放在 logs/ 下。\n2. 修改日志文件夹名称 (Logger 配置) 在 main.py 中初始化 TensorBoardLogger 时，如果你的 Hydra 路径已经很精确了（比如上面已经精确到了秒），通常我们希望 Logger 不要再创建子文件夹（不要再搞个 version_0），而是直接写在当前目录。\n修改 main.py：\nPython\n1 2 3 # \u0026#34;.\u0026#34; 表示直接使用 Hydra 创建好的那个目录，不创建新的子文件夹 # name=\u0026#34;\u0026#34; 和 version=\u0026#34;\u0026#34; 能够去除额外的目录层级 logger = TensorBoardLogger(save_dir=\u0026#34;.\u0026#34;, name=\u0026#34;\u0026#34;, version=\u0026#34;\u0026#34;) 3. 修改模型权重文件名 (Checkpoint 回调) 这是最重要的一步。你需要使用 PyTorch Lightning 的 ModelCheckpoint 回调来自定义保存的文件名（例如包含验证集准确率）。\n修改 main.py：\nPython\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from pytorch_lightning.callbacks import ModelCheckpoint # ... 在 main 函数中 ... def main(cfg: DictConfig): # ... 模型初始化代码 ... # --- 定义 Checkpoint 回调 --- checkpoint_callback = ModelCheckpoint( dirpath=\u0026#34;.\u0026#34;, # 保存路径：直接存在当前 Hydra 目录下 filename=\u0026#34;best-{epoch:02d}-{val_loss:.2f}\u0026#34;, # 文件名格式 monitor=\u0026#34;val_loss\u0026#34;, # 监控的指标 (需在 training_step 用 self.log 记录过) mode=\u0026#34;min\u0026#34;, # \u0026#34;min\u0026#34; 表示 loss 越小越好 save_top_k=1, # 只保存最好的 1 个模型 save_last=True # 额外保存一个 last.ckpt (最新的模型，以防中断) ) trainer = pl.Trainer( # ... 其他参数 ... callbacks=[checkpoint_callback], # 注册回调 logger=logger ) trainer.fit(model, train_loader) 注意：你在 filename 里使用的 {val_loss} 必须在你的 LitModel 代码里通过 self.log(\u0026ldquo;val_loss\u0026rdquo;, \u0026hellip;) 记录过，否则会报错。\n4. 完整整合示例 把你现在的 conf/config.yaml 和 main.py 更新为以下内容：\nconf/config.yaml YAML\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 defaults: - model: default_model - train: default_train - _self_ experiment_name: \u0026#34;resnet_cifar10\u0026#34; hydra: run: # 最终路径: logs/resnet_cifar10/2023-xx-xx_12-00-00/ dir: logs/${experiment_name}/${now:%Y-%m-%d_%H-%M-%S} sweep: dir: multirun/${experiment_name}/${now:%Y-%m-%d_%H-%M-%S} subdir: run_${hydra.job.num} main.py Python\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import hydra from omegaconf import DictConfig import pytorch_lightning as pl from pytorch_lightning.loggers import TensorBoardLogger from pytorch_lightning.callbacks import ModelCheckpoint # ... import your model ... @hydra.main(version_base=None, config_path=\u0026#34;conf\u0026#34;, config_name=\u0026#34;config\u0026#34;) def main(cfg: DictConfig): pl.seed_everything(42) model = ... # 初始化你的模型 # 1. 设置日志器：直接写在 Hydra 目录下 logger = TensorBoardLogger(save_dir=\u0026#34;.\u0026#34;, name=\u0026#34;\u0026#34;, version=\u0026#34;\u0026#34;) # 2. 设置模型保存规则 # 假设模型里 log 了 \u0026#39;val_acc\u0026#39; checkpoint_callback = ModelCheckpoint( dirpath=\u0026#34;checkpoints\u0026#34;, # 会在 Hydra 目录下创建一个 checkpoints 文件夹 filename=\u0026#34;{epoch:02d}-{val_acc:.4f}\u0026#34;, # 比如: 05-0.9821.ckpt monitor=\u0026#34;val_acc\u0026#34;, mode=\u0026#34;max\u0026#34;, save_top_k=1 ) trainer = pl.Trainer( max_epochs=cfg.train.max_epochs, logger=logger, callbacks=[checkpoint_callback], # ... ) trainer.fit(model, ...) if __name__ == \u0026#34;__main__\u0026#34;: main() 最终效果 运行后，你的文件结构会非常清晰：\nPlaintext\nlogs/ └── resnet_cifar10/ \u0026lt;\u0026ndash; config.yaml 中的 experiment_name └── 2023-11-20_10-30-00/ \u0026lt;\u0026ndash; 时间戳 ├── .hydra/ \u0026lt;\u0026ndash; Hydra 的配置备份 ├── checkpoints/ \u0026lt;\u0026ndash; ModelCheckpoint 创建的文件夹 │ └── 05-0.9821.ckpt \u0026lt;\u0026ndash; 自定义的文件名 ├── events.out.tfevents\u0026hellip; \u0026lt;\u0026ndash; TensorBoard 日志 └── hparams.yaml \u0026lt;\u0026ndash; PL 自动保存的参数\n","date":"2025-11-23T21:43:40+08:00","permalink":"https://zaoanhh.github.io/research_blog/p/pytorch-lightning--hydra-%E5%B7%A5%E7%A8%8B%E5%8C%96%E5%AE%9E%E8%B7%B5/","title":"PyTorch Lightning + Hydra 工程化实践"}]