深度学习——超参数调优
第一部分:什么是超参数?为什么要调优?
一、参数 vs 超参数(Parameter vs Hyperparameter)
| 类型 | 定义 | 举例 | 是否通过训练自动学习? | 
|---|---|---|---|
| 参数(Parameter) | 是模型在训练过程中通过反向传播自动学习到的变量 | 权重(Weights)、偏置(Biases) | 是 | 
| 超参数(Hyperparameter) | 是在训练开始前由人工指定的控制模型结构或训练方式的变量 | 学习率、Batch Size、网络层数等 | 否 | 
一句话理解:
参数是模型自动“学”的,超参数是你手动“设”的。
二、为什么超参数调优如此重要?
超参数对训练过程的影响巨大。一个合理的超参数组合可能让模型快速收敛、泛化能力强;而不合适的设置可能导致:
-  模型不收敛 
-  过拟合或欠拟合 
-  训练速度极慢 
-  浪费大量资源与时间 
举例说明:
-  学习率太大 ➜ loss 震荡甚至发散 
-  学习率太小 ➜ loss 降得极慢,浪费时间 
-  网络太深 ➜ 训练困难,可能过拟合 
-  Batch Size 太小 ➜ 收敛不稳定;太大 ➜ 内存吃紧 
三、超参数调优的目标
调优的目标是找到一组最优超参数组合,使得模型在验证集上表现最优(即泛化能力强),而不是仅仅在训练集上表现好。
评估标准可能包括:
-  Accuracy(分类任务) 
-  mIoU、Dice(分割任务) 
-  Loss 曲线收敛速度 
-  参数/资源效率(比如在一定内存限制下的最好结果) 
四、超参数分类示意图
                    超参数│┌────────────────────┼─────────────────────┐│                    │                     │
模型结构超参数     优化器超参数       训练过程超参数
(例如层数、宽度) (例如学习率、动量) (例如BatchSize、Epoch)
五、为什么不能“一次性设好”?
-  模型复杂非线性:超参数之间有复杂的相互作用(例如:Batch Size 和学习率不是独立的)。 
-  任务差异:不同任务需要的超参数不同,例如图像分类 vs 图像分割。 
-  数据集变化:数据大小、类别不均衡性都会影响超参数效果。 
-  计算资源限制:硬件条件约束下无法盲目使用大模型、大Batch。 
六、小结
| 重点回顾 | 
|---|
| 参数是学出来的,超参数是设出来的 | 
| 不同超参数影响训练不同方面(结构、优化、效率) | 
| 超参数调优的目标是提升模型泛化性能 | 
| 一个合理的超参数组合可能带来质变的性能提升 | 
第二部分:常见的超参数类型
深度学习中涉及大量超参数,我们可以从三个维度来进行分类讲解:
| 分类维度 | 包含超参数 | 
|---|---|
| 模型结构相关 | 网络层数、每层宽度、激活函数等 | 
| 优化器相关 | 学习率、动量、权重衰减、调度器等 | 
| 训练过程相关 | Batch Size、Epoch、Dropout、正则化等 | 
一、模型结构相关超参数
网络层数(Depth)
-  定义:网络的“深度”,即堆叠了多少层神经网络。 
-  作用: -  更深的网络可以学习更复杂的特征。 
-  但过深会引发梯度消失/爆炸问题,训练困难。 
 
-  
-  经验: -  小数据集或简单任务,浅网络(如3~5层)更稳妥。 
-  大数据集(如ImageNet)上可以使用 ResNet50/101 等深层网络。 
 
-  
-  技巧: -  使用残差连接(ResNet)来训练更深的模型。 
-  在医学图像中,U-Net 结构的深度一般设置在4~5层左右。 
 
-  
每层宽度(Width)
-  定义:每一层神经元(或卷积通道)的数量。 
-  影响: -  控制模型的表达能力。 
-  太少容易欠拟合,太多可能过拟合且计算成本大。 
 
-  
-  经验调节: -  卷积神经网络中,宽度通常从浅层的 64 逐渐加倍(如 64→128→256)。 
-  分类问题中,全连接层常用如 512、1024 等神经元。 
 
-  
激活函数(Activation Function)
-  常见选项: -  ReLU:目前最常用,简单高效。
-  Leaky ReLU/PReLU:解决 ReLU 死亡问题。
-  Sigmoid:饱和慢,较少使用。
-  Tanh:比 Sigmoid 好些,但仍有梯度问题。
 
