深度学习实战:VGG11在CIFAR10数据集上的优化与调参

张开发
2026/6/8 23:18:40 15 分钟阅读
深度学习实战:VGG11在CIFAR10数据集上的优化与调参
1. VGG11与CIFAR10的适配改造第一次用VGG11跑CIFAR10时准确率卡在70%死活上不去后来发现原生的VGG11结构根本不适合这个小尺寸数据集。CIFAR10的32x32像素图像经过5次池化后特征图会缩小到1x1这导致大量空间信息丢失。我的解决方案是砍掉最后一个池化层同时调整全连接层结构class VGG11_CIFAR(nn.Module): def __init__(self): super().__init__() # 修改后的卷积部分 self.features nn.Sequential( # Block1-4保持原样 nn.Conv2d(512, 512, kernel_size3, padding1), nn.BatchNorm2d(512), nn.ReLU(inplaceTrue), # 删除最后一个池化层 ) # 调整后的全连接层 self.classifier nn.Sequential( nn.Linear(512*2*2, 1024), # 特征图尺寸变为2x2 nn.ReLU(inplaceTrue), nn.Dropout(0.5), nn.Linear(1024, 512), nn.ReLU(inplaceTrue), nn.Dropout(0.3), # 降低dropout比例 nn.Linear(512, 10) )实测发现这个改动能让准确率直接提升5-8个百分点。另一个容易忽略的细节是批归一化层的参数初始化很多教程里直接用默认初始化但在小数据集上特别敏感。我习惯手动设置更小的初始缩放系数for m in self.modules(): if isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight, 0.1) # 默认是1.0 nn.init.constant_(m.bias, 0)2. 学习率动态调整实战固定学习率训练VGG11简直就是灾难特别是在CIFAR10这种包含大量相似类别如猫/狗/鹿的数据集上。我常用的三段式学习率策略是这样的预热阶段前5个epoch线性增长学习率从1e-6到1e-4主训练阶段余弦退火衰减周期设为20个epoch微调阶段当验证集准确率连续3轮不提升时学习率降为1/10PyTorch实现代码比想象中简单from torch.optim.lr_scheduler import CosineAnnealingLR, SequentialLR optimizer torch.optim.SGD(model.parameters(), lr1e-4, momentum0.9) warmup_scheduler LambdaLR(optimizer, lr_lambdalambda e: min(1., e/5)) cosine_scheduler CosineAnnealingLR(optimizer, T_max20) scheduler SequentialLR( optimizer, schedulers[warmup_scheduler, cosine_scheduler], milestones[5] )这个组合策略在我的实验中比单纯使用StepLR提升了约3%的最终准确率。有个坑要注意当使用学习率预热时如果同时启用了梯度裁剪gradient clipping预热阶段要把阈值设大些否则会抑制初期学习。3. 数据增强的进阶技巧torchvision的常规数据增强对CIFAR10远远不够。经过多次实验我发现这两个组合效果最好train_transform transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.RandomApply([ transforms.ColorJitter(0.4, 0.4, 0.4, 0.1) ], p0.8), transforms.RandomGrayscale(p0.2), transforms.RandomApply([ transforms.GaussianBlur(kernel_size3) ], p0.5), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.247, 0.243, 0.261)), transforms.RandomErasing(p0.5, scale(0.02, 0.1), ratio(0.3, 3.3)), ])关键点在于颜色扰动CIFAR10的类别对颜色敏感如青蛙的绿色所以ColorJitter的强度要适中高斯模糊kernel_size不要超过3否则会抹去关键特征随机擦除scale参数要小因为图像本身尺寸就小有个反直觉的发现在验证集上适度的加噪反而能提升鲁棒性。我通常在测试时添加高斯噪声test_transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize(...), Lambda(lambda x: x torch.randn_like(x) * 0.01) # 1%噪声 ])4. 模型正则化的组合拳单纯靠dropout防止过拟合效果有限我常用的正则化组合是深度监督在中间层添加辅助分类器权重约束对卷积核进行L2正则早停策略基于验证损失的动态判断辅助分类器的实现示例class VGG11_DS(nn.Module): def __init__(self): super().__init__() # 常规卷积层... self.aux_classifier1 nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Linear(256, 10) # 接在第三个block后 ) def forward(self, x): x3 self.conv_block3(x) aux_out self.aux_classifier1(x3) # 继续前向传播... return main_out, aux_out训练时要计算两个损失main_loss criterion(outputs[0], labels) aux_loss criterion(outputs[1], labels) total_loss main_loss 0.3 * aux_loss # 辅助损失权重设为0.3对卷积核的约束可以这样实现optimizer torch.optim.Adam([ {params: model.conv_parameters(), weight_decay: 1e-4}, {params: model.fc_parameters(), weight_decay: 5e-4} ], lr1e-3)早停策略我推荐动态阈值法当验证损失连续5轮高于历史最佳值的1.05倍时终止训练比固定epoch数更合理。5. 训练过程的可视化监控光看准确率曲线会错过很多关键信息我必看的五个监控指标梯度分布直方图用torchviz绘制各层梯度幅度激活值稀疏度统计ReLU后非零激活的比例权重更新比率参数更新量与原有权重的比值类间混淆矩阵特别关注相似类别的误判特征分布T-SNE每隔10个epoch可视化一次实现梯度监控的代码片段from torchviz import make_dot def plot_grad_flow(named_parameters): ave_grads [] layers [] for n, p in named_parameters: if p.grad is None: continue layers.append(n) ave_grads.append(p.grad.abs().mean()) plt.figure(figsize(10,5)) plt.bar(np.arange(len(ave_grads)), ave_grads, alpha0.5) plt.xticks(np.arange(len(ave_grads)), layers, rotation90) plt.show() # 在训练循环中调用 loss.backward() plot_grad_flow(model.named_parameters()) optimizer.step()当发现某层梯度突然消失或爆炸时可以立即调整初始化方式或加入梯度裁剪。我常用的梯度裁剪阈值是torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm2.0, norm_type2)6. 模型微调的终极策略当所有常规优化手段用尽后我通常会进行三轮微调冻结卷积层只训练全连接层学习率设为1e-3解冻浅层训练后三个卷积块全连接学习率1e-4全网络微调所有层用1e-5学习率训练每轮微调前用不同的数据增强策略# 第一阶段增强 weak_aug transforms.Compose([...]) # 最终阶段增强 strong_aug transforms.Compose([...])有个提升模型鲁棒性的小技巧在最后10个epoch时改用SWA随机权重平均from torch.optim.swa_utils import AveragedModel, SWALR swa_model AveragedModel(model) swa_scheduler SWALR(optimizer, swa_lr1e-6) # 在训练最后阶段调用 swa_model.update_parameters(model) swa_scheduler.step()最终保存模型时建议同时保存ONNX格式便于部署dummy_input torch.randn(1, 3, 32, 32).cuda() torch.onnx.export(model, dummy_input, vgg11_cifar10.onnx)

更多文章