4. 数据生成与预处理实战
DeepONet 的预测能力上限,很大程度上由训练数据决定。与图像任务可以直接采集现成样本不同,动力系统算子学习的数据必须由我们自己"制造":给定一族初值条件、系统参数和时间域,用数值求解器逐条积分出轨迹,再整理成网络可消费的张量。本节围绕这条数据生产线展开,重点解决三个问题——采样空间怎么设计、数值求解怎么保证质量、数据怎么归一化与对齐并封装成可复用管道。
一、采样策略:在算子输入空间上布点
DeepONet 学习的是"输入函数 → 输出函数"的映射。以阻尼振子 ẍ + 2ζωₙẋ + ωₙ²x = 0 为例,分支网络的输入可以取初始状态 (x₀, v₀),主干网络的输入是时间坐标 t,输出是位移轨迹 x(t)。因此采样要覆盖三类维度:初始条件(x₀、v₀ 的取值区间)、系统参数(阻尼比 ζ、固有频率 ωₙ)、时间域(积分区间 [0, T] 与采样步长 Δt)。
采样方式上,均匀网格适合低维且边界重要的场景,但维度升高后样本数指数爆炸;拉丁超立方(LHS)在每个维度上分层抽样,能以较少样本获得较好的空间填充性,是参数采样的首选。对初值这类连续函数输入,还可采用高斯随机场(GRF)生成平滑的随机初值曲线,使分支网络见到更丰富的函数形态。一个可参考的示例配置如下(数值为教学假设,非实测结果):
import numpy as np
from scipy.stats import qmc
N_SAMPLES = 2000
sampler = qmc.LatinHypercube(d=4, seed=42)
u = sampler.random(N_SAMPLES) # [0,1]^4
# 映射到物理区间
x0 = -1.0 + 2.0 * u[:, 0] # 初始位移 [-1, 1]
v0 = -1.0 + 2.0 * u[:, 1] # 初始速度 [-1, 1]
zeta = 0.05 + 0.45 * u[:, 2] # 阻尼比 [0.05, 0.5]
wn = 1.0 + 4.0 * u[:, 3] # 固有频率 [1, 5] rad/s
时间域的采样需要与求解器精度匹配。若用四阶 Runge–Kutta 以 Δt=0.01 s 积分到 T=10 s,则每条轨迹有 1001 个时间点;主干网络若直接吃全部点,计算量偏大,通常再从中抽取 100~200 个配点作为主干输入,而监督标签仍保留全分辨率轨迹用于评估。
二、数值求解与数据质量校验
数据生成的第二步是逐样本积分。对刚性较强的参数区域(如 ζ 很小、ωₙ 很大),固定步长 RK4 可能失稳,此时应改用自适应步长的 solve_ivp(如 RK45 或 Radau),并设置 rtol=1e-8, atol=1e-10。求解完成后必须做质量校验,否则错误样本会悄悄污染训练集:检查轨迹是否出现 NaN/Inf、能量是否单调不增(无外力阻尼系统)、末端幅值是否已衰减到数值噪声以下。对不满足条件的样本应剔除并记录剔除率,若剔除率超过 5%,说明采样区间或求解器设置需要调整。
三、归一化:统计量必须"只来自训练集"
动力系统数据常跨越多个数量级(位移可能从 1e-3 到 1e0),不归一化会让损失被大幅值样本主导。推荐对分支输入、主干输入和标签分别做 Z-score:x̂ = (x − μ) / σ。关键在于 μ 和 σ 只能由训练集计算,验证集和测试集直接套用同一组统计量,否则会造成信息泄漏,使验证误差虚低。归一化参数应与数据集一起序列化保存,推理时原样加载,保证训练与部署的输入分布一致。
四、序列对齐与批处理
不同参数下轨迹的衰减速度差异很大,若按"末端幅值降到阈值"截断,轨迹长度会参差不齐。工程上通常统一在固定时间网格上重采样:对每条轨迹用线性或三次插值映射到 M 个等距时间点,得到形状一致的 (N, M) 标签矩阵。批处理时按样本维度切分,而不是按时间点切分——同一 batch 内应包含完整的轨迹,否则主干网络的配点会跨样本混叠。划分训练/验证/测试集时同样以样本为单位,并固定随机种子。
五、封装为可复用管道
把上述步骤写成一个由配置文件驱动的生成器,是让实验可复现的关键。建议的目录与接口约定:config.yaml 记录采样区间、样本数、求解器参数、归一化方式与随机种子;生成脚本读取配置后依次执行采样、求解、校验、归一化、对齐、划分,最终落盘为 dataset_v1.npz,同时写入配置哈希与样本数。下游训练脚本只依赖这个 npz 文件,不再关心数据如何产生。这样当需要扩大样本量或调整参数区间时,只需改配置并重新生成,训练代码零改动。