-  
-  实践建议: -  默认使用 ReLU 或 Leaky ReLU。 
-  对于输出层: -  分类:Softmax(多类),Sigmoid(二分类) 
-  分割:Sigmoid(二类掩膜),Softmax(多类掩膜) 
 
-  
 
-  
二、优化器相关超参数
学习率(Learning Rate)
-  训练中最关键的超参数,影响每一步参数更新的“幅度”。 
-  通常是调优的第一优先项。 
-  值设得不合适可能: -  太小 → 训练慢,陷入局部最优; 
-  太大 → Loss 震荡甚至发散。 
 
-  
-  典型值: -  SGD:1e-2 ~ 1e-3 
-  Adam:1e-3 ~ 1e-4 
 
-  
-  建议: -  使用学习率调度器(如StepLR、CosineAnnealing)。 
 
-  
动量(Momentum / β1 / β2)
-  用于平滑梯度更新,防止震荡。 
-  在不同优化器中的形式: -  SGD + Momentum:动量系数通常为 0.9。 
-  Adam:β1 一般为 0.9,β2 一般为 0.999。 
 
-  
-  不建议轻易更改,除非你很熟悉优化器行为。 
权重衰减(Weight Decay / L2 正则化)
-  作用:防止过拟合,引入对权重大小的惩罚项。 
-  常设值:1e-5 ~ 1e-4。 
-  默认建议加上,可以提高泛化能力。 
学习率调度器(LR Scheduler)
-  作用:在训练过程中动态调整学习率。 
-  常用策略: -  StepLR: 每隔N个epoch乘以一个γ
-  CosineAnnealingLR: 余弦退火
-  ReduceLROnPlateau: 根据验证集Loss下降情况自动调整
 
-  
-  建议: -  推荐使用 CosineAnnealingLR+ warmup 起步。
 
-  
三、训练过程相关超参数
Batch Size
-  决定每次梯度更新使用多少样本。 
-  大小影响: -  小 Batch(如 16、32):更噪声,收敛稳定性差但泛化强。 
-  大 Batch(如 128+):训练快但容易过拟合。 
 
-  
-  实践建议: -  根据显存能力尽量调大,但通常不超过 256。 
-  注意学习率要跟 Batch Size 协调调整(比如大 Batch → 可增大学习率)。 
 
-  
Epoch 数
-  控制整个数据集被“看过”的轮数。 
-  太小 → 欠拟合,太大 → 过拟合。 
-  通常结合 EarlyStopping 或验证集监控动态决定训练时长。 
Dropout 比例
-  防止过拟合的一种策略。 
-  建议值:0.3 ~ 0.5(不要超过0.7) 
-  不建议在卷积层用 Dropout(会破坏局部特征结构),常用于全连接层。 
正则化强度(L1 / L2)
-  L2(权重衰减)更常见,L1 可用于稀疏建模。 
-  多数情况下,设置一个小的 L2(如1e-4)即可。 
四、数据增强和预处理相关超参数
-  例如图像翻转概率、亮度变化范围、裁剪尺寸等。 
-  合适的数据增强不仅能提升泛化能力,还能防止过拟合。 
-  医学图像中,增强不能过强,要考虑语义一致性。 
小结
| 超参数类别 | 推荐调优顺序 | 
|---|---|
| 学习率 | 最重要,优先调 | 
| Batch Size | 次优先,影响显存 | 
| 网络结构 | 先简单→复杂,逐步加深 | 
| Dropout / 正则化 | 防过拟合常用手段 | 
| LR Scheduler | 搭配学习率共同调整 | 
第三部分:超参数调优方法论(策略、流程与工具)
一、手动调参的经典策略(适合初期模型开发)
原则:单变量控制 + 分阶段调优
不要一次改多个超参数。保持其他不变,逐个尝试,观察其对训练过程和验证集效果的影响。
推荐调优顺序:
| 步骤 | 优先级 | 超参数类型 | 说明 | 
|---|---|---|---|
| ① | 高 | 学习率 | 首先确定模型是否能正常收敛 | 
| ② | 高 | Batch Size | 尽可能大,但不爆显存为宜 | 
| ③ | 中 | 优化器选择 | 如 Adam、SGD+Momentum | 
| ④ | 中 | LR Scheduler | 控制收敛速度和效果 | 
| ⑤ | 低 | 网络结构 | 小到大试试(层数/通道数) | 
| ⑥ | 低 | Dropout、正则化 | 控制过拟合 | 
实战技巧举例:
-  判断学习率设得好不好: -  Loss 快速下降 → ✅ 
-  Loss 抖动大或直接发散 → 学习率太大 ❌ 
-  Loss 降得慢、像“爬坡”一样缓慢 → 学习率太小 ❌ 
 
