2026/9/30 7:55:33

深度学习调参指南中文版:学习率与优化器实战拆解

深度学习调参指南中文版:学习率与优化器实战拆解 简介深度学习调参指南中文版是一份面向工程师与研究人员的实战型技术文档适合具备机器学习基础、希望系统提升模型性能的读者。它针对当前调参过程依赖猜测、缺乏系统性资料的痛点从损失函数选择、优化器比较、超参数调整策略到正则化技术应用给出可落地的操作建议并覆盖工作流实施、监督与自监督学习等场景。资源包为单一PDF文件压缩包约3.09MB内容结构清晰按开始新项目、选择模型架构、选择优化器、Batch Size 设定、增量调整策略等章节展开兼顾理论框架与具体实验细节。目前已有902人学习下载读者可借此建立一套系统化的调优手册减少盲目试错理解如何高效调整深度学习模型以实现更佳性能与准确性并跟随社区讨论持续更新认知。1. 深度学习调参指南中文版从学习率到优化器的实战拆解训练一个 CNN 做恶意软件识别准确率卡在 82% 死活上不去换了个学习率策略直接跳到 91%——这种事我经历过不止一次。深度学习调参指南中文版这个标题说的就是把这套“玄学”变成可复现流程的事。它解决的核心问题是模型结构定了、数据洗好了超参数怎么设才能让模型性能真正跑满。适合谁看刚配完 PyTorch 环境、跑通第一个深度学习项目但指标不达预期的人以及做过几个项目、但对优化器和学习率调度只停留在“抄配置”阶段的熟手。这篇不堆公式按“先立住理论、再动手复现、最后避坑”的顺序把学习率、优化器、批大小、正则化这几个最影响结果的参数讲透。2. 超参数到底在调什么先分清哪些参数值得花时间2.1 参数和超参数的区别以及调参的优先级排序深度学习里的 parameter 和 hyperparameter 是两回事。模型权重是 parameter由训练自动学出来学习率、批大小、优化器类型、正则化系数这些是 hyperparameter得你手动设。调参调的是后者。但超参数不是平等的。按对最终模型性能的影响程度我一般这样排优先级优先级超参数影响程度调参成本1学习率及调度策略极高低2优化器类型高低3批大小中高中4正则化Dropout/Weight Decay中低5网络深度/宽度高但属结构搜索高6数据增强强度中中学习率排第一不是随便说的。同一个 ResNet 结构学习率从 0.1 改成 0.001最终精度可能差 5 到 10 个百分点。而优化器从 SGD 换成 Adam收敛速度差异明显但最终精度不一定差很多——关键看你怎么配学习率。所以调参的正确顺序是先定优化器和学习率的组合再调批大小最后微调正则化。反过来先调 Dropout 再调学习率基本是浪费时间。2.2 学习率最重要的一个参数没有之一学习率决定了每次权重更新的步长。太大loss 震荡不收敛太小收敛慢还容易陷局部最优。常见做法是先用一个较大的学习率跑几百步看 loss 曲线再决定量级。我一般用学习率范围测试LR Range Test来定初始值。做法很简单从 1e-7 开始每步乘以一个固定倍数比如 1.05跑几百步记录 loss 随学习率的变化。loss 下降最快那段对应的学习率就是比较合适的初始值。# LR Range Test 简化实现 import torch import torch.nn as nn import matplotlib.pyplot as plt def lr_range_test(model, optimizer, criterion, data_loader, start_lr1e-7, end_lr1.0, num_iter200): lr_factor (end_lr / start_lr) ** (1 / num_iter) lrs, losses [], [] best_loss float(inf) for i, (inputs, targets) in enumerate(data_loader): if i num_iter: break # 手动设置当前学习率 for param_group in optimizer.param_groups: param_group[lr] start_lr * (lr_factor ** i) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, targets) loss.backward() optimizer.step() lrs.append(optimizer.param_groups[0][lr]) losses.append(loss.item()) # 平滑处理避免噪声干扰 if len(losses) 5: smoothed sum(losses[-5:]) / 5 if smoothed best_loss: best_loss smoothed elif smoothed best_loss * 4: break # loss 爆炸提前停止 plt.plot(lrs, losses) plt.xscale(log) plt.xlabel(Learning Rate) plt.ylabel(Loss) plt.show() return lrs, losses这段代码的逻辑是逐步增大学习率观察 loss 变化。参数说明——start_lr设 1e-7 足够小保证初始不会震荡end_lr设 1.0 覆盖常见范围num_iter设 200 步在大多数数据集上够用。跑出来的图loss 最低点左侧一个数量级的位置就是推荐的初始学习率。比如最低点在 0.01那初始值设 0.001 比较稳。定完初始值还得配调度策略。常见三种StepLR 每 N 个 epoch 降一半适合传统 CNN 训练CosineAnnealingLR 按余弦曲线降到接近零适合训练周期固定的场景ReduceLROnPlateau 验证集 loss 不降就降学习率最省心但需要多跑几个 epoch 才触发。我一般先用 CosineAnnealingLR因为它在图像分类任务上表现稳定不用手动设 milestone。2.3 优化器选型SGD 还是 Adam看任务不看热度优化器的选择经常被过度讨论。实际用下来结论比较明确图像分类、目标检测这类 CV 任务SGD Momentum 配合学习率调度最终精度通常比 Adam 高 0.5 到 2 个百分点但需要更仔细地调学习率和训练轮数。NLP、Transformer 类模型Adam 或 AdamW 几乎是默认选择因为稀疏梯度和自适应学习率对 embedding 层更友好。小数据集或快速原型验证Adam 收敛快省时间。# 三种优化器的典型配置 import torch.optim as optim # SGD MomentumCV 任务首选需要配学习率调度 optimizer_sgd optim.SGD( model.parameters(), lr0.1, # 初始学习率配合 CosineAnnealing 从 0.1 降到 0 momentum0.9, # 动量0.9 是绝大多数情况的安全值 weight_decay1e-4 # L2 正则防止过拟合 ) # AdamNLP 和快速验证首选 optimizer_adam optim.Adam( model.parameters(), lr1e-3, # Adam 的默认学习率通常不需要大改 betas(0.9, 0.999), # 一阶和二阶矩估计的衰减率默认值很少需要动 eps1e-8 # 数值稳定项默认即可 ) # AdamWAdam 的正确 weight decay 版本Transformer 训练标配 optimizer_adamw optim.AdamW( model.parameters(), lr1e-4, weight_decay0.01 # AdamW 的 weight decay 和 Adam 的 L2 不一样值可以大一些 )参数说明SGD 的momentum0.9基本不用调weight_decay在 1e-4 到 1e-2 之间试。Adam 的lr1e-3是默认值如果 loss 震荡就降到 1e-4。AdamW 的weight_decay0.01是 BERT 训练里的常见配置比 Adam 的 L2 更合理因为它是解耦的。一个容易翻车的点Adam 的 weight decay 实现和 SGD 的 L2 正则不等价。早期 PyTorch 的 Adam 把 weight decay 直接加在梯度上和 L2 正则混在一起导致正则效果被自适应学习率缩放。AdamW 修正了这个问题。所以如果你用 Adam 且需要正则化优先换 AdamW。3. 动手复现一个图像分类任务的完整调参流程3.1 基线配置先跑通再优化拿 CIFAR-10 做例子用 ResNet-18。基线配置不追求最优只求能稳定跑通、有个合理的起点。# 基线训练配置 import torch import torch.nn as nn import torchvision.models as models import torchvision.transforms as transforms from torch.utils.data import DataLoader import torchvision # 数据增强基线只用随机裁剪和翻转 transform_train transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ]) transform_test transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ]) train_set torchvision.datasets.CIFAR10(root./data, trainTrue, downloadTrue, transformtransform_train) test_set torchvision.datasets.CIFAR10(root./data, trainFalse, downloadTrue, transformtransform_test) train_loader DataLoader(train_set, batch_size128, shuffleTrue, num_workers4) test_loader DataLoader(test_set, batch_size128, shuffleFalse, num_workers4) # 模型 model models.resnet18(num_classes10) device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) # 损失函数和优化器 criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lr0.1, momentum0.9, weight_decay5e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max200)基线配置的关键参数batch_size128在单卡 8G 显存下比较稳lr0.1是 SGD 在 CIFAR-10 上的经典起点weight_decay5e-4是 ResNet 论文里的配置T_max200对应 200 个 epoch 的余弦退火。跑完 200 个 epochCIFAR-10 上 ResNet-18 的基线精度大概在 93% 到 94% 之间。如果低于 92%说明配置有问题先检查数据增强和归一化参数。3.2 调参实验控制变量法逐个调基线跑通后按优先级逐个调。每次只改一个参数记录结果。实验一学习率扫描。固定其他参数把初始学习率从 0.01 试到 0.5。CIFAR-10 上 SGD 的合理范围是 0.05 到 0.20.1 通常最好。如果 loss 在前几个 epoch 就炸到 nan说明学习率太大如果 loss 下降极慢说明太小。实验二批大小。从 64 试到 512。批大小影响梯度估计的噪声和 BN 层的统计量。一般规律是批大小翻倍学习率也翻倍线性缩放规则。但批大小太大比如 1024会降低泛化需要配合 warmup。# 带 warmup 的学习率调度适合大批量训练 from torch.optim.lr_scheduler import LambdaLR def warmup_cosine(optimizer, warmup_epochs5, total_epochs200): def lr_lambda(epoch): if epoch warmup_epochs: return epoch / warmup_epochs # 线性 warmup progress (epoch - warmup_epochs) / (total_epochs - warmup_epochs) return 0.5 * (1 math.cos(math.pi * progress)) # 余弦退火 return LambdaLR(optimizer, lr_lambda) # 使用batch_size512 时lr 可以设 0.4配合 5 个 epoch 的 warmup optimizer torch.optim.SGD(model.parameters(), lr0.4, momentum0.9, weight_decay5e-4) scheduler warmup_cosine(optimizer, warmup_epochs5, total_epochs200)warmup 的作用是让模型在训练初期用较小的学习率稳定下来避免大批量带来的梯度噪声导致发散。warmup_epochs5在 200 个 epoch 的训练里占 2.5%比较合适。实验三正则化。先调 weight_decay从 1e-4 到 1e-2。然后调 DropoutResNet 里通常只在最后全连接层前加概率 0.3 到 0.5。如果训练集精度远高于验证集比如差 5 个点以上说明过拟合加大正则化如果两者都低说明欠拟合减小正则化或增加模型容量。3.3 用 TensorBoard 看曲线别只看最终精度调参过程中最终精度只是一个数字。真正有用的是训练曲线。TensorBoard 是标配工具。from torch.utils.tensorboard import SummaryWriter writer SummaryWriter(runs/cifar10_resnet18_lr01) for epoch in range(200): model.train() train_loss 0 for inputs, targets in train_loader: inputs, targets inputs.to(device), targets.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, targets) loss.backward() optimizer.step() train_loss loss.item() # 记录训练 loss 和当前学习率 writer.add_scalar(Loss/train, train_loss / len(train_loader), epoch) writer.add_scalar(LR, optimizer.param_groups[0][lr], epoch) # 验证 model.eval() correct, total 0, 0 with torch.no_grad(): for inputs, targets in test_loader: inputs, targets inputs.to(device), targets.to(device) outputs model(inputs) _, predicted outputs.max(1) total targets.size(0) correct predicted.eq(targets).sum().item() writer.add_scalar(Accuracy/test, 100. * correct / total, epoch) scheduler.step() writer.close()看曲线时关注三个信号训练 loss 是否平滑下降震荡说明学习率大或批大小小验证精度是否在训练后期还在涨如果早早就平了说明模型容量不够或学习率降太快训练和验证曲线的差距差距大是过拟合差距小但都低是欠拟合。4. 避坑与排查调参路上最常见的五个翻车现场4.1 学习率设了但没生效现象改了lr参数但 loss 曲线和之前一模一样。原因最常见的是在优化器定义之后又覆盖了学习率或者用了 scheduler 但 scheduler 的初始值没对上。另一个隐蔽原因是 PyTorch 的param_groups里某些层被单独设了学习率比如微调时对 backbone 和 head 设了不同 lr。解决在训练循环里打印optimizer.param_groups[0][lr]确认每一步的实际学习率。如果用了 scheduler检查scheduler.step()的调用位置——PyTorch 1.1 之后应该在 optimizer.step() 之后调用顺序反了会导致第一个 epoch 的学习率被跳过。4.2 BatchNorm 和批大小的隐形耦合现象批大小从 128 降到 32精度掉了 3 个点但学习率已经按线性缩放规则调过了。原因BatchNorm 的统计量依赖批大小。批太小均值和方差的估计噪声大导致训练不稳定。这个问题在目标检测和分割任务里更明显因为每张图的实例数不同。解决批大小低于 32 时考虑用 GroupNorm 替代 BatchNorm或者用 SyncBN 跨卡同步统计量。另一个办法是冻结 BN 层的统计量设track_running_statsFalse但这会改变模型行为需要重新调学习率。4.3 Adam 的 weight decay 不是 L2 正则现象用 Adam 配weight_decay1e-4过拟合没缓解验证精度反而降了。原因Adam 的自适应学习率会把 weight decay 的效果缩放。具体来说梯度大的参数实际正则化强度被减小梯度小的参数被放大。这和 SGD 的 L2 正则行为不一致。解决换 AdamW它的 weight decay 是解耦的不经过自适应学习率缩放。如果必须用 Adam把 weight_decay 设小一点1e-5 到 1e-4或者改用 Dropout 做正则化。4.4 数据增强和批大小的交互现象加了 RandomErasing 和 Mixup 之后训练 loss 下降变慢最终精度没涨。原因强数据增强相当于增加了任务难度需要更长的训练周期和更大的批大小来稳定梯度。如果训练轮数没变模型还没收敛就停了。解决加数据增强的同时把训练 epoch 增加 20% 到 50%批大小适当加大。另外Mixup 和 CutMix 会改变 loss 的尺度学习率可以稍微调大一点。4.5 随机种子没固定实验结果不可复现现象同样的配置跑两次精度差 1 到 2 个点不知道是调参有效还是随机波动。原因PyTorch、NumPy、Python 的随机种子没固定数据加载的 shuffle 顺序、权重初始化、Dropout 掩码都不同。解决在训练脚本开头固定所有随机种子。import torch import numpy as np import random import os def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) # 下面两行会降低训练速度但保证卷积算法确定性 torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark False os.environ[PYTHONHASHSEED] str(seed) set_seed(42)注意cudnn.deterministicTrue会让训练慢 10% 到 20%但调参阶段值得开。确定最终配置后可以关掉它换速度。5. 进阶技巧用学习率预热和分层学习率榨出最后两个点调参调到后期基线配置已经跑满想再涨点精度就得用一些进阶手段。我常用的两个学习率预热warmup和分层学习率layer-wise LR。Warmup 前面提过但它的价值在训练初期。模型刚初始化时权重是随机的梯度方向噪声大。如果直接用大学习率前几百步可能把权重推到不好的区域。Warmup 用 5 到 10 个 epoch 从小学习率线性升到初始值让模型先稳定下来。在 Transformer 和大型 CNN 上warmup 通常能带来 0.5 到 1 个点的提升。分层学习率适合微调场景。比如用预训练的 ResNet 做新任务backbone 已经学到了通用特征不需要大改head 是随机初始化的需要大学习率快速收敛。做法是把参数分组# 分层学习率backbone 用小 lrhead 用大 lr backbone_params list(model.layer1.parameters()) list(model.layer2.parameters()) \ list(model.layer3.parameters()) list(model.layer4.parameters()) head_params list(model.fc.parameters()) optimizer torch.optim.SGD([ {params: backbone_params, lr: 0.001}, # backbone 小学习率 {params: head_params, lr: 0.01} # head 大学习率 ], momentum0.9, weight_decay5e-4) # 注意scheduler 会同时缩放两组的学习率比例保持不变 scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max100)参数说明backbone 的 lr 设 head 的十分之一左右。如果预训练模型来自不同领域比如 ImageNet 预训练用到医学图像backbone 的 lr 可以再小一点甚至先冻结几个 epoch 再解冻。验证分层学习率是否有效看两个信号训练初期 head 的梯度范数是否远大于 backbone说明 head 在快速学习验证精度是否比统一学习率时更高。如果没差别说明预训练特征和目标任务差距不大不需要分层。最后一个技巧用验证集上的精度曲线做早停。不要跑满所有 epoch验证精度连续 10 个 epoch 不涨就停。这能省 20% 到 30% 的训练时间而且避免过拟合。我一般把patience设成总 epoch 数的 10%比如 200 个 epoch 就设 20。这些技巧单独用提升有限但组合起来——warmup 分层学习率 早停——在微调任务上通常能比基线高 1.5 到 2.5 个点。调参没有银弹但把每个环节的细节做对累积起来就是差距。希望帮到你。本文还有配套的精品资源点击获取