资讯动态

PyTorch正则化实战:权重衰减、Dropout与训练流程全解析

发布时间:2026/10/26 5:45:48 来源:尧图企业网站定制
正则化这三个字在 PyTorch 项目里出现的频率几乎和loss.backward()一样高但真正能把它用对的人并不多。大多数人写训练脚本时的动作是weight_decay1e-4写上nn.Dropout(0.5)加上然后就默认自己做过正则化了。等模型在验证集上开始飘再回头加更多的东西最后把网络调成一锅粥还说不清是哪一项起了作用。这篇文章想做的事情很直接把 PyTorch 里能称作正则化的手段一次性摊开讲清楚每一种背后在干什么、代码里怎么写、以及在什么数据规模、什么模型结构、什么训练阶段该用它。不管你是刚跑通第一个 MNIST 的新手还是在为大模型微调时 loss 曲线发愁的熟手下面这些内容都能直接抄到你的训练脚本里。需要先明确一个前提正则化在 PyTorch 里从来不是某一个 API而是一组分散在损失函数、网络层、优化器、数据管道和训练循环里的技巧。它们唯一的共同点是都在对抗同一件事——模型把训练集背下来了。所以我会按它在哪里介入训练来分类而不是按教科书那种 L1/L2 的数学分类这样你在改代码的时候能更快对上号。1. 先搞清楚过拟合在 PyTorch 里长什么样再谈该加什么很多讨论正则化的文章一上来就列方法我倾向于反过来。因为你只有先认出过拟合的具体形态才知道该加哪一味药。PyTorch 的训练循环给了你非常多的观测点但多数人只盯着train_loss和val_loss两个数字这就把问题简化过头了。1.1 三种典型曲线形态对应的不同病因第一种是训练损失一路下滑逼近 0验证损失在某个 epoch 之后反弹上升两者形成明显的喇叭口。这是最标准的过拟合模型容量相对数据量过剩加权重衰减、Dropout 或者数据增强都会有明显收益。第二种是训练损失和验证损失同时很高且贴得很近这不是过拟合而是欠拟合你这时候加正则化只会让情况更糟应该先考虑加容量、调学习率或者检查数据标注。第三种比较隐蔽两个损失都在降但验证集上的关键指标比如 mAP、F1、AUC早就不涨甚至开始掉了。这种情况在类别不平衡的任务里特别常见模型在优化一个和你真正关心的指标不完全一致的目标这时候该动的是损失函数和采样策略而非简单地往网络里塞 Dropout。我自己的习惯是在训练脚本里同时记录train_loss、val_loss、验证指标、以及梯度的范数。梯度范数这个量很多人不记录但它对判断正则化是不是加过头了非常有用——当你发现grad_norm长期处在极低的值而训练损失又降不下去基本可以判断是权重衰减或者梯度裁剪把模型压死了。1.2 PyTorch 里正则化介入训练的五条路径与其记方法名不如记路径。第一条是优化器路径weight_decay参数就挂在这里它在参数更新时起作用。第二条是损失路径你在loss后面手工加惩罚项或者给CrossEntropyLoss传label_smoothing。第三条是网络结构路径nn.Dropout、nn.BatchNorm2d、Stochastic Depth 这类层在前向传播时引入随机性或归一化。第四条是数据路径torchvision.transforms里那一堆随机变换、Mixup、CutMix都是从这里进来的。第五条是训练流程路径Early Stopping、EMA、SWA、梯度裁剪它们不改模型结构也不改损失改的是你怎么用这些梯度。这个划分的价值在于当你要做消融实验或者排查问题时能快速定位该关掉哪一块。我见过不少人把 Dropout、weight decay、数据增强同时上新效果变好了却说不清是谁的功劳结果换到另一个数据集上照搬参数就翻车——因为新数据集可能根本不需要那么多正则化。1.3 一个常被忽略的前提正则化的强度要和训练预算匹配这一点很少被写进文档。正则化的本质是给优化过程增加约束而约束生效需要足够的迭代次数去消化。你在一个只训练 10 个 epoch 的小实验里设weight_decay1e-2模型根本来不及在约束下找到好的解表现出来的就是欠拟合。反过来在需要训练几百个 epoch 的大任务里1e-4 的权重衰减可能几乎不起作用。经验上的做法是先不加任何正则化把模型训到过拟合看清楚它的上限在哪然后再逐项加入每次只加一项并观察验证曲线的变化。这个过程虽然笨但它是唯一能让你对参数建立直觉的方式。跳过这一步直接抄别人的超参你永远不知道该往哪个方向调。2. 权重衰减与 L1/L2最容易被误用的那个默认选项weight_decay是 PyTorch 优化器里最容易被无脑填上的参数也是误解最多的一项。很多人以为它等价于 L2 正则这在 SGD 下基本成立但在 Adam 系列优化器下就不成立了。更麻烦的是很多人把权重衰减作用到了所有参数上包括 bias 和 BatchNorm 的缩放参数这就属于典型的用错了还以为在做正则化。2.1 weight_decay 到底在更新公式里做了什么在torch.optim.SGD里weight_decay的实现是把梯度改成g λw然后按w w - lr * (g λw)更新。这确实等价于在损失里加λ/2 * ||w||²因为那一项的梯度就是λw。这就是经典的 L2 正则。但在torch.optim.Adam里weight_decay是把λw加到梯度上之后再走 Adam 的一阶、二阶动量归一化。这意味着惩罚项会被自适应学习率缩放权重大的参数受到的衰减反而不一定大效果和真正的 L2 相去甚远。这也是AdamW被提出来的原因它把衰减从梯度里拆出来直接乘在参数上w w - lr * (adam_update λw)做到解耦。实际选型上如果你用的是 Adam 系直接换AdamW别在Adam(weight_decay...)上纠结。这是我在多个视觉和 NLP 任务上反复验证过的结论AdamW在同样的weight_decay数值下通常给出更稳定的泛化表现尤其是在微调预训练模型的时候。# 推荐写法AdamW 参数分组 decay_params, no_decay_params [], [] for name, param in model.named_parameters(): if not param.requires_grad: continue # bias 和归一化层的仿射参数不进衰减 if param.ndim 1 or name.endswith(.bias): no_decay_params.append(param) else: decay_params.append(param) optimizer torch.optim.AdamW( [ {params: decay_params, weight_decay: 0.01}, {params: no_decay_params, weight_decay: 0.0}, ], lr3e-4, )2.2 为什么 bias 和 Norm 层参数不该被衰减这是个值得展开讲的细节因为它解释了上面那段参数分组的动机。bias 的作用是给激活值做平移它不控制模型的复杂度衰减它只会让模型更难拟合数据的整体偏移。想象一下你的数据均值不在 0 附近衰减 bias 相当于强行把模型往原点拽模型得花额外的容量去对抗这个拉力纯属内耗。归一化层的weight和bias更特殊。BatchNorm的weight初始值是 1它控制的是归一化后特征的缩放。如果对它做权重衰减等于不断地把缩放系数往 0 拉归一化的效果会被削弱深层网络尤其容易出现训练不稳。这类参数数量很少衰减它们带来的正则化收益微乎其微风险却实实在在。一个实用的判断规则维度大于 1 的参数才考虑衰减。卷积核是 4 维线性层权重是 2 维这些是真正控制表达能力的bias 是 1 维Norm 层的weight也是 1 维全部排除。上面代码里的param.ndim 1就是这个意思。2.3 L1 正则为什么在 PyTorch 里没有现成开关PyTorch 的优化器只提供了weight_decay也就是 L2。想让模型产生稀疏性、做特征选择你得自己把 L1 项加进损失里def l1_penalty(model, exclude_normTrue): total 0.0 for name, p in model.named_parameters(): if not p.requires_grad: continue if exclude_norm and (p.ndim 1): continue total total p.abs().sum() return total loss criterion(logits, target) 1e-5 * l1_penalty(model) loss.backward()这里有几个坑必须提醒。第一1e-5这个系数要和你的主损失量级匹配如果你的CrossEntropyLoss在 2.0 左右L1 项在几十甚至上百的量级因为要对所有参数求和那这个系数必须压得很小否则训练会直接崩。第二L1 在 0 点不可导PyTorch 里abs()在 0 处的梯度是 0所以实际优化出来的解不会像理论那样精确稀疏别指望它能帮你把特征数精确降到某个值。第三L1 和 AdamW 的自适应学习率配合时行为比较怪稳妥的做法是 L1 配合 SGD 动量使用或者用近端算子类的优化器。我个人的态度是L1 在现代深度网络里用得比 L2 少得多它更适合线性模型、浅层网络、或者明确需要可解释性的场景。深度模型想要稀疏性剪枝、结构化稀疏训练这些手段比 L1 更直接可控。3. Dropout 家族位置、概率与推理一致性如果只能保留一种正则化手段我会选 Dropout因为它的性价比最高、最不需要调参、适用范围最广。但 Dropout 也是会用和用对之间差距很大的一项重点不在加不加而在加在哪、p 取多少、以及有没有在推理时正确关掉。3.1 nn.Dropout 与 F.dropout 的行为差异nn.Dropout是模块注册在model.modules()里会随着model.train()/model.eval()自动切换开关。torch.nn.functional.dropout是函数需要你手动传trainingself.training忘了传的话它在评估阶段仍然会随机丢神经元导致你的推理结果每次都不一样——这个 bug 我在别人的代码里见过不止一次而且因为结果只是略微抖动很难第一时间发现。还有一个细节PyTorch 的 Dropout 在训练时会把保留的激活值除以1-p这叫 inverted dropout保证训练和推理的期望一致。所以推理时你不需要做任何缩放直接model.eval()就行。有些人还在推理前手动乘(1-p)那是老式实现的习惯在 PyTorch 里属于画蛇添足会让输出整体偏小。class Net(nn.Module): def __init__(self, p0.2): super().__init__() self.fc1 nn.Linear(768, 256) self.drop nn.Dropout(p) # 用模块别用函数 self.fc2 nn.Linear(256, 10) def forward(self, x): x F.relu(self.fc1(x)) x self.drop(x) # train/eval 自动切换 return self.fc2(x)3.2 结构化的变体Dropout2d、Dropout3d 与 SpatialDropout通道维度的 Dropout 在卷积网络里比普通 Dropout 更有效。nn.Dropout2d会按通道整张特征图地丢弃而不是逐元素丢。这样做的理由是卷积特征图的相邻像素高度相关逐元素丢的话信息会从邻居那里漏回来正则化效果被稀释整通道丢才能真正逼迫网络学到冗余的表征。输入形状要注意nn.Dropout2d期望(N, C, H, W)。如果你在做序列任务输入是(N, L, D)要么用nn.Dropout1d形状(N, C, L)要么干脆先transpose再丢。这个形状要求在 1D 卷积和 Transformer 混用的模型里经常被忽略结果就是 Dropout 静默地按错误维度工作没报错但效果打折。至于 Stochastic Depth也叫 DropPath它是把整个残差块随机跳过在 ResNet、ViT 这类深层结构里效果显著。它的直觉是深层网络里有大量冗余层随机跳过一部分相当于训练了一个隐式的子网络集成而且因为跳过的层不参与前向和反向还能省算力。实现上要注意必须只在训练时启用且通常和层深度挂钩浅层少丢深层多丢def drop_path(x, drop_prob: float, training: bool): if drop_prob 0.0 or not training: return x keep 1 - drop_prob shape (x.shape[0],) (1,) * (x.ndim - 1) mask x.new_empty(shape).bernoulli_(keep) return x * mask / keep3.3 p 到底取多少以及放在哪些位置这是实操中最常被问的问题。我的经验值全连接层之间p0.2~0.5卷积层之后p0.1~0.3Embedding 之后p0.1~0.2Transformer 的 attention 和 FFN 之间通常p0.1。大模型微调时Dropout 往往还要再降因为预训练权重已经很稳太强的随机性反而破坏它。位置的判断标准是Dropout 放在非线性激活之后、下一层线性变换之前。放在激活之前也行效果差别不大但不要放在最终的输出层之前还设大 p 值那会让 logits 的方差过大配合交叉熵容易训不动。还有一个反直觉的点Dropout 和 BatchNorm 同时使用时两者的统计量会互相干扰。训练时 Dropout 让激活值的方差随批次波动BatchNorm 的 running mean/var 估计变得更不稳。稳妥的做法是卷积主干用 BatchNorm 不用 Dropout全连接头用 Dropout 不用 BatchNorm两者在空间上分开。如果你确实要混用把 Dropout 放在 BN 之后别再放它前面。4. 归一化层与隐式正则那些不算正则化的正则化BatchNorm、LayerNorm、GroupNorm 这些层严格来说不是正则化它们的设计初衷是稳定训练、加速收敛。但实践中它们确实能提升泛化原因在于每个 batch 的统计量都带噪声前向传播时引入的这点随机性起到了类似 Dropout 的效果。有些论文专门研究过这件事结论是 BN 的正则效应在 batch size 较小时更明显。4.1 BatchNorm 的隐式正则与其代价nn.BatchNorm2d在训练时用当前 batch 的均值和方差做归一化同时用滑动平均更新running_mean和running_var推理时用这两个滑动统计量。这个机制带来两个后果一是训练和推理的行为不一致二是 batch size 太小的时候统计量噪声过大模型性能会明显下降。后者是实际部署时的大坑。你在训练时用 256 的 batch推理时 batch 变成 1如果直接model.eval()其实没问题但如果你忘了切 eval 模式BN 会用单样本的均值方差去归一化输出必然是错的。还有一类问题出现在分布式训练里BatchNorm2d默认只在单卡内统计多卡时等效 batch 变小这时候应该换nn.SyncBatchNormmodel nn.SyncBatchNorm.convert_sync_batchnorm(model) model nn.parallel.DistributedDataParallel(model, device_ids[local_rank])要注意convert_sync_batchnorm必须在包 DDP 之前调用顺序反了不生效。而且转换之后模型参数名会变多了bn前缀之类如果你有预训练权重加载逻辑得处理 key 的映射。4.2 什么时候该换成 LayerNorm 或 GroupNorm判断依据是 batch size 和你对训练/推理一致性的要求。目标检测、分割这类任务因为输入分辨率不固定batch 往往开不大BN 的统计量不稳这时候换成 GroupNorm 通常能带来明显提升。Transformer 类结构天然用 LayerNorm因为它对序列长度和 batch 都不敏感。至于 Weight Standardization 和 Spectral Normalization前者是把权重标准化后再做卷积能替代 BN 的部分作用同时避免 batch 依赖后者控制权重矩阵的谱范数在 GAN 的判别器里几乎是标配。这两个属于进阶手段普通分类任务用不上但如果你在做生成模型或者对训练稳定性要求极高值得了解。5. 标签层面的正则化Label Smoothing、Mixup、CutMix前面几类动的都是模型和参数这一类动的是监督信号本身。它们的共同思路是不要给模型一个绝对正确的目标让它对正确答案保留一点不确定性。这在标签可能有噪声、或者类别之间存在语义关联的任务里收益很大。5.1 Label Smoothing 的一行实现与适用边界PyTorch 从 1.10 开始nn.CrossEntropyLoss直接支持label_smoothing参数criterion nn.CrossEntropyLoss(label_smoothing0.1)它的作用是把 one-hot 标签[0, 0, 1, 0]变成[0.033, 0.033, 0.9, 0.033]以 4 类、ε0.1 为例模型不再被要求把正确类别的 logit 推到无穷大。副作用是模型的置信度校准更好预测概率更接近真实准确率这在需要做阈值判断或者给下游系统提供概率的场景里很有价值。但它不是万能的。在类别极度不平衡的任务里标签平滑会进一步压缩少数类的监督信号可能适得其反。另外在蒸馏任务里如果你要用教师模型的软标签就不该再对学生加 label smoothing两者叠加会让目标过于平滑学生学不到尖锐的决策边界。5.2 Mixup 与 CutMix把数据增强做到损失函数里这两种方法的共同点是混合两个样本及其标签区别在于混合方式。Mixup 是在像素层面做线性插值CutMix 是裁剪一块区域贴上去并按面积比例混合标签。def mixup_batch(x, y, alpha0.2, num_classes10): lam np.random.beta(alpha, alpha) idx torch.randperm(x.size(0), devicex.device) mixed_x lam * x (1 - lam) * x[idx] y_onehot F.one_hot(y, num_classes).float() mixed_y lam * y_onehot (1 - lam) * y_onehot[idx] return mixed_x, mixed_y # 训练循环里配套用软标签损失 logits model(mixed_x) loss -(mixed_y * F.log_softmax(logits, dim1)).sum(dim1).mean()参数alpha控制混合强度的分布越小越倾向于取接近 0 或 1 的lam也就是混合得越轻。分类任务上常用的范围是 0.2 到 1.0检测和分割任务上 Mixup 用得很少因为框和掩码没法做线性插值。CutMix 相对更适合视觉任务因为它保留了局部区域的真实像素不会产生自然界不存在的混合纹理。实现上需要计算裁剪框的坐标并按面积算lam代码稍长但很多训练框架里都有现成的封装。我要强调一个实践中的观察这两种方法在数据量充足、模型本身已经很强的场景下收益会明显衰减甚至会拖慢收敛。它们真正发光的地方是小数据集、以及需要强泛化的竞赛类任务。如果你只有几千张图Mixup 加上适度的数据增强效果往往比调网络结构更显著。6. 训练流程层面的正则化Early Stopping、EMA、SWA 与梯度裁剪这一类的特点是完全不动模型定义只改训练循环怎么写。好处是随时可以开关坏处是它们容易被忽略因为不体现在网络结构里。6.1 Early Stopping 的正确写法Early Stopping 的逻辑很简单验证指标连续 N 个 epoch 没提升就停。但写对有两个细节。第一监控的指标要和你的最终目标一致分类任务别只盯 loss看准确率或者 F1第二保存的是最佳 epoch的权重而不是最后 epoch 的权重best_score, best_state, patience_counter -1.0, None, 0 PATIENCE 8 for epoch in range(EPOCHS): train_one_epoch(model, loader, optimizer, criterion) score evaluate(model, val_loader) if score best_score 1e-4: best_score score best_state {k: v.detach().clone() for k, v in model.state_dict().items()} patience_counter 0 else: patience_counter 1 if patience_counter PATIENCE: break model.load_state_dict(best_state)1e-4这个最小改善阈值很重要没有它的话验证指标的微小抖动会不断重置计数器patience 形同虚设。另外best_state必须用clone()做深拷贝直接存state_dict()拿到的是引用后续训练会把它改掉——这个坑非常隐蔽我第一次踩的时候排查了大半天。6.2 EMA 与 SWA在参数空间做平均指数滑动平均EMA维护一份模型参数的滑动副本用shadow decay * shadow (1 - decay) * param更新。它在训练末期几乎不花额外算力却能稳定提升一两个点在检测、分割、自监督这些任务里几乎是标配。decay一般取 0.999 到 0.9999训练步数越多取越大。class ModelEMA: def __init__(self, model, decay0.999): self.decay decay self.shadow {k: v.detach().clone() for k, v in model.state_dict().items()} torch.no_grad() def update(self, model): for k, v in model.state_dict().items(): if v.dtype.is_floating_point: self.shadow[k].mul_(self.decay).add_(v.detach(), alpha1 - self.decay) else: self.shadow[k].copy_(v)SWA随机权重平均思路不同它是在训练的后段每隔若干步采一次权重做等权平均配合一个周期性变化的学习率效果更好。PyTorch 有官方封装torch.optim.swa_utils用起来不复杂from torch.optim.swa_utils import AveragedModel, SWALR, update_bn swa_model AveragedModel(model) swa_scheduler SWALR(optimizer, swa_lr0.05) for epoch in range(SWA_START, EPOCHS): train_one_epoch(model, loader, optimizer, criterion) swa_model.update_parameters(model) swa_scheduler.step() # 关键一步重估 BN 统计量 update_bn(loader, swa_model, devicecuda) torch.save(swa_model.state_dict(), swa.pth)update_bn这一步千万别漏。SWA 平均后的权重对应的激活分布和原始权重不同不重新跑一遍前向更新 BN 的 running 统计量推理效果会明显掉。6.3 梯度裁剪它不是正则化但常常被当成正则化用torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)的本来目的是防止梯度爆炸在 RNN 和 Transformer 训练里几乎是必需品。它确实能间接起到稳定训练的作用但它不改变模型的解空间严格说不算正则化。真正需要注意的是裁剪阈值的选择。max_norm1.0是在大批 NLP 任务上被验证过的默认值但如果你在训练视觉模型梯度范数本来就在 0.1 到 10 之间波动机械地设 1.0 会把大部分梯度都截掉训练变得极慢。稳妥做法是前几百步先记录梯度范数的分布取 95 分位数左右作为阈值。7. 数据增强投入产出比最高的一类正则化如果让我在有限时间里只优化一件事我会选数据增强而不是调正则化参数。原因是它扩充了有效的数据分布从根上缓解过拟合而其他手段都只是在约束模型。7.1 增强强度的判断依据增强太弱没效果太强会引入分布偏移导致训练困难。一个我常用的判断方法用增强后的数据训练一个小模型如果它在干净验证集上的准确率明显低于用弱增强训练的同类模型说明增强过头了。另一个信号是训练损失下降异常缓慢且长时间卡在高位。图像分类的常见组合是RandomResizedCropRandomHorizontalFlipColorJitter轻度RandomErasing。注意RandomHorizontalFlip在文字识别、方向敏感的任务里绝对不能用ColorJitter在医学影像、工业质检这类对色彩敏感的场景要慎用或去掉。这些细节做不到位增强就是在破坏标签信息。7.2 什么时候该用更强的增强当你的模型已经很大、数据相对不足弱增强已经无法阻止过拟合时就该上 RandAugment、AutoAugment 这一档。它们通过随机组合多种变换和随机强度在 ImageNet 级别的任务上能带来明显提升。代价是训练变慢而且需要更长的训练周期才能收敛——增强强了等效 epoch 数要相应增加。一个实操建议调增强的时候把num_workers和persistent_workersTrue一起设上否则每个 epoch 重新起进程的开销会吃掉大量时间让你误以为是增强本身太慢。loader DataLoader( dataset, batch_size64, shuffleTrue, num_workers8, pin_memoryTrue, persistent_workersTrue, drop_lastTrue, )drop_lastTrue在训练集上建议开因为 BN 遇到尺寸为 1 的最后一个 batch 会报错或者统计量异常而pin_memoryTrue配合non_blockingTrue的.to(device)能进一步减少数据传输开销。8. 组合策略不同场景下我会怎么配理论讲完落到具体场景才有意义。下面这张表是我在几类常见任务上的默认起点注意是起点不是终点实际还得看你自己的验证曲线调整。场景权重衰减Dropout归一化其他手段小数据集图像分类几千张1e-4 ~ 1e-30.3 ~ 0.5BatchNorm强数据增强、Mixup、Early Stopping大数据集图像分类1e-2 ~ 5e-20.1 ~ 0.2BatchNorm中等增强、Label Smoothing大模型微调NLP0.01 ~ 0.10.0 ~ 0.1LayerNorm小学习率、梯度裁剪、EMA目标检测/分割1e-4 ~ 1e-30.1GroupNorm 或 SyncBN多尺度训练、EMA表格数据/小样本1e-3 ~ 1e-20.1 ~ 0.3LayerNorm特征选择、交叉验证集成生成模型视情况0.0 ~ 0.2谱归一化梯度惩罚、EMA 生成器大模型微调那一栏的权重衰减看起来很大但注意它作用在前缀参数上配合很小的学习率实际效果是防止预训练权重被少量数据带偏。这个数值在 LoRA 这类参数高效微调里尤其常见因为可训练参数很少衰减过小几乎不起作用。还有一个组合上的经验不要同时把 Dropout、权重衰减、强数据增强都拉到高档位。三者叠加时模型的拟合能力被三重削弱很容易陷入欠拟合而欠拟合的表现和过拟合在曲线上一开始很像你会误判方向继续加码。正确的做法是一个一个加每次观察验证集是否真的改善。另外补充一个容易被忽略的点正则化强度的最优值会随训练长度变化。短训练适合弱正则化长训练适合强正则化。你从别人那里抄来的weight_decay5e-2很可能是在 300 epoch 的设置下调出来的你只训练 30 epoch 就用这个值模型大概率学不动。9. 排查实录正则化相关的典型症状与处理下面这些是我在实际调模型时反复遇到的按症状整理成速查表遇到问题可以直接对号入座。症状可能原因排查手段处理方式训练损失降不下去验证损失也高正则化过强或欠拟合关掉所有正则化重跑降低 weight_decay、减小 Dropout 的 p训练损失正常验证损失持续上升典型过拟合看两者分叉点加数据增强、加权重衰减、开 Early Stopping训练指标很好推理指标明显下降忘了 model.eval()检查推理脚本推理前必须 model.eval()每次推理结果都不一样Dropout/BN 未切 eval连续跑两次对比同上并检查 F.dropout 的 training 参数BN 层报错或统计量异常batch 内样本数为 1打印 batch 形状开 drop_lastTrue或换 GroupNorm多卡训练效果比单卡差BN 未同步检查是否用了 SyncBNconvert_sync_batchnorm 后包 DDP训练前几 epoch 就发散梯度爆炸打印 grad_norm加梯度裁剪检查学习率加了 Label Smoothing 后准确率下降类别不平衡或蒸馏场景关掉对比只在不平衡程度低的分类任务用SWA 后效果反而变差没更新 BN 统计量检查是否调 update_bn补上 update_bn 再评估微调时性能不及从头训练weight_decay 过小对比不同衰减值大模型微调适度加大衰减表格之外再补三个不在表里的经验。第一验证集本身也会被过拟合。如果你反复根据验证集调正则化参数调了几十次之后验证集上的表现就不再可信了。稳妥做法是留一个测试集完全不参与调参或者用交叉验证来估计真实性能。我见过不少人报告的成绩比实际部署时高出好几个点原因就在这里。第二正则化的效果和随机种子强相关。在小数据集上p0.3和p0.5的差异可能被种子带来的波动完全淹没。判断某个改动是否真的有效至少跑三个种子取平均否则你调的可能只是噪声。第三日志要记全。每次实验至少记录使用的正则化组合及参数、训练轮数、最终验证指标、最佳 epoch。不记录的话一周之后你完全想不起来哪个配置对应哪个结果前面所有的调参都白做。我习惯在训练脚本里把超参直接 dump 成 json 存到输出目录配合 TensorBoard 一起看回溯起来非常方便。最后分享一个我最近比较喜欢的做法把正则化配置做成可组合的开关用配置文件控制每一项的开关和强度训练时通过命令行覆盖。这样跑消融实验的时候只需要改一行参数不用动代码配合上面说的种子重复能很快摸清每个手段在你这个任务上的真实贡献。这比凭感觉堆参数要靠谱得多也是我踩了无数坑之后固定下来的工作流。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