-  
-  调整 Batch Size 后需要改学习率! -  经验公式:  
 
-  
二、网格搜索(Grid Search)与随机搜索(Random Search)
这两个是传统的自动化调参策略,适用于搜索空间不大的情况。
网格搜索(Grid Search)
-  穷举式搜索所有组合,例如: learning_rates = [0.01, 0.001, 0.0001] batch_sizes = [32, 64, 128] optimizers = [SGD, Adam]
-  总共要跑 3×3×2 = 18 次实验。 
-  优点:简单直接,能找出局部最优组合。 
-  缺点:计算资源消耗大,不适合维度高或范围广的超参数。 
随机搜索(Random Search)
-  在设定的搜索范围内随机采样组合,减少计算量。 
研究表明,在高维空间中,随机搜索通常比网格搜索更高效(Bergstra & Bengio, 2012)。
-  举例:随机采样 10 组学习率+Batch组合,而不是穷举全部组合。 
三、贝叶斯优化(Bayesian Optimization)
原理简介:
-  构建一个超参数 → 验证效果之间的“代理模型”,如高斯过程回归(Gaussian Process),预测某组合的效果。 
-  然后在代理模型中选择最可能带来提升的超参数组合继续试验。 
-  是一种智能搜索策略,相比随机或网格更节省时间与资源。 
工具推荐:
-  Optuna -  支持分布式、多目标优化、可视化。 
-  非常适合深度学习超参数调优。 
-  后面我可以手把手教你用它调 UNet 或 CNN。 
 
-  
四、学习率自动查找(Learning Rate Finder)
让模型自动找到最适合的学习率区间!
PyTorch 中的实现(如 fastai 的 lr_find()):
 
-  从一个极小学习率开始,指数增长; 
-  绘制 Learning Rate vs Loss 曲线; 
-  选取 loss 急剧下降前的最小值处作为起始学习率。 
五、一周期学习率策略(One Cycle Policy)
学习率不是越低越好,而是在训练初期升高、中期降低,能获得更好泛化效果。
-  论文:Super-Convergence(2018) by Leslie Smith 
-  支持: torch.optim.lr_scheduler.OneCycleLR
-  特别适合图像分类、分割等任务。 
六、逐步调优流程总结图(推荐收藏)
        ↓ 定义模型结构↓设置初始学习率、Batch Size↓↻ 调整学习率找收敛区间(LR Finder)↓↻ 调整 Batch Size + 学习率联动↓↻ 调整优化器参数(Momentum、Decay)↓↻ 调度器(Cosine / Step)选择↓↻ 网络结构细节优化(层数/通道数)↓↻ 加入 Dropout、正则化、EarlyStopping↓↻ 自动搜索工具(Optuna / Ray Tune)
