课程摘要
本节解决“网络搭好了,怎样才能稳定训练”的关键问题。课程围绕SGD、Momentum、Adam与AdamW讲解参数更新原理,学习余弦退火等学习率调度方法,并系统掌握权重衰减、Dropout、BatchNorm和Early Stopping。结合有限元代理模型,分析多尺度输出、过拟合、数据泄露和验证集设计等工程问题,帮助学习者建立从损失下降到物理可靠性验证的完整训练思维。
一、本节学习目标
完成本节后,你应当能够:
- 解释优化器在神经网络训练中的作用。
- 区分SGD、Momentum、Adam和AdamW。
- 理解学习率过大或过小产生的现象。
- 使用余弦退火动态调整学习率。
- 理解L2正则化、权重衰减、Dropout和BatchNorm。
- 根据验证集损失实施Early Stopping。
- 为位移、应力等多尺度力学输出设计合理损失函数。
- 判断模型是欠拟合、正常收敛还是过拟合。
- 使用PyTorch组织规范的训练与验证循环。
二、训练神经网络究竟在做什么
在前几节中,我们已经学习了MLP、CNN、LSTM和Transformer。无论使用哪一种网络,训练的核心都可以写成:
\[ \boldsymbol{\theta}^{*} = \underset{\boldsymbol{\theta}}{\arg\min} \; \mathcal{L} \left( \boldsymbol{\theta} \right) \]
其中:
- $\boldsymbol{\theta}$:网络中的权重和偏置;
- $\mathcal{L}$:损失函数;
- $\boldsymbol{\theta}^{*}$:使损失尽可能小的参数。
对于有限元代理模型,损失函数可能是:
\[ \mathcal{L} = \frac{1}{N} \sum_{i=1}^{N} \left( \hat{\sigma}_{i} - \sigma_{i} \right)^2 \]
其中:
- $\sigma_i$:有限元计算得到的真实应力;
- $\hat{\sigma}_i$:神经网络预测应力;
- $N$:训练样本数量。
网络训练的基本过程是:
```mermaid flowchart LR A[输入几何、材料与载荷] --> B[神经网络前向传播] B --> C[计算预测误差] C --> D[反向传播计算梯度] D --> E[优化器更新参数] E --> B B --> F[验证集评估] F --> G{验证损失是否改善} G -- 是 --> H[保存最佳模型] G -- 否 --> I[累计等待轮数] I --> J{达到耐心值} J -- 是 --> K[提前停止] J -- 否 --> B ```这里需要区分两个问题:
- 优化问题:怎样让损失下降?
- 泛化问题:怎样让模型在未见工况上仍然准确?
因此:
\[ \boxed{ \text{训练损失很低} \neq \text{模型具有良好的泛化能力} } \]
三、梯度下降:寻找损失函数的最低点
3.1 梯度的含义
损失函数关于参数的梯度为:
\[ \boldsymbol{g}_t = \nabla_{\boldsymbol{\theta}} \mathcal{L} \left( \boldsymbol{\theta}_t \right) \]
梯度指向损失增加最快的方向,因此参数需要沿梯度反方向更新:
\[ \boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t - \eta\boldsymbol{g}_t \]
其中 $\eta$ 为学习率。
可以将训练过程想象成在山谷中寻找最低点:
- 梯度告诉我们下坡方向;
- 学习率决定每一步走多远;
- 优化器决定如何利用当前和历史坡度。
3.2 学习率过大
如果学习率过大,参数可能跨过最低点并不断振荡:
\[ \eta\ \text{过大} \quad\Longrightarrow\quad \mathcal{L}\ \text{振荡甚至发散} \]
常见现象包括:
- 损失忽高忽低;
- 损失突然变成
NaN; - 应力预测出现极端值;
- 梯度迅速增大。
3.3 学习率过小
如果学习率过小:
\[ \eta\ \text{过小} \quad\Longrightarrow\quad \text{训练速度过慢} \]
模型虽然可能最终收敛,但需要大量训练轮数,甚至会暂时停留在平坦区域。
四、SGD:最基础的优化器
SGD即随机梯度下降。其参数更新公式为:
\[ \boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t - \eta \nabla_{\boldsymbol{\theta}} \mathcal{L}_t \]
这里的“随机”是指每次通常使用一个小批次数据估计梯度,而不是使用全部训练数据。
假设训练集中有10万组Abaqus参数化仿真结果,可以把每次用于训练的数据量设为:
\[ \text{batch size}=64 \]
这样每次只读取64个样本并更新一次参数。
SGD的优点
- 原理简单;
- 内存开销较低;
- 参数较少;
- 合理调参后可能具有良好的泛化能力。
SGD的不足
- 对学习率较敏感;
- 在狭长损失谷中容易来回振荡;
- 收敛速度可能较慢。
五、Momentum:让更新方向具有惯性
Momentum在梯度下降中加入历史方向:
\[ \boldsymbol{v}_t = \beta\boldsymbol{v}_{t-1} + (1-\beta)\boldsymbol{g}_t \]\[ \boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t - \eta\boldsymbol{v}_t \]
其中 $\beta$ 是动量系数,常见起始设置为:
\[ \beta=0.9 \]
它可以类比为一个沿山谷滚动的小球:
- 当前梯度决定新的加速方向;
- 历史梯度形成惯性;
- 一致方向上的更新得到增强;
- 来回振荡的方向相互抵消。
PyTorch中的典型写法为:
optimizer = torch.optim.SGD( model.parameters(), lr=0.01, momentum=0.9 )这里的0.01只是常用起始值,不是适用于所有问题的固定答案。
六、Adam:为不同参数自动调整步长
在力学预测网络中,不同输入特征可能具有完全不同的尺度。例如:
\[ E=210000\ \text{MPa} \]\[ \nu=0.3 \]\[ F=10000\ \text{N} \]
即使进行了标准化,不同网络参数的梯度幅值仍可能差异很大。Adam会分别估计梯度的一阶矩和二阶矩。
6.1 一阶矩
\[ \boldsymbol{m}_t = \beta_1\boldsymbol{m}_{t-1} + (1-\beta_1)\boldsymbol{g}_t \]
它近似表示梯度的移动平均。
6.2 二阶矩
\[ \boldsymbol{v}_t = \beta_2\boldsymbol{v}_{t-1} + (1-\beta_2)\boldsymbol{g}_t^2 \]
它反映梯度平方的移动平均。
6.3 偏差修正
\[ \hat{\boldsymbol{m}}_t = \frac{\boldsymbol{m}_t}{1-\beta_1^t} \]\[ \hat{\boldsymbol{v}}_t = \frac{\boldsymbol{v}_t}{1-\beta_2^t} \]
6.4 参数更新
\[ \boldsymbol{\theta}_{t+1} = \boldsymbol{\theta}_t - \eta \frac{ \hat{\boldsymbol{m}}_t }{ \sqrt{\hat{\boldsymbol{v}}_t}+\varepsilon } \]
Adam常被用作深度力学模型的初始选择:
optimizer = torch.optim.Adam( model.parameters(), lr=1e-3 )七、AdamW:将权重衰减与梯度更新分开
AdamW的核心特点是将权重衰减从自适应梯度更新中分离出来。简化表示为:
\[ \boldsymbol{\theta}_{t+1} = (1-\eta\lambda)\boldsymbol{\theta}_t - \eta \frac{ \hat{\boldsymbol{m}}_t }{ \sqrt{\hat{\boldsymbol{v}}_t}+\varepsilon } \]
其中 $\lambda$ 是权重衰减系数。
PyTorch写法为:
optimizer = torch.optim.AdamW( model.parameters(), lr=1e-3, weight_decay=1e-4 )对于CNN、Transformer及多数代理模型,可以先把AdamW作为基线,再根据验证集调整。
图18-1 SGD与Adam在狭长损失面上的优化轨迹
图中展示的是二维教学损失函数,不是真实力学模型训练结果。SGD首先迅速消除陡峭方向上的误差,随后沿平缓方向缓慢移动;Adam会根据不同方向的梯度尺度调整步长。
本次示例实际运行结果为:
SGD最终损失: 0.178473 Adam最终损失: 0.041897这只能说明Adam在这个示例及当前参数下下降更快,不能据此得出“Adam永远优于SGD”的结论。
八、学习率调度
固定学习率并不一定适合整个训练过程。常见策略是:
- 前期使用较大学习率,快速寻找合适区域;
- 后期逐渐减小学习率,进行精细收敛。
8.1 余弦退火
余弦退火学习率为:
\[ \eta_t = \eta_{\min} + \frac{1}{2} \left( \eta_{\max}-\eta_{\min} \right) \left[ 1+ \cos \left( \frac{\pi t}{T_{\max}} \right) \right] \]
其中:
- $\eta_{\max}$:初始学习率;
- $\eta_{\min}$:最小学习率;
- $T_{\max}$:完成一次退火的总轮数;
- $t$:当前轮数。
图18-2 余弦退火学习率变化过程
本例设置:
\[ \eta_{\max}=0.001 \]\[ \eta_{\min}=0.00001 \]
实际计算结果为:
Epoch 0: 0.001000 Epoch 50: 0.000505 Epoch 100: 0.000010PyTorch实现:
optimizer = torch.optim.AdamW( model.parameters(), lr=1e-3, weight_decay=1e-4 ) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR( optimizer, T_max=100, eta_min=1e-5 )训练时,应在完成参数更新后调用:
optimizer.step() scheduler.step()九、什么是过拟合
过拟合是指模型很好地记住了训练数据,却不能准确预测未见工况。
典型现象为:
\[ \mathcal{L}_{\mathrm{train}}\downarrow \]
但经过一段时间后:
\[ \mathcal{L}_{\mathrm{validation}}\uparrow \]
例如,模型可能记住了训练集中固定的网格位置,却没有真正学习到载荷、几何与应力之间的关系。
图18-3 训练损失与验证损失的典型变化
图中曲线是为了讲解过拟合而生成的教学模拟数据,不是真实有限元训练记录。
右图中的最佳验证损失出现在:
最佳轮数:Epoch 86 最佳验证损失:0.166295虽然Epoch 86之后训练损失继续下降,但验证损失已经开始回升,因此应保存Epoch 86对应的模型。
十、权重衰减与L2正则化
L2正则化在原损失函数中加入权重平方项:
\[ \mathcal{L}_{\mathrm{total}} = \mathcal{L}_{\mathrm{data}} + \lambda \sum_j w_j^2 \]
它会限制权重无限增大,使模型倾向于学习更加平滑的映射。
对于力学代理模型,这可能降低模型对个别异常仿真样本的过度敏感。
但是:
\[ \lambda\ \text{过大} \quad\Longrightarrow\quad \text{模型欠拟合} \]
初始实验可以尝试:
\[ \lambda \in \left\{ 10^{-5},10^{-4},10^{-3} \right\} \]
最终参数应由验证集决定。
十一、Dropout:训练时随机关闭部分神经元
Dropout在训练过程中,以概率 $p$ 将部分神经元输出设置为零:
\[ m_i \sim \operatorname{Bernoulli}(1-p) \]\[ \tilde{h}_i = \frac{m_i}{1-p}h_i \]
例如:
dropout = torch.nn.Dropout(p=0.1)其含义是训练时随机关闭约10%的特征。
Dropout为什么能够缓解过拟合
如果某个预测结果过度依赖少数神经元,随机关闭这些神经元会迫使网络寻找更分散、更稳健的特征组合。
使用时需要注意
调用:
model.train()时Dropout生效。
调用:
model.eval()时Dropout关闭。
因此,在验证和预测前必须执行:
model.eval()否则同一个有限元工况可能在不同预测中得到不同结果。
十二、BatchNorm:稳定中间特征分布
对于一个小批次中的特征,BatchNorm首先计算均值和方差:
\[ \mu_B = \frac{1}{m} \sum_{i=1}^{m}x_i \]\[ \sigma_B^2 = \frac{1}{m} \sum_{i=1}^{m} (x_i-\mu_B)^2 \]
然后标准化:
\[ \hat{x}_i = \frac{x_i-\mu_B} {\sqrt{\sigma_B^2+\varepsilon}} \]
最后执行可学习的缩放和平移:
\[ y_i = \gamma\hat{x}_i+\beta \]
典型网络可以写成:
model = torch.nn.Sequential( torch.nn.Linear(5, 64), torch.nn.BatchNorm1d(64), torch.nn.ReLU(), torch.nn.Dropout(0.1), torch.nn.Linear(64, 64), torch.nn.BatchNorm1d(64), torch.nn.ReLU(), torch.nn.Linear(64, 2) )若输入形状为:
[32, 5]表示32个样本、每个样本5个特征,则模型输出形状为:
[32, 2]可以分别对应:
\[ \begin{bmatrix} \hat{u}_{\max}, \hat{\sigma}_{\mathrm{Mises,max}} \end{bmatrix} \]
需要注意:当批量非常小时,BatchNorm统计量可能不稳定。此时可以尝试增加批量、去掉BatchNorm或使用LayerNorm。
十三、Early Stopping:停止在验证性能最好的位置
Early Stopping不是简单地“训练到某一轮就停止”,而是持续监控验证集。
假设:
\[ \text{patience}=10 \]
表示验证损失连续10轮没有改善时停止训练。
核心逻辑如下:
best_loss = float("inf") best_state = None wait = 0 patience = 10 for epoch in range(200): train_one_epoch() validation_loss = validate() if validation_loss < best_loss: best_loss = validation_loss best_state = { name: value.detach().cpu().clone() for name, value in model.state_dict().items() } wait = 0 else: wait += 1 if wait >= patience: print("Early stopping at epoch:", epoch + 1) break model.load_state_dict(best_state)预期输出形式
实际轮数取决于数据和随机种子,因此不应预先写死。正常日志可能类似:
Epoch 001 | train_loss=0.8421 | val_loss=0.8014 Epoch 020 | train_loss=0.1835 | val_loss=0.2048 Epoch 046 | train_loss=0.0728 | val_loss=0.1162 Early stopping at epoch: 56 Best validation loss: 0.1162最终恢复的是验证损失最低时的参数,而不是停止时最后一轮的参数。
十四、有限元代理模型实战设计
考虑一个悬臂梁参数化数据集。
14.1 输入参数
\[ \boldsymbol{x} = \begin{bmatrix} L,\ b,\ h,\ E,\ F \end{bmatrix} \]
其中:
- $L$:梁长;
- $b$:截面宽度;
- $h$:截面高度;
- $E$:弹性模量;
- $F$:端部载荷。
14.2 预测目标
\[ \boldsymbol{y} = \begin{bmatrix} u_{\max},\ \sigma_{\max} \end{bmatrix} \]
其中:
- $u_{\max}$:最大位移,单位mm;
- $\sigma_{\max}$:最大应力,单位MPa。
14.3 为什么不能直接计算总MSE
假设:
\[ u_{\max}=0.25\ \text{mm} \]\[ \sigma_{\max}=320\ \text{MPa} \]
如果直接计算两个输出的MSE,应力误差可能完全支配总损失,导致模型忽视位移。
正确方法是分别标准化:
\[ u' = \frac{u-\mu_u}{s_u} \]\[ \sigma' = \frac{\sigma-\mu_\sigma}{s_\sigma} \]
标准化后的损失可以写成:
\[ \mathcal{L} = \operatorname{MSE} \left( \hat{u}',u' \right) + \operatorname{MSE} \left( \hat{\sigma}',\sigma' \right) \]
预测结束后再执行反标准化:
\[ \hat{u} = \hat{u}'s_u+\mu_u \]\[ \hat{\sigma} = \hat{\sigma}'s_\sigma+\mu_\sigma \]
十五、推荐的PyTorch训练结构
for epoch in range(max_epochs): # 1. 训练阶段 model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() prediction = model(x_batch) loss = loss_function(prediction, y_batch) loss.backward() # 可选:防止梯度爆炸 torch.nn.utils.clip_grad_norm_( model.parameters(), max_norm=1.0 ) optimizer.step() # 2. 验证阶段 model.eval() validation_loss = 0.0 with torch.no_grad(): for x_batch, y_batch in validation_loader: prediction = model(x_batch) validation_loss += loss_function( prediction, y_batch ).item() validation_loss /= len(validation_loader) # 3. 更新学习率 scheduler.step() # 4. 保存最佳模型并判断是否提前停止 update_early_stopping(validation_loss)训练循环中最容易遗漏的几个步骤是:
optimizer.zero_grad() loss.backward() optimizer.step()验证阶段则必须使用:
model.eval() with torch.no_grad(): ...十六、训练集、验证集和测试集怎么划分
有限元数据不能简单地把全部节点随机打乱后划分。
假设每个Abaqus工况产生5000个节点,如果同一工况中的部分节点进入训练集、另一部分进入测试集,模型实际上已经见过该工况的几何、载荷和材料信息。
合理做法是按完整仿真工况划分:
\[ \text{训练工况} : \text{验证工况} : \text{测试工况} = 70\%:15\%:15\% \]
例如:
- 训练集:裂纹长度2、4、6、8 mm;
- 验证集:裂纹长度5、9 mm;
- 测试集:裂纹长度7、10 mm。
这样才能检验模型对未见裂纹长度的泛化能力。
同时,均值和标准差只能由训练集计算:
\[ \mu_{\mathrm{train}}, \quad s_{\mathrm{train}} \]
验证集与测试集必须使用同一组训练统计量,不能各自重新标准化。
十七、怎样阅读训练曲线
情况一:训练损失和验证损失都很高
可能原因:
- 网络容量不足;
- 学习率过小;
- 输入特征不足;
- 训练时间不够;
- 数据或标签存在错误。
这通常属于欠拟合。
情况二:训练损失持续下降,验证损失开始上升
说明发生过拟合,可以尝试:
- Early Stopping;
- 增加权重衰减;
- 加入Dropout;
- 减小网络;
- 增加训练工况;
- 检查数据划分。
情况三:损失大幅振荡
可以检查:
- 学习率是否过大;
- 是否存在异常值;
- 输入与输出是否标准化;
- 梯度是否爆炸;
- 数据中是否出现
NaN或无穷大。
情况四:损失下降后长期不变
可以尝试:
- 调整学习率;
- 使用学习率调度;
- 检查激活函数;
- 检查模型是否欠拟合;
- 检查目标量是否被某一大尺度输出支配。
十八、建议的起始配置
以下参数只用于建立第一个基线模型:
| 配置项 | 建议起始值 |
|---|---|
| 优化器 | AdamW |
| 初始学习率 | $10^{-3}$ |
| 权重衰减 | $10^{-4}$ |
| Batch size | 32或64 |
| Dropout | 0.1 |
| 最大训练轮数 | 200 |
| Early Stopping耐心值 | 10~20 |
| 学习率调度 | 余弦退火 |
| 梯度裁剪 | 1.0 |
| 随机种子 | 固定 |
这些参数不是“标准答案”。一次只调整少量参数,并记录验证集结果,比同时改变所有设置更容易找到问题。
十九、模型训练后的力学检查
损失函数下降只是数值层面的检查。完成训练后还需要验证:
19.1 边界条件
固定端位移应近似满足:
\[ \boldsymbol{u} = \boldsymbol{0} \]
19.2 载荷—响应趋势
在线弹性范围内:
\[ F\uparrow \quad\Longrightarrow\quad u\uparrow \]
若载荷增大而预测位移反而减小,需要检查数据与模型。
19.3 刚度趋势
其他条件相同时:
\[ E\uparrow \quad\Longrightarrow\quad u\downarrow \]
19.4 应力集中区域
含孔洞或裂纹模型中,最大应力通常应出现在几何不连续区域附近,而不是随机出现在远离缺陷的位置。
19.5 量纲恢复
标准化空间中的误差必须还原到物理单位:
\[ \operatorname{MAE}_u\ [\text{mm}] \]\[ \operatorname{MAE}_\sigma\ [\text{MPa}] \]
只有这样才能判断误差是否具有工程意义。
二十、常见错误
- 只保存最后一轮模型,没有保存验证集最优模型。
- 验证时忘记执行
model.eval()。 - 验证阶段忘记关闭梯度计算。
- 将测试集用于选择学习率和网络结构。
- 使用全部数据计算标准化参数,造成数据泄露。
- 位移与应力未经标准化便直接相加计算损失。
- 学习率过大导致损失出现
NaN,却误认为模型结构错误。 - 同时使用很大的Dropout和权重衰减,导致严重欠拟合。
- 只看训练损失,不绘制验证损失。
- 把较低的数据误差直接等同于较高的物理可靠性。
二十一、课堂练习
基础练习
- 将Adam的学习率分别设置为:
\[ 10^{-2},\quad10^{-3},\quad10^{-4} \]
比较损失下降速度。
将Dropout从0改为0.1、0.3和0.5,比较训练损失与验证损失。
将Early Stopping的耐心值从10改为20,观察停止轮数。
进阶练习
- 使用SGD、Adam和AdamW训练同一个模型。
- 保证模型、数据划分和随机种子完全一致。
- 记录最佳验证损失、最佳轮数和测试误差。
- 将位移与应力分别还原为mm和MPa。
- 判断更低的标准化损失是否一定对应更小的工程误差。
思考题
为什么不能根据测试集误差选择优化器?
参考答案:
测试集应模拟模型最终面对的未知数据。如果反复根据测试结果调整优化器,测试信息就参与了训练决策,最终误差将不再代表真实泛化能力。
二十二、本节小结
本节可以概括为三条主线:
\[ \boxed{ \text{优化器决定参数怎样更新} } \]\[ \boxed{ \text{学习率调度决定每一步走多远} } \]\[ \boxed{ \text{正则化与Early Stopping控制模型何时停下} } \]
一个可靠的训练过程应当同时关注:
- 训练损失是否下降;
- 验证损失是否改善;
- 测试工况是否真正独立;
- 位移和应力是否正确标准化;
- 预测结果是否满足基本力学规律。
最终需要记住:
\[ \boxed{ \text{稳定收敛} + \text{良好泛化} + \text{物理验证} = \text{可信的力学代理模型} } \]
配套文件
- [完整可运行绘图与优化器演示代码]
- [实际运行结果]
【零基础学智能仿真-18】深度学习训练策略-优化器、正则化与稳定收敛资源-CSDN下载