
周报写到这里其实有点跑偏了本来只想花两天把 Fourier-DeepONet 这篇过一遍结果一路从算子学习追到频谱偏差又顺手把一维基准复现跑通还拿波形反演的小规模数据试了一轮。这篇就把这周读的东西、动手改的代码和踩的坑一次性整理清楚。Fourier-DeepONet本质上是在 DeepONet 的 Trunk 网络前端插了一层随机傅里叶特征映射用可控的高频基函数把查询坐标抖到高频空间从而缓解神经网络天生的低频偏置。它解决的问题很具体普通 DeepONet 学大尺度趋势很快一到解的振荡细节就开始摆烂。适合正在做 PDE 代理模型、参数反演、多尺度仿真的同学参考也适合已经用过 FNO 但被规则网格绑住手脚的人。下面按为什么这么改—改在哪—怎么跑—怎么迁—怎么修的顺序讲。1. 先把算子学习这件事说透Fourier-DeepONet 到底想解决什么在动手改代码之前得先把问题定性清楚。很多人第一次看这类论文会觉得不就是加了个位置编码吗这个直觉一半对一半错位置编码是为高频细节服务的但 DeepONet 的结构决定了它必须加在特定位置、用特定尺度否则反而更糟。这一章把动机拆开讲。1.1 Branch 和 Trunk 分工的本质DeepONet 要学的是一个算子 $\mathcal{G}: \mathcal{U} \to \mathcal{V}$输入是一个函数 $u$比如介质参数场、初始条件、边界载荷输出也是一个函数 $v \mathcal{G}(u)$。网络在任意查询点 $y$ 处的输出写成$$\mathcal{G}(u)(y) \approx \sum_{k1}^{p} b_k(u), t_k(y) b_0$$Branch 网络吃的是输入函数 $u$ 在一组固定传感器位置上的采样值输出 $p$ 维向量 $b(u)$可以理解为这组基下的系数Trunk 网络吃的是查询坐标 $y$输出 $p$ 维向量 $t(y)$可以理解为一组可学习的基函数在第 $y$ 点的取值。两者逐元素相乘再求和就得到了输出函数在 $y$ 处的值。这个设计和经典的 POD-Galerkin 降阶是同一个思路先找一组基再算系数。区别在于基和系数都是网络学出来的不依赖任何数值求解器。所以它能做到训练好之后对新的输入函数 $u$ 做一次前向就出整个解而不需要重新组装矩阵、重新迭代。我第一次跑通的时候印象很深——同一个模型训练时见过一千个不同的系数场推理时给个没见过的几十毫秒出全场解这个体验和传统有限元完全是两个世界。1.2 高维/高频解上普通 DeepONet 卡在哪问题出在 Trunk 上。Trunk 的输入是坐标输出是基函数值这个映射如果只用普通 MLP就会撞上频谱偏差网络会优先拟合低频分量高频分量要么学得很慢要么根本学不动。表现形式很有迷惑性——训练 loss 降到 $10^{-4}$ 量级就不动了画图一看整体轮廓完全对但所有震荡细节像被磨皮磨掉了一样。这不是玄学。从 NTK 的角度看MLP 对输入各频率方向的核特征值衰减速度不同高频方向衰减极快导致优化器在那些方向上几乎没有梯度信号。我做过一个很直观的对照实验同一个解把它的高频分量单独拿出来让 MLP 拟合基本上学不出来但把高频分量先做一次正弦变换再喂进去几十个 epoch 就收敛了。常见的缓解手段有几类。一是换激活函数比如用正弦激活替代 ReLU/GELU二是坐标编码把低维坐标映射到高维频率空间三是多尺度网络结构四是在损失里加强高频权重。Fourier-DeepONet 走的是第二条路在 Trunk 的第一层前面插入随机傅里叶特征代价极小几乎不增加多少参数也不改变 DeepONet 原有的任意点查询能力。1.3 傅里叶特征这一步改动的价值边界我得说清楚它不是什么万能药。傅里叶特征能起作用的场景有一个共同前提目标函数的高频成分是有结构的、可以用固定频率基展开的比如波动解、湍流场、变系数方程的解。如果高频成分本质上是噪声那加傅里叶特征只会把噪声也一起放大。另一个边界是维度。傅里叶特征的随机矩阵 $B$ 尺寸是in_dim × num_freqs一维坐标时1 × 128参数量可以忽略但如果是三维或四维时空坐标4 × 256也还好可一旦到十维参数空间这个随机投影带来的方差就会开始拖后腿这时候通常要配合更大的num_freqs和更保守的 $\sigma$或者退回去用可学习的位置编码。提示判断你的任务需不需要傅里叶特征最简单的办法是先看目标解的频谱。把一批真值解做 FFT如果能量集中在低频 10% 的模态内普通 DeepONet 大概率够用如果高频段还有明显的能量平台那就值得上。还有一点容易被忽略傅里叶特征改变的是 Trunk 的输入表示不改变 Branch。这意味着网络对输入函数的编码能力没变变的只是查询坐标的分辨能力。所以如果你的瓶颈在输入侧比如输入是分辨率很高的图像、采样点特别稀疏那加傅里叶特征帮助有限得去改 Branch 的结构。2. 结构级拆解改动只有几行但每行都有讲究真正动手写的时候你会发现核心改动不到二十行代码。但这二十行里有三个参数必须想明白频率数、频率尺度 $\sigma$、以及归一化区间。这三个东西是绑在一起的任何一个没对上效果就会崩。2.1 傅里叶特征层的维度、归一化与 σ 的确定标准写法是把输入坐标 $x$ 映射为$$\gamma(x) \big[\sin(2\pi B x),\ \cos(2\pi B x)\big], \quad B \sim \mathcal{N}(0, \sigma^2)$$输出维度是2 × num_freqs。$\sin$ 和 $\cos$ 成对出现是有讲究的它保证了对坐标平移的等变性——只加 $\sin$ 的话坐标原点平移一下整个表示就变了。这里最容易翻车的是归一化的约定。如果坐标归一化到 $[0, 1]$那 $2\pi B x$ 里的 $2\pi$ 是对的$\sigma$ 就直接对应在整个域内振荡多少个周期如果坐标归一化到 $[-1, 1]$同样的 $\sigma$ 会让振荡频率变密一倍得改成 $\pi B x$ 才等价。我第一批实验就是在这里栽的训练集用 $[0,1]$ 归一化验证集脚本图省事用了[-1,1]结果验证误差怎么都下不去排查了大半天才发现是坐标尺度不一致。$\sigma$ 的确定有个很好用的经验算法。先估计目标解的最短波长 $\lambda_{\min}$ 和求解域长度 $L$则最高波数 $k_{\max} 2\pi/\lambda_{\min}$。坐标归一化到 $[0,1]$ 后$x$ 走完整域对应相位变化 $2\pi\sigma$要覆盖 $k_{\max} L$于是$$\sigma \approx \frac{k_{\max} L}{2\pi} \frac{L}{\lambda_{\min}}$$举个数求解域长 $L1$解里最短波长 $\lambda_{\min} 0.05$那 $\sigma \approx 20$。这个数和我实测出来的最优点基本吻合。注意这只是起点实际还要在 $[0.5\sigma, 2\sigma]$ 范围内扫一遍。num_freqs相对宽松一般取 64 到 256。它的作用是用多少个随机频率去覆盖这个频带太少覆盖不全太多则 Trunk 第一层变宽、显存涨收益递减。我的经验是 $\sigma$ 定好之后num_freqs取 128 就够除非解的空间分辨率特别高。2.2 Branch 端要不要也加傅里叶特征这里有个设计分歧值得单独拎出来说。我看到的版本是在 Trunk 上加理由很直接Trunk 编码的是查询坐标而频域分辨率低正是坐标编码的问题所在。但也有工作把傅里叶特征同时加到 Branch 的输入上对输入函数的采样值做一次随机投影。两种做法的效果不一样。只加 Trunk模型对输出场的空间细节更敏感但输入函数的高频信息仍然要靠 Branch 自己去啃。两边都加理论上表达能力更强但参数量和过拟合风险都上去了而且 Branch 的输入维度通常是几百维的传感器读数随机投影后维度会爆炸。我实测下来的结论是输入函数本身平滑、输出解高频的场合加 Trunk 一边就够输入函数本身就带强高频比如高对比度的介质场时Branch 端其实更该用卷积编码器去提取多尺度特征而不是简单做傅里叶投影。所以在波形反演这类任务上我最后采用的是Branch 用 1D/2D 卷积 Trunk 用傅里叶特征的混合结构比两边都硬套傅里叶特征稳定得多。2.3 输出层、偏置项与多物理场输出输出层有个容易漏的细节偏置项。前面公式里的 $b_0$ 是一个标量或多输出时是向量它提供了一个与输入无关的基线。别小看它很多解都有明显的均值偏移比如压力场、温度场没有这个偏置网络得靠基函数的组合去凑这个常数浪费容量还拖慢收敛。多物理场输出的情况要注意维度设计。如果一次要预测 $q$ 个场比如速度场 压力场标准做法是把 Trunk 输出改成 $p \times q$ 的矩阵Branch 输出仍是 $p$ 维最后得到 $q$ 维输出。这比训练 $q$ 个独立模型省事得多而且几个场之间还能共享基函数信息。激活函数我一般用 GELU比 ReLU 平滑对求导友好——这点在做物理约束损失时很重要因为要对输出做二阶甚至三阶自动微分激活函数的二阶导如果处处为零ReLU 就是这样物理残差项就废了。这个坑我踩过当时 PDE 残差死活降不下去后来发现是激活函数选错了换成tanh或 GELU 立刻正常。注意如果你的损失函数里包含 PDE 残差Trunk 和输出层绝对不能用 ReLU 家族否则二阶导为零物理项等于没加。这不是可选项。3. 从零跑通一维基准问题上的完整实操读论文最怕的就是看完觉得懂了、一写就废。所以我给自己定的规矩是任何算子学习的方法必须在两天内用一个自己能完全控制的一维问题上跑通再去碰真实任务。这一章把完整流程摊开包括数据生成、模型代码、训练细节和实测数字。3.1 数据生成与预处理选的问题是一维变系数扩散方程$$-\frac{d}{dx}\left(a(x)\frac{du}{dx}\right) f(x), \quad x \in (0,1), \quad u(0)u(1)0$$算子映射就是 $a(x) \mapsto u(x)$。选它有三个理由解足够光滑但会随 $a$ 的高频成分出现局部剧烈变化正好是频谱偏差的重灾区有限差分求解只要十几毫秒能快速造几千条数据边界条件固定省去处理变边界的麻烦。系数场 $a(x)$ 用几个不同频率的正弦叠加生成幅度按 $1/k$ 衰减保证整体正定最后 clip 到 $[0.2, \infty)$ 避免退化。右端项 $f(x)$ 固定为常数 1这样变化全来自 $a$任务更纯粹。数值解用有限差分组装三对角系统直接求解网格 $n201$ 个点。下面这段是数据生成的核心import numpy as np def solve_1d(n201, m_modes5, seed0): 生成一组 (a, u) 样本方程 -(a u) 1u(0)u(1)0 rng np.random.default_rng(seed) x np.linspace(0.0, 1.0, n) dx x[1] - x[0] a np.ones(n) for k in range(1, m_modes 1): amp rng.normal(0.0, 1.0) / k phase rng.uniform(0.0, 2.0 * np.pi) a 0.3 * amp * np.sin(2.0 * np.pi * k * x phase) a np.clip(a, 0.2, None) f np.ones(n) a_half 0.5 * (a[1:] a[:-1]) # 半节点系数长度 n-1 N n - 2 # 内点个数 A np.zeros((N, N)) for i in range(N): A[i, i] (a_half[i] a_half[i 1]) / dx**2 if i 0: A[i, i - 1] -a_half[i] / dx**2 if i N - 1: A[i, i 1] -a_half[i 1] / dx**2 u np.zeros(n) u[1:-1] np.linalg.solve(A, f[1:-1]) return x, a, u生成 1000 组样本前 800 组训练、后 200 组验证。传感器位置取 101 个每隔一个网格点取一个作为 Branch 的输入输出要求在全部 201 个网格点上求值。这样构造是有意的——输入比输出稀疏模拟真实场景中传感器数量少于全场分辨率的情况。预处理只有两步坐标除以域长归一化到 $[0,1]$输入输出不做标准化因为量级本来就接近 1。如果你的问题量级差异大务必先做标准化否则 Branch 和 Trunk 的梯度尺度会差好几个数量级。3.2 模型代码模型部分我把傅里叶特征单独抽成一个模块方便做消融import math import torch import torch.nn as nn class FourierFeatures(nn.Module): 坐标 - [sin(2πBx), cos(2πBx)]B 固定不训练 def __init__(self, in_dim, num_freqs, sigma): super().__init__() B torch.randn(in_dim, num_freqs) * sigma self.register_buffer(B, B) self.out_dim 2 * num_freqs def forward(self, x): proj 2.0 * math.pi * x self.B # x 已归一化到 [0,1] return torch.cat([torch.sin(proj), torch.cos(proj)], dim-1) def mlp(in_dim, hidden, out_dim, depth3, actnn.GELU): layers, d [], in_dim for _ in range(depth): layers [nn.Linear(d, hidden), act()] d hidden layers [nn.Linear(d, out_dim)] return nn.Sequential(*layers) class FourierDeepONet(nn.Module): def __init__(self, m_sensors, coord_dim1, p128, num_freqs128, sigma20.0, branch_hidden128, trunk_hidden128, depth3): super().__init__() self.branch mlp(m_sensors, branch_hidden, p, depth) self.fourier FourierFeatures(coord_dim, num_freqs, sigma) self.trunk mlp(self.fourier.out_dim, trunk_hidden, p, depth) self.bias nn.Parameter(torch.zeros(1)) def forward(self, a, x): b self.branch(a) # [B, p] t self.trunk(self.fourier(x)) # [N, p] return b t.T self.bias # [B, N] 注意register_buffer这一行不能省。如果直接把它当普通属性模型存盘再加载时 $B$ 会重新随机初始化推理结果和训练时对不上而且这种错误很难察觉——loss 看起来正常部署后精度莫名其妙掉一大截。3.3 训练循环、损失配比与监控损失用相对 L2$$\mathcal{L} \frac{1}{B}\sum_{i1}^{B} \frac{| \hat{u}_i - u_i |_2}{| u_i |_2 \epsilon}$$用相对误差而不是 MSE是因为不同样本的解幅值差异较大用 MSE 会让大幅值样本主导梯度小幅值区域的细节被忽略。def rel_l2(pred, target, eps1e-8): return torch.mean( torch.norm(pred - target, dim-1) / (torch.norm(target, dim-1) eps) ) model FourierDeepONet(m_sensors101, p128, num_freqs128, sigma20.0) opt torch.optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-5) sched torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max6000) for epoch in range(6000): model.train() perm torch.randperm(n_train) for i in range(0, n_train, 32): idx perm[i:i 32] pred model(a_in[idx], xq) loss rel_l2(pred, u_out[idx]) opt.zero_grad(); loss.backward() nn.utils.clip_grad_norm_(model.parameters(), 1.0) opt.step() sched.step()训练配置上几个关键点是踩坑踩出来的。学习率 1e-3 配 AdamW 余弦退火在这类任务上比较稳batch size 32 偏小但如果你的显存够、样本多可以往 128 拉收敛曲线会更平滑。梯度裁剪设为 1.0——傅里叶特征会让输入的高频方向梯度变大不裁剪的话偶尔会出现 loss 突然飙到 nan 的情况尤其是在 $\sigma$ 取得偏大的时候。监控指标我除了 train/val 的相对 L2还额外记录输出场的频谱能量比把预测和真值分别做 FFT看前 10% 模态和剩余模态的能量占比是否一致。这个指标比单纯的 L2 更能反映细节有没有学到——很多时候 L2 已经降到 $10^{-2}$但高频能量比只有真值的一半说明模型在偷懒。3.4 实测记录与调参曲线$\sigma$ 的扫描结果如下都是同一套数据、同样的训练轮数6000 步只改傅里叶特征参数$\sigma$num_freqs参数量训练集相对 L2验证集相对 L2高频能量保持率不加基线-3.4 万1.82e-22.11e-20.462643.6 万1.35e-21.61e-20.6181283.9 万4.7e-35.9e-30.83161283.9 万2.9e-34.1e-30.91201283.9 万3.1e-34.3e-30.90451283.9 万7.8e-31.02e-20.781001283.9 万2.6e-23.4e-20.52几条结论挺明确。第一$\sigma$ 从 0 加到 16误差降了一个数量级说明这一步改动的收益是实打实的。第二存在明显的最优区间我这个任务在 16 附近最好$\sigma$ 超过 45 就开始退化到 100 时比不加还差——随机频率太密$\sin$ 值在相邻采样点之间来回跳变变成了事实上的噪声。第三高频能量保持率比 L2 更能反映问题基线模型只有 0.46意味着解的一半高频细节被抹平了而 L2 看起来还行。训练成本上加傅里叶特征后单个 epoch 从 1.7 秒涨到 1.9 秒涨了大约 12%主要开销在 Trunk 第一层的矩阵乘法输入从 1 维变成 256 维。这个代价我认为完全值得。还有个附带发现$\sigma$ 取最优值时收敛速度也更快大概 2500 步就到了基线 6000 步的精度。原因不难理解——高频基函数一开始就提供了正确的归纳偏置网络不用花力气从低频慢慢爬上去。4. 迁移到波形反演任务上的经验一维问题跑通之后我把它往真正的目标场景上推了一步从观测波形反演介质速度模型。这部分是我这周花时间最多的也是最容易出问题的地方因为它的难点不在网络结构而在任务建模和训练策略。4.1 任务建模把反演写成算子映射波形反演要解决的问题是给定一组源在介质中激发、被检波器接收到的波形记录 $d(x_r, t)$反推介质的空间速度分布 $v(x)$。它能写成算子映射的形式$\mathcal{G}: d \mapsto v$。这里面有个建模选择。Trunk 输入什么输出场的定义域是二维空间所以 Trunk 吃的是 $(x, z)$ 这样的空间坐标加傅里叶特征后维度是2 × num_freqs再乘 2一维变二维后num_freqs通常要跟着翻倍因为两个方向的频率都需要覆盖。Branch 输入是波形原始维度是检波器数 × 时间采样点动辄几万维直接展平喂 MLP 是不可行的——这也是我说 Branch 端别硬套傅里叶特征的原因之一。我的做法是 Branch 用一个轻量的 2D 卷积编码器把波形按检波器 × 时间排成一张图两层卷积接池化把空间维度压掉最后拉平成特征向量。这样既保留了波形的时空结构走时、同相轴的斜率这些信息都在里面又把维度压到了几百。输出侧我用了双头结构主头输出速度模型 $v(x,z)$辅助头输出一个低分辨率的中间量。目的是给网络一个更容易学的辅助任务类似多任务学习里的技巧。实测下来辅助头对最终精度的提升大概 5% 到 8%不多但稳定。提示反演的输入输出量纲差异极大波形是压强量级速度是 km/s 量级务必对两边都做标准化。我一开始只标准化了波形结果训练前三万步 loss 几乎不动加了速度归一化之后立刻正常。4.2 训练策略分阶段、课程学习与物理约束波形反演是个强非线性问题一次性端到端训往往不收敛或者掉进局部极小。我用的是分阶段策略一共三段第一阶段纯数据驱动。只用有真值速度模型的样本损失就是相对 L2。这一段的目的是让网络先学到波形的大致走时特征对应什么尺度的速度结构把骨架搭起来。学习率 1e-3跑两万步。这一段结束时验证误差通常在 8% 到 12% 之间看起来很一般但曲线已经稳定。第二阶段加入低频课程。把输入波形做低通滤波只用低频段训练然后逐步放宽截止频率。这是从传统反演里借来的思路——低频决定大尺度背景高频决定细节。直接上全频段的话网络会优先拟合高频的局部扰动反而把背景速度搞乱。课程设计的参数是截止频率从 5 Hz 逐步升到 25 Hz每 5000 步升一次。第三阶段加物理约束。损失里加入波动方程残差项。具体做法是在预测出的速度场上做一次正演把正演波形和观测波形在数据域比对$$\mathcal{L}_{\text{phys}} \frac{|\mathcal{F}(v) - d|_2}{|d|_2}$$其中 $\mathcal{F}$ 是正演算子。这里要诚实说一句严格做自动微分的 PDE 残差代价太高每次迭代都要跑正演时间成本是纯数据驱动的几十倍。我实际采用的是折中方案——正演只在每 N 步做一次、用较低精度并且对这一项的学习率单独缩放。即便如此这一项带来的精度提升是显著的尤其是在数据覆盖不全的区域比如检波器稀疏的一侧物理约束能起到插值正则的作用避免反演出不合理的结构。注意物理项和数损失的梯度尺度差得很远直接相加会让物理项主导优化。我的做法是给物理项乘一个 0.05 到 0.2 的系数并且用梯度归一化让它和主损失的梯度范数保持同一量级。这个系数没有通用值得看具体问题。4.3 泛化能力的实测与分布漂移应对这部分是我最关心的。训练集我用的是分层介质模型水平层状、速度随深度递增、界面略有起伏测试集分三类同分布的分层模型、含局部异常体的模型、以及速度范围超出训练集 30% 的模型。后两类就是典型的分布漂移。结果比较能说明问题测试集类型基线 DeepONet 相对 L2Fourier-DeepONet 相对 L2同分布分层模型11.3%6.8%含异常体19.7%10.2%速度范围外推27.4%16.9%同分布上提升明显异常体上提升更大——这符合预期因为异常体带来的正是高波数扰动傅里叶特征正好擅长这个。外推场景两边的误差都很大但差距依然存在。不过我得泼点冷水分布漂移下的绝对误差依然不够用。17% 的相对误差在工程上意味着反演出来的速度结构只能当初始模型还得接传统迭代精修。这也是我现在对这个方法的定位——它是个高质量的初始化器和快速代理不是替代品。真要落地我倾向于先用它出初值再跑几轮传统反演整体迭代次数能压到原来的三分之一左右这个收益已经很可观了。另外还有个实测发现值得记一下训练集里加入少量含异常体的样本后外推性能改善得非常明显误差从 17% 降到 12% 左右。这说明网络缺的不是表达能力是见过的模式多样性。所以在数据生成阶段与其把某一类模型造到极致精细不如把类型铺开、每种几百个。5. 踩坑记录与排查速查表这周调试过程中遇到的问题我基本都记了这里挑高频的整理出来。有些是傅里叶特征特有的有些是 DeepONet 通病混在一起看反而更容易定位。5.1 损失卡在某个值不动最常见的是 loss 降到某个量级后纹丝不动训练曲线拉平。分三种情况排查第一种$\sigma$ 太小。傅里叶特征退化成近似恒等映射等于没加。验证方法很简单把 Trunk 第一层的权重打印出来看看输出尺度或者直接画一下 $\gamma(x)$ 随 $x$ 变化的曲线如果在一个域内只振荡一两次那 $\sigma$ 就是不够。第二种物理项系数过大。数据项和物理项在打架谁也不让谁。这时候你会看到两个 loss 分量交替下降总 loss 却不动。处理办法是把物理项系数降一个数量级或者改成两阶段训练。第三种学习率设置不当配合梯度裁剪把梯度裁干净了。梯度范数被裁到 1.0 之后如果长期贴着这个值说明真实梯度远大于此实际更新步长被严重压缩。可以临时关掉裁剪看看 loss 是否恢复下降。5.2 训练集拟合好、验证集崩掉这个我遇到过一次很典型的训练误差 3%验证误差 25%。查下来是传感器位置在训练和验证时不一致——数据生成脚本里用了随机采样传感器位置但训练脚本写死了一组固定索引。表面上都能跑实际上输入分布完全不同。除了这种低级错误正经的过拟合对策Trunk 的num_freqs别盲目调大128 到 256 之间一般够用再大就是纯增加容量Branch 加 dropout0.1 左右训练样本量至少要覆盖参数量的十分之一以上我这个模型 4 万参数800 个样本偏少加到 2000 之后验证误差降了将近一半。还有一点值得说num_freqs调大对过拟合的影响比想象中小。因为 $B$ 是固定的随机矩阵不参与训练增加频率数只是增加 Trunk 第一层的输入维度实际可训练参数增加得很有限。所以过拟合的锅一般不在傅里叶特征在后面的 MLP 太宽太深。5.3 高频细节依然糊加了傅里叶特征还是学不到细节通常是三个原因之一。一是训练数据的数值解本身就糊。如果你用的参考解是低阶格式算出来的数值耗散已经把高频磨掉了那网络学到的就是糊的解跟网络结构无关。这个坑很隐蔽建议拿一个解析解或者高阶格式的解做交叉验证确认参考解的高频是真实的。二是采样点太稀疏。输出在 201 个点上求值但输入只有 101 个传感器如果解的高频成分波长接近甚至小于采样间距那就是欠采样再怎么改网络也救不回来。这时候要么加密传感器要么降低目标频率范围。三是损失函数对高频不敏感。相对 L2 损失对小幅值的高频波动不敏感因为分母被大幅值的低频主导了。可以试试加一个频域的辅助损失对预测和真值分别做 FFT在频域算 L2。我用过一段时间对细节提升有帮助代价是每次迭代多一次 FFT慢个 10% 左右。5.4 一张表收尾把上面这些整理成速查表出问题时按症状对号入座能省不少时间症状最可能的原因优先尝试的改法loss 完全不动$\sigma$ 太小、坐标归一化不一致检查 $\gamma(x)$ 的振荡周期统一归一化区间loss 前期降后期平学习率过大触发梯度裁剪降 lr 到 5e-4临时关闭裁剪观察loss 出现 nan$\sigma$ 过大导致数值溢出降 $\sigma$检查输入是否有越界值训练好验证崩传感器位置/归一化不一致、样本太少固定预处理流程加样本Branch 加 dropout整体对细节糊参考解数值耗散、欠采样换高阶参考解或加密采样点高频过冲、出现伪影$\sigma$ 偏大、物理项系数过大降 $\sigma$ 到实测最优区降物理项权重外推性能差训练分布太窄扩充样本类型宁可粗糙也要多样推理结果和训练不一致B没注册成 buffer改用register_buffer并重新训练我个人在实际操作中的体会是这类方法的调参敏感度其实比想象中低——真正决定成败的是数据侧的功夫参考解够不够准、采样够不够密、分布够不够宽。网络结构上那点改动只要 $\sigma$ 别离谱、归一化别搞混基本都能跑出合理结果。下周打算把三维的算例补上看看傅里叶特征在更高维坐标下的表现另外把物理项的稀疏正演加速一下现在这一步还是整个流程里最慢的环节。