小结:调参三大黄金建议
-  控制变量法永不过时: 每次只改一个,效果才可解释。 
-  调参顺序讲策略: 先优化能显著提升效果的(学习率、Batch)。 
-  自动化工具是加速器: 用得好能帮你节省大量资源。 
代码部分
学习率查找器(LR Finder)
# 先安装:pip install torch-lr-finder
from torch_lr_finder import LRFindermodel = SimpleCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=1e-7, momentum=0.9)lr_finder = LRFinder(model, optimizer, criterion, device=device)
lr_finder.range_test(train_loader, end_lr=1, num_iter=100)
lr_finder.plot()  # 画出Loss随LR变化曲线
lr_finder.reset() # 恢复模型参数
通过观察曲线,找到 loss 降得最快且开始发散前的那个学习率,作为训练起始学习率。
Batch Size与学习率联动调节
base_batch_size = 64
base_lr = 0.01new_batch_size = 128
new_lr = base_lr * (new_batch_size / base_batch_size)print(f"当 batch_size={new_batch_size} 时,建议学习率设置为 {new_lr}")
一周期学习率策略(One Cycle Policy)
from torch.optim.lr_scheduler import OneCycleLRoptimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = OneCycleLR(optimizer, max_lr=0.1, steps_per_epoch=len(train_loader), epochs=10)for epoch in range(10):model.train()for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()scheduler.step()  # 学习率自动调整if batch_idx % 100 == 0:print(f"Epoch {epoch} Batch {batch_idx} Loss: {loss.item():.4f} LR: {scheduler.get_last_lr()[0]:.6f}")
网格搜索(Grid Search)
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader# 简单CNN模型定义(同前)
class SimpleCNN(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Conv2d(3, 16, 3, padding=1)self.pool = nn.MaxPool2d(2)self.fc = nn.Linear(16*16*16, 10)def forward(self, x):x = self.pool(torch.relu(self.conv1(x)))x = x.view(x.size(0), -1)x = self.fc(x)return x# 数据加载
transform = transforms.Compose([transforms.ToTensor()])
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 网格搜索超参数空间
learning_rates = [0.01, 0.001]
momentums = [0.8, 0.9]
batch_sizes = [32, 64]best_loss = float('inf')
best_params = {}for lr in learning_rates:for momentum in momentums:for batch_size in batch_sizes:print(f"训练参数: LR={lr}, Momentum={momentum}, Batch Size={batch_size}")# 重新加载数据集以调整batch sizetrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)model = SimpleCNN().to(device)criterion = nn.CrossEntropyLoss()optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)model.train()total_loss = 0batches = 0for batch_idx, (data, target) in enumerate(train_loader):if batch_idx > 50:  # 为节约时间,只训练部分batchbreakdata, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()total_loss += loss.item()batches += 1avg_loss = total_loss / batchesprint(f"平均损失: {avg_loss:.4f}")if avg_loss < best_loss:best_loss = avg_lossbest_params = {'lr': lr, 'momentum': momentum, 'batch_size': batch_size}print(f"最佳参数组合: {best_params},最优平均损失: {best_loss:.4f}")
随机搜索(Random Search)
import randomlearning_rates = [0.01, 0.001, 0.0001, 0.005]
momentums = [0.7, 0.8, 0.9, 0.95]
batch_sizes = [32, 64, 128]num_trials = 10  # 随机尝试次数best_loss = float('inf')
best_params = {}for _ in range(num_trials):lr = random.choice(learning_rates)momentum = random.choice(momentums)batch_size = random.choice(batch_sizes)print(f"训练参数: LR={lr}, Momentum={momentum}, Batch Size={batch_size}")train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)model = SimpleCNN().to(device)criterion = nn.CrossEntropyLoss()optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)model.train()total_loss = 0batches = 0for batch_idx, (data, target) in enumerate(train_loader):if batch_idx > 50:breakdata, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()total_loss += loss.item()batches += 1avg_loss = total_loss / batchesprint(f"平均损失: {avg_loss:.4f}")if avg_loss < best_loss:best_loss = avg_lossbest_params = {'lr': lr, 'momentum': momentum, 'batch_size': batch_size}print(f"随机搜索最佳参数组合: {best_params},最优平均损失: {best_loss:.4f}")
贝叶斯优化(Bayesian Optimization)
import optunadef objective(trial):lr = trial.suggest_loguniform('lr', 1e-5, 1e-1)momentum = trial.suggest_uniform('momentum', 0.7, 0.99)batch_size = trial.suggest_categorical('batch_size', [32, 64, 128])train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)model = SimpleCNN().to(device)criterion = nn.CrossEntropyLoss()optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)model.train()total_loss = 0batches = 0for batch_idx, (data, target) in enumerate(train_loader):if batch_idx > 50:breakdata, target = data.to(device), target.to(device)optimizer.zero_grad()output = model(data)loss = criterion(output, target)loss.backward()optimizer.step()total_loss += loss.item()batches += 1avg_loss = total_loss / batchesreturn avg_lossstudy = optuna.create_study(direction='minimize')
study.optimize(objective, n_trials=20)print("贝叶斯优化最佳参数:", study.best_params)
print("最佳平均损失:", study.best_value)
| 方法 | 优缺点 | 代码复杂度 | 
|---|---|---|
| 网格搜索 | 简单、完全搜索;计算资源消耗大 | 最简单 | 
| 随机搜索 | 计算资源节省;随机覆盖;适合高维参数 | 简单 | 
| 贝叶斯优化 | 智能高效搜索;适合复杂空间 | 需要库支持,稍复杂 | 
关于贝叶斯优化我自己的理解:经过多次训练后,软件库中的方法 会记录不同参数的效果, 形成一个类似曲线的形式, 观察参数变化和模型性能之间的关系后 有选择的选择下一次测试的参数组合,最终选择出最优的参数组合。
