用 NumPy 与 Autograd 实现可微光学逆向设计
Runnable code完整项目见 GitHub 仓库,代码按职责组织如下:
- 完整可运行实验:
autograd-inverse-design.ipynb,包含可微 ASM、Gaussian 输入与目标场、mode-overlap loss、Autograd 梯度、Adam 优化,以及最终光场和三张相位图的可视化。- 正文中的代码片段均摘自该 notebook,并按计算链路拆分讲解。
最近在学习论文 TorchOptics: An open-source Python library for differentiable Fourier optics simulations。
一、问题场景:用三块相位调制器完成光束变换
优化目标
给定一个输入光场和目标光场,不再人工设计每一块相位板,而是把相位分布当成待优化参数,通过光场传播、loss 和梯度下降,自动寻找合适的 phase profile。
论文 Figure 4 给出了一个很小但非常完整的例子:
$\text{Gaussian beam}\rightarrowM_1\rightarrow P\rightarrowM_2\rightarrow P\rightarrowM_3\rightarrow P\rightarrow\text{four Gaussian beams}.$
光学系统布局
其中三块 phase modulator 分别位于
$z=0,\quad 0.2,\quad 0.4\ {\rm m},$
目标平面位于
$z=0.6\ {\rm m}.$
数值参数
论文使用 $250\times250$ 的采样网格、$10,\mu{\rm m}$ pixel spacing、$700,{\rm nm}$ 波长和 $150,\mu{\rm m}$ Gaussian waist,并用 Adam、$lr=0.1$ 训练 400 iterations。
初始条件与待优化参数
输入是 waist 为 $150,\mu{\rm m}$ 的单束 Gaussian,目标是在四个指定位置形成四束 Gaussian。三块调制器的 phase profile
$\phi_1(x,y),\quad \phi_2(x,y),\quad \phi_3(x,y)$
均从零相位开始,因此初始系统只是让输入 Gaussian 在自由空间传播 $0.6,{\rm m}$。优化要寻找这三张 phase map,使输出 complex field 与目标 mode 尽可能重合。
二、整体思路:把传播模型接入梯度优化
TorchOptics 用 PyTorch 把这些步骤全部封装好了。但如果只调用:
其实很难真正理解 inverse design 到底发生了什么。
所以这次我不用 TorchOptics,而是只使用:
自己搭出完整链路:
$\text{phase}\rightarrow\text{optical propagation}\rightarrow\text{output field}\rightarrow\text{loss}\rightarrow\text{gradient}\rightarrow\text{Adam}$
这里真正想验证的并不是能不能重新造一个 TorchOptics,而是:
一个普通的 Fourier optics simulator,到底怎样变成一个可以做 inverse design 的 simulator?
从 forward simulation 到 inverse design
以前写光场传播时,我解决的问题通常是:
$U_0(x,y)\overset{P}{\longrightarrow}U_z(x,y).$
输入场、传播距离和系统参数都是已知的,我们只计算输出。
例如 Angular Spectrum Method:
$U_z=\mathcal F^{-1}\left[\mathcal F(U_0)H_z\right],$
其中 transfer function 为
$H_z(k_x,k_y)=\exp\left[iz\sqrt{k^2-k_x^2-k_y^2}\right].$
这属于典型的 forward problem:
$\text{已知 optical system,求 output}$
inverse design 则反过来了。
我们知道输入:
$U_{\rm in},$
也知道希望得到的目标:
$U_{\rm target},$
但是不知道中间的 phase modulator 应该长什么样。
于是:
$\phi_1(x,y),\quad\phi_2(x,y),\quad\phi_3(x,y)$
变成待求参数。
问题就变成:
$\text{寻找 }\phi_1,\phi_2,\phi_3\text{,使 }U_{\rm out}\approx U_{\rm target}$
这已经和训练神经网络非常像了。
只是我们训练的不是 neural-network weight,而是一个真实光学系统中的 phase profile。
三、建立可优化的光学问题
三平面 phase modulator 的 forward model
每块 phase modulator 的 transmission function 为
$M_j(x,y)=e^{i\phi_j(x,y)}.$
所以通过一块 phase-only SLM 后:
$U'(x,y)=U(x,y)e^{i\phi_j(x,y)}.$
三块调制器组成的完整 forward model 为:
$U_1=U_{\rm in}e^{i\phi_1},$$U_2=P_{z}(U_1)e^{i\phi_2},$$U_3=P_{z}(U_2)e^{i\phi_3},$
最后
$U_{\rm out}=P_{z}(U_3).$
用代码写出来反而非常简单:
这里的 H 是传播距离为 $0.2,{\rm m}$ 的 ASM transfer function;三段自由空间传播使用同一个 H。phase_modulate() 与 propagate() 会在后文分别定义。这个函数就是整个 inverse-design forward model。
输入 Gaussian beam
输入使用 Gaussian field:
$U(x,y)=C\exp\left[-\frac{(x-x_0)^2+(y-y_0)^2}{w_0^2}\right].$
注意这里定义的是 field amplitude,而不是 intensity。
因此:
$I(x,y)=|U(x,y)|^2.$
代码:
采用论文参数:
所以整个 simulation window 为
$L=N\Delta x=250\times10\,\mu{\rm m}=2.5\,{\rm mm}.$
构造 four-Gaussian target
论文 Listing 2 并没有直接生成 target,而是:
也就是说,论文没有在 Listing 2 中给出四个 Gaussian 的具体位置参数。
因此这里并不是逐像素恢复论文的 target.pt,而是自己构造一个等价任务:
$U_{\rm target}=\sum_{j=1}^{4}G_j(x,y).$
四个 Gaussian 分别位于:
$(-d,-d),\quad(-d,d),\quad(d,-d),\quad(d,d).$
这里取:
$d=0.5\,{\rm mm}.$
代码:
因此这里的目标是:
$\text{single Gaussian}\rightarrow\text{four Gaussian beams}$
归一化输入场与目标场
如果直接比较两个 field,它们的总功率也会影响 loss。
但现在真正关心的是:
输出 field 的 spatial mode 是否和 target 一样?
因此先定义 optical power:
$P=\iint |U(x,y)|^2dxdy.$
离散化以后:
$P\approx\sum_{m,n}|U_{mn}|^2\Delta x\Delta y.$
于是做 normalization:
这样:
$\iint |U|^2dxdy=1.$
输入场和 target 都进行归一化。
Phase modulator 与零相位初始化
Phase-only modulator 不改变 amplitude,只改变 phase:
$U_{\rm out}=U_{\rm in}e^{i\phi}.$
所以代码只有一行:
一开始我们完全不知道 phase profile 应该是什么,因此初始化:
此时:
$e^{i\phi}=1.$
三块 SLM 什么都没有做。
因此最初的 optical system 实际上只是:
$\text{Gaussian}\rightarrow0.6\,{\rm m\ free\ space}.$
显然不可能自动变成四个 Gaussian。
接下来就需要优化。
四、构建可微的前向传播
Differentiable ASM
我这里使用 Angular Spectrum Method 作为 propagation backend。
先把输入场变换到 spatial-frequency domain:
$A(k_x,k_y)=\mathcal F\{U(x,y)\}.$
自由空间传播会给每个 plane-wave component 加上不同 phase:
$A_z=A_0H_z,$
其中
$H_z=\exp(i k_z z),$
以及
$k_z=\sqrt{k^2-k_x^2-k_y^2}.$
最后:
$U_z=\mathcal F^{-1}(A_z).$
关键不在 ASM 本身。
关键在于现在:
$U$
依赖:
$\phi_1,\phi_2,\phi_3.$
因此 FFT 也必须存在于 automatic differentiation 的 computational graph 中。
这里将固定的 frequency grid 和 transfer function 用普通 NumPy 预先计算,而依赖 phase 的传播路径使用 autograd.numpy:
而不能在 differentiable path 中调用普通:
否则 grad() 会把 phase 包装成 ArrayBox,普通 NumPy FFT 并不知道怎样处理它。
Autograd 官方文档说明它支持大部分 NumPy FFT 操作以及 complex number differentiation,因此这种包含 complex FFT 的 real-valued objective 可以进行 reverse-mode differentiation。
传播距离、波长和采样网格在优化过程中保持不变,因此先在 computational graph 外构造 $H$:
这里的 Nx、Ny、dx、dy、wavelength 和 propagation_distance 就是问题场景中已经给出的数值参数。随后才定义可微的 FFT 传播:
于是 computational graph 变成:
$\phi\rightarrowe^{i\phi}\rightarrowFFT\rightarrowH\rightarrowIFFT\rightarrowU_{\rm out}.$
这一步完成以后,普通 Fourier-optics simulator 就变成了一个 differentiable simulator。
五、定义目标函数
Mode-overlap loss
接下来需要回答一个问题:
怎么判断 output 和 target 有多接近?
最直接的方法可能是 intensity MSE:
$\|I_{\rm out}-I_{\rm target}\|^2.$
但论文并不是这样做的。
它优化的是两个 complex fields 的 mode overlap。论文 loss 为:
$L=1-\left|\iintU_{\rm out}(x,y)U_{\rm target}^*(x,y)dxdy\right|^2.$
为了让实现对 field power 更稳健,我这里显式写成 normalized overlap:
$\eta=\frac{|\langle U_t,U_o\rangle|^2}{\langle U_o,U_o\rangle\langle U_t,U_t\rangle}.$
然后:
$L=1-\eta$
其中:
$0\le\eta\le1.$
当 output 完全等于 target mode:
$\eta=1,$
因此:
$L=0.$
代码:
这里优化的不是简单的 intensity pattern。
而是 complex optical mode:
$\text{amplitude + phase}$
这也是 mode overlap 相比 intensity MSE 更重要的一点。
Objective function
整个 optical system 现在已经可以看成一个函数:
$U_{\rm out}=F(\phi_1,\phi_2,\phi_3).$
再把 output 送进 loss:
$L=L(U_{\rm out},U_{\rm target}).$
于是最终:
$L=L(\phi_1,\phi_2,\phi_3)$
代码:
这里把输入场、目标场、传播传输函数和积分采样间隔全部显式列出,避免 objective 依赖正文中没有交代的隐式全局状态。
六、从梯度到参数更新
Automatic differentiation
我们需要:
$\frac{\partial L}{\partial\phi_1},\qquad\frac{\partial L}{\partial\phi_2},\qquad\frac{\partial L}{\partial\phi_3}.$
每个 $\phi$ 都是一个:
$250\times250$
matrix。
也就是说单块 phase modulator 就有:
$62500$
个 optimization variables。
三块一共:
$187500$
个变量。
手推这样的 gradient 显然没有必要。
Autograd 可以直接根据 computational graph 使用 reverse-mode automatic differentiation。
grad(objective, 0)、grad(objective, 1) 和 grad(objective, 2) 分别指定对前三个 phase 参数求导;其余参数只作为 forward calculation 所需的固定量传入。
$g_1=\nabla_{\phi_1}L,$$g_2=\nabla_{\phi_2}L,$$g_3=\nabla_{\phi_3}L.$
至此最重要的链路已经跑通:
$\phi\rightarrowe^{i\phi}\rightarrowFFT\rightarrowpropagation\rightarrowU_{\rm out}\rightarrowL\rightarrow\nabla_\phi L$
Adam 更新 phase
最后只剩 optimizer。
我没有再引入 PyTorch,而是简单实现了一遍 Adam。
对于 gradient $g_t$:
$m_t=\beta_1m_{t-1}+(1-\beta_1)g_t,$$v_t=\beta_2v_{t-1}+(1-\beta_2)g_t^2.$
经过 bias correction:
$\hat m_t=\frac{m_t}{1-\beta_1^t},$$\hat v_t=\frac{v_t}{1-\beta_2^t}.$
最后:
$\phi_{t+1}=\phi_t-\alpha\frac{\hat m_t}{\sqrt{\hat v_t}+\epsilon}.$
代码:
三块 phase modulator 分别维护自己的 Adam state:
Optimization loop
最终训练循环其实已经非常接近 neural-network training:
其中 num_iterations=400,三个 Adam 实例分别保存三张 phase map 的一阶矩和二阶矩状态。
$\text{Gaussian}$
经过当前三块 phase masks,
得到:
$U_{\rm out}.$
计算它与:
$U_{\rm target}$
之间的 mode mismatch,
再沿着:
$-\nabla_\phi L$
方向调整所有 SLM pixels。
然后重新传播。
不断重复。
七、理解优化结果
怎样理解训练后的三张 phase map
最终得到:
$\phi_1(x,y),\qquad\phi_2(x,y),\qquad\phi_3(x,y).$
它们通常看起来并不像某种简单 lens 或 grating。
这是正常的。
因为 optimizer 并没有被告知:
第一块负责分束,第二块负责聚焦,第三块负责修正。
它只知道最终 objective:
$U_{\rm out}\rightarrowU_{\rm target}.$
因此三块 phase mask 会联合工作。
可以理解为:
$M_1,M_2,M_3$
共同构成了一个 spatial-mode transformation。
第一块产生新的 spatial-frequency components,传播过程中这些频率分量积累不同 phase,第二和第三块继续重新组织 amplitude 和 phase,最终让目标 plane 上的 complex field 尽可能接近指定 target。
这就是 multi-plane light conversion 背后的基本思想之一。
八、边界、差异与实现注意点
与原论文的差异:这里固定使用 ASM
需要特别说明一点。
论文给出的 propagation-method selection criterion 为:
$z_{\rm critical}=\frac{L\Delta x}{\lambda}.$
当前:
$L=2.5\,{\rm mm},$$\Delta x=10\,\mu{\rm m},$$\lambda=700\,{\rm nm}.$
因此:
$z_{\rm critical}\approx35.7\,{\rm mm}.$
而每次 propagation distance 为:
$z=200\,{\rm mm}.$
也就是说:
$z>z_{\rm critical}.$
按照论文的规则,TorchOptics 在这种情况下默认更倾向使用 Direct Integration,而不是 ASM。论文 Listing 2 没有显式指定 propagation method,而是让 System 自己处理。
所以这篇文章更准确的表述是:
用 NumPy + Autograd 重写 TorchOptics Figure 4 的 inverse-design architecture,并使用自己实现的 ASM 作为 differentiable propagation backend。
它不是 Figure 4 的逐像素数值复刻。
如果之后把 propagate() 换成 differentiable DI,其余 optimization pipeline 完全不需要改变。
这一点实际上也很好地体现了系统设计:
$\text{propagation model}\quad\text{和}\quad\text{optimization framework}$
是两个可以独立替换的模块。
为什么优化中的 phase 可以超过 $2\pi$
训练过程中我没有执行:
因为真正进入 forward model 的是:
$e^{i\phi}.$
而:
$e^{i(\phi+2\pi n)}=e^{i\phi}.$
所以数学上:
$\phi$
和:
$\phi+2\pi$
完全等价。
因此 optimization 时允许 phase 自由变化没有问题。
最终显示或映射到实际 SLM 时,再使用:
$\phi_{\rm SLM}=\phi\bmod2\pi.$
九、总结:从 forward optics 到 inverse design
以前学习 Fourier optics 时,重点通常是:
$U_0\rightarrowU_z.$
也就是:
光经过一个系统以后会变成什么?
但 inverse design 把问题变成了:
$\text{target}\rightarrow\text{system parameters}.$
也就是:
我想得到这个结果,中间的光学系统应该长什么样?
真正连接这两个问题的并不是某个新的 diffraction formula。
Forward physics 仍然是:
$FFT\rightarrowH\rightarrowIFFT.$
新增的其实只有三个东西:
$\text{objective}+\text{gradient}+\text{optimizer}$
也就是说:
$\text{Differentiable Optics}=\text{Forward Optics}+\text{Automatic Differentiation}$
forward model 决定:
$\text{一个设计会产生什么结果};$
loss 定义:
$\text{什么结果算好};$
gradient 告诉我们:
$\text{参数往哪个方向修改};$
optimizer 则不断执行这个修改。
这次真正从头写完之后,TorchOptics 里面:
这两行代码背后的物理和计算过程也就不再是黑盒了。