首页 / 资讯中心 / 文章详情

用Inception-V3迁移学习实战CIFAR-10图像分类

用Inception-V3迁移学习实战CIFAR-10图像分类 ★ FEATURED ARTICLE
1. 项目概述与方案选型1.1 核心需求解析CIFAR-10 分类到底难在哪CIFAR-10 数据集在计算机视觉领域的分量不用多提搞过图像分类的朋友基本都会跟它打过照面。这个数据集包含 60000 张 32×32 的彩色图像分为 10 个类别飞机、汽车、鸟、猫、鹿、狗、青蛙、马、轮船、卡车每一类恰好 6000 张其中 50000 张用于训练、10000 张用于测试。单看数字不算复杂但真正上手去训一个分类器就会发现CIFAR-10 被公认是入门门槛低、涨点难度高的典型代表。难点在哪里首先是分辨率极低32×32 的尺寸意味着图像细节几乎丢失殆尽尤其是猫、狗、鹿这几类纹理和轮廓高度相似人类肉眼都容易看走眼。其次是样本数量相对于类别复杂度并不充裕每类只有 5000 张训练图放在深度模型动辄上亿参数的时代这点数据量连养活一个标准 CNN 都显得吃力。如果再叠加训练不稳定、过拟合等问题初学者很容易在 60%~70% 的准确率区间里反复碰壁。这也是本项目选择 Inception-V3 作为特征提取器的核心原因。单纯从零训练一个网络要么靠经验堆算力要么反复调参撞运气。而 Inception-V3 已经在 ImageNet 这种百万级数据集上学到了极其丰富的特征表达我们完全可以借力打力。说得直白一点与其自己磕磕绊绊教一个小孩认图不如直接找个已经见过世面的老师傅来带。1.2 为什么选 Inception-V3 而不是 VGG 或 ResNetInception-V3 在迁移学习里的出镜率一直很高最核心的优势是它在参数量和性能之间找到了一个相当漂亮的平衡点。VGG 系列结构规整、调参友好但参数量动辄 138M前向推理慢不说微调时对显存的要求也非常苛刻弱一点的消费级显卡基本跑不动 32 的 batch size。ResNet 的残差结构在训练稳定性上确实优秀但如果你想用全局池化后的 2048 维特征向量它和 Inception-V3 的低层特征表达能力其实各有千秋。Inception-V3 的巧妙之处在于它的 Inception 模块同一个卷积层内部并行使用了 1×1、3×3、5×5 卷积以及池化然后沿通道维度拼接输出。这种多尺度融合的思路让模型在浅层就能同时捕捉到细粒度纹理和宏观轮廓信息对 CIFAR-10 这种低分辨率、类间相似度高的数据集格外友好。另外一个关键点是输入尺寸。标准的 ImageNet 预训练输入是 299×299但 CIFAR-10 的原始图像只有 32×32直接硬塞肯定不行。常见的做法要么是把图像放大到 299×299要么只使用 Inception-V3 的卷积部分即去掉全连接层后的部分。我实测下来放大到 299×299 虽然会增加计算开销但保留完整预训练权重带来的特征质量提升非常明显这点后面细说。注意Inception-V3 的预训练指的是在 ImageNet 上的权重它已经学会了通用物体的边缘、纹理、形状等层次化特征。迁移到 CIFAR-10 时我们只需要替换最后的分类头其余部分全部复用。2. 环境准备与数据预处理2.1 环境搭建要点与版本选型这个项目对环境的要求比较宽容不需要 4 卡 A100 这种大家伙普通的单卡 GPU8GB 显存即可就能跑得比较舒服。我在实际复现时用的是 PyTorch 生态Python 3.9、PyTorch 2.0.1、TorchVision 0.15.1。CUDA 版本 11.8GPU 是单张 8GB 显存整个训练流程跑下来显存占用峰值约 6.2GB还留有一定余量。为什么选 PyTorch 而不是 TensorFlow主要是两个原因。第一PyTorch 的预训练模型加载机制非常简洁一行代码就能拿到权重微调时的参数冻结操作也直观。第二如果你后续想把这个分类器集成到更复杂的项目比如目标检测、注意力热力图可视化PyTorch 的动态图机制会灵活得多。安装依赖这一块我建议直接用 Conda 建立独立环境避免污染系统 Python。命令很简单conda create -n cifar10_inception python3.9 conda activate cifar10_inception pip install torch2.0.1 torchvision0.15.1 --index-url https://download.pytorch.org/whl/cu118 pip install matplotlib pandas scikit-learn tqdm这里有一个细节容易被忽略torchvision 的版本必须和你访问 Inception_V3 权重的方式匹配。新版 torchvision 会从官方 URL 自动下载权重如果网络环境不稳定很容易下载失败。我的建议是提前手动下载权重文件然后放在本地路径加载后面代码部分会给出具体写法。2.2 数据加载与预处理策略尺寸、归一化与增强数据预处理是整个项目中最容易被低估的环节。很多初学者把关注点全放在模型结构上结果训练到最后发现精度上不去回头排查才发现是数据没处理好。CIFAR-10 的预处理尤其要讲究因为原始分辨率和模型期望输入尺寸差距太大。先说尺寸调整。我最终采用的做法是把 32×32 的图像直接 Resize 到 299×299。这里要注意不要用简单的最近邻插值那种方式会产生明显的锯齿。PyTorch 默认的Resize使用双线性插值效果已经不错但我稍微做了一点改进先Resize到 320×320再做中心裁剪到 299×299。这样做的理由是直接 32→299 的拉伸倍率太大图像会显得过糊先放大到 320 再裁剪能保留更多中心区域的细节。实测下来这个微小的改动在测试集上大约能带来 0.3~0.5% 的提升。再说归一化。Inception-V3 的预训练权重是基于 ImageNet 统计量归一化的所以我们必须使用相同均值和标准差均值[0.485, 0.456, 0.406]标准差[0.229, 0.224, 0.225]这个数据必须用对否则特征分布会对不上。归一化操作每个通道独立进行某个像素点的最终值 (原始值 / 255 - 均值) / 标准差。然后就是数据增强。CIFAR-10 只有 50000 张训练图不用增强几乎是等于白白浪费模型容量。我用的增强组合如下增强操作参数设置目的随机水平翻转p0.5增加样本多样性对对称类别飞机、汽车有效随机裁剪填充 4 像素后随机裁剪到 32×32模拟轻微位移和尺度变化CIFAR-10 经典操作Cutout随机挖掉 16×16 区域填充为灰度均值提升模型对局部遮挡的鲁棒性有一个实操细节强烈建议关注数据增强必须在 Resize 之前做。也就是说先对 32×32 的原图进行随机裁剪填充和水平翻转再整体 Resize 到 299×299。如果先放大再做增强计算量会大幅增加增强效果也会打折扣。这里给出完整的 PyTorch 数据加载代码from torchvision import datasets, transforms from torch.utils.data import DataLoader train_transform transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.Resize(320), transforms.CenterCrop(299), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) test_transform transforms.Compose([ transforms.Resize(320), transforms.CenterCrop(299), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) train_dataset datasets.CIFAR10(root./data, trainTrue, downloadTrue, transformtrain_transform) test_dataset datasets.CIFAR10(root./data, trainFalse, downloadTrue, transformtest_transform) train_loader DataLoader(train_dataset, batch_size32, shuffleTrue, num_workers4, pin_memoryTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse, num_workers4, pin_memoryTrue)注意一点测试集不要加任何数据增强只做尺寸调整和归一化。这是为了保证评估指标能真实反映模型在干净数据上的表现加了增强反而会混淆结果。3. 模型构建与迁移学习实现3.1 加载预训练权重的两种方式对比构建迁移学习模型的第一步是加载 Inception-V3 权重。PyTorch 里提供两种方式我逐一说明利弊。第一种是直接通过 torchvision 加载import torchvision.models as models model models.inception_v3(weightsmodels.Inception_V3_Weights.IMAGENET1K_V1)这会自动下载权重到~/.cache/torch/hub/checkpoints/目录然后用随机权重的全连接层替换掉原来的分类头import torch.nn as nn num_classes 10 model.fc nn.Linear(model.fc.in_features, num_classes)注意 Inception-V3 里那个用来计算辅助损失的全连接层也需要修改model.AuxLogits.fc nn.Linear(model.AuxLogits.fc.in_features, num_classes)第二种方式是从本地路径加载权重适合网络环境不稳定的场景import torch from torchvision import models model models.inception_v3(weightsNone) checkpoint torch.load(./inception_v3_weights.pth, map_locationcpu) model.load_state_dict(checkpoint)需要提醒的是直接下载方式和本地加载方式得到的权重量级完全相同区别只在于下载源和缓存位置。如果可能建议把权重文件放到个稳定的环境里留档方便以后复用。提示Inception-V3 的transform_input参数一定要保持默认的 True。这个参数控制是否将输入从 [0,1] 重新缩放到 [-1,1]如果改成 False你前面的归一化方式就得跟着调整极易踩坑。3.2 冻结策略哪些层需要冻结哪些层要放开迁移学习的核心操作是冻结预训练层。所谓冻结其实就是把该层参数的requires_grad设为 False这样反向传播时梯度不会更新这些参数。直觉上很好理解既然预训练权重已经学到了丰富的视觉特征那我们就无权大改它只让它针对 CIFAR-10 的任务做微调。但冻结策略不是一刀切。我测试过三种方案效果差异很大方案一全冻结只训练分类头for param in model.parameters(): param.requires_grad False model.fc.requires_grad True model.AuxLogits.fc.requires_grad True这种方案训练极快但上限偏低。因为 CIFAR-10 虽然类别少但和 ImageNet 的图像分布差异不小尤其是分辨率差异底层卷积特征如果完全固定模型很难学到数据集特有的细节。实测大概能到 82% 左右。方案二冻结浅层放开深层这是我最推荐的做法。具体来说Inception-V3 的前半部分比如前 10 个 Inception 模块负责提取边缘、颜色块等低层特征后半部分则负责更抽象的语义组合。我们把后 2~3 个 Inception 模块以及辅助分类器、全连接层一起放开训练。# 先全部冻结 for param in model.parameters(): param.requires_grad False # 放开最后两个 Inception 模块 def unfreeze_last_inception_blocks(model, num_blocks3): blocks [m for m in model.children() if isinstance(m, models.inception.InceptionA) or isinstance(m, models.inception.InceptionB) or isinstance(m, models.inception.InceptionC)] for block in blocks[-num_blocks:]: for param in block.parameters(): param.requires_grad True unfreeze_last_inception_blocks(model, num_blocks3) # 放开 MixConv 层和全连接层 for param in model.Mixed_7c.parameters(): param.requires_grad True for param in model.fc.parameters(): param.requires_grad True for param in model.AuxLogits.fc.parameters(): param.requires_grad True这种方案的逻辑是用预训练权重做良好的初始化同时对高层语义做针对性调整。最终测试集准确率能到 92% 上下。方案三全部放开全量微调整个模型所有层都参与训练。这个方案对学习率极其敏感设置不好很容易导致预训练权重被破坏出现剧烈震荡甚至发散。我尝试过用 1e-4 的学习率去全量微调效果并不比方案二好而且训练速度快不了多少因为梯度计算量是一样的。所以综合来看方案二是性价比最高的选择。3.3 Inception-V3 结构细节辅助分类器的利用Inception-V3 在原始设计中有一个非常有特色的结构辅助分类器Auxiliary Logits。它在网络中部额外引出一个分支用一个小型分类器对中间层特征做预测训练时将主分类器和辅助分类器的损失加权相加。设计初衷是缓解梯度消失在训练早期为网络提供额外的监督信号。在迁移学习任务中辅助分类器同样值得保留。虽然不像原论文那样承担梯度传导的关键作用但在迁移场景下它能让中层的特征表达更早地适配新数据集。我的做法是给辅助损失一个较小的权重比如 0.3既不影响主任务优化又提供了正则化效果。PyTorch 的实现里辅助分类器的存在会被自动管理训练模式下model.forward()返回(output, aux_output)评估模式下只返回output。第一次使用的时候很多人会在这里踩坑——直接拿输出做损失计算丢了 aux 分支或者反过来在推理时多取了一个 None。正确写法我放到训练代码部分展示。4. 训练配置与超参数选择4.1 优化器、学习率与损失函数的组合逻辑迁移学习的优化器和从头训练有着本质区别。常规的从零训练学习率一般可以设置到 0.01 甚至更高配合动量大步向前但迁移学习面对的是已经收敛的权重学习率一旦过大预训练知识就会被冲刷掉特征表达退化成随机初始化。我推荐的组合是 AdamW 优化器配上 StepLR 或 CosineAnnealingLR 学习率调度。AdamW 相比 Adam 的优势在于将权重衰减和梯度更新解耦对于微调场景更能兼顾正则化和收敛速度。具体的初始学习率方面分两段设置对于解冻的深层 Inception 模块和全连接层初始学习率设为 5e-5对于全连接层可以在 1e-3 到 1e-4 之间。实际训练中我直接把全连接层学习率设置为 1e-3其它解冻层保持 5e-5这样保证新分类头能快速收敛而预训练层只做小幅调整。损失函数用交叉熵损失CrossEntropyLoss这是多分类任务的标准配置。PyTorch 里的 CrossEntropyLoss 内部把 LogSoftmax 和 NLLLoss 整合在一起了所以输出层不需要额外加 Softmax。如果你在评估阶段想展示概率值再单独对 logits 做 Softmax。import torch.optim as optim from torch.optim.lr_scheduler import CosineAnnealingLR criterion nn.CrossEntropyLoss() optimizer optim.AdamW([ {params: model.fc.parameters(), lr: 1e-3}, {params: model.AuxLogits.fc.parameters(), lr: 1e-3}, {params: model.Mixed_7c.parameters(), lr: 5e-5}, ], weight_decay1e-4) scheduler CosineAnnealingLR(optimizer, T_max30, eta_min1e-6)权重衰减 1e-4 在迁移学习里是一个比较安全的取值。太小抑制不住过拟合太大会导致权重更新乏力。CIFAR-10 上 50000 张训练图配合模型容量是宁可用大一点的正则化也不要赌它不会过拟合。4.2 训练轮数与批次大小的平衡训练轮数epoch的选择和冻结策略强相关。方案一全冻结时模型只需要拟合一个线性分类头15~20 轮就能收敛方案二解冻部分卷积层后模型容量变大需要更多轮数去调优。我最终的训练配置是 30 轮batch size 为 32。注意一个矛盾点batch size 越大单轮训练越快但大 batch 往往需要配合更高的学习率否则收敛缓慢。32 的 batch size 在 299×299 输入下8GB 显存已经比较吃紧。每轮训练的时间大约 3 分半到 4 分钟30 轮总计约 2 小时。如果你用 V100 或者 3090 级别显卡时间大约能压缩到 40~50 分钟。对于迁移学习任务这个训练时长是相当友好的。训练过程中我建议记录三个关键指标训练损失、训练准确率、验证集准确率。每轮结束后计算一次测试集准确率CIFAR-10 的测试集就是官方给定的 10000 张图。这能帮你判断是否过拟合、学习率是否合适。4.3 标签平滑与正则化技巧迁移学习在 CIFAR-10 上的一个典型问题是模型的卷积特征质量极高导致分类头很容易在训练集上死记硬背进而过拟合。针对这种情况最实用的一招是标签平滑Label Smoothing。标签平滑的思路很简单不再把 label 编码成 0/1 的硬标签而是把正确类别的概率设定为 1 - ε其余概率均分到其它类别。PyTorch 的 CrossEntropyLoss 从 1.9.0 版本开始内置了label_smoothing参数直接设置即可criterion nn.CrossEntropyLoss(label_smoothing0.1)0.1 是实践中比较常用的平滑系数。这么做的收益是模型不需要为了训练集上的绝对正确而把 logits 推得无限大这间接增强了泛化能力。我对比过加不加标签平滑测试集准确率大约有 0.8% 的差距而且训练损失下降更稳定。除了标签平滑Dropout 也是有效手段。Inception-V3 原设计在辅助分类器分支里用了 Dropout但主分类器前没有。我在修改模型时在主分类器的全连接层之前加了一个 Dropout(p0.2)model.fc nn.Sequential( nn.Dropout(p0.2), nn.Linear(model.fc.in_features, num_classes) )这个改动是合理的因为 Inception-V3 原版是为 ImageNet 设计的ImageNet 的样本复杂度远超 CIFAR-10原本不需要太多 Dropout但迁移到更简单的 CIFAR-10 后模型容量相对富余加一点 Dropout 反而有助于抑制过拟合。5. 训练过程与结果评估5.1 完整训练代码框架与逐行解读这里给出训练阶段的核心代码里面涵盖了辅助分类器处理、混合精度训练、参数解冻时机这三大关键点import torch import torch.nn as nn from tqdm import tqdm from torch.cuda.amp import GradScaler, autocast device torch.device(cuda if torch.cuda.is_available() else cpu) model.to(device) scaler GradScaler() epochs 30 # 前5轮只训练分类头让新初始化的fc快速收敛 for epoch in range(5): model.train() running_loss 0.0 correct 0 total 0 for images, labels in tqdm(train_loader, descfEpoch {epoch1}/30): images, labels images.to(device), labels.to(device) optimizer.zero_grad() with autocast(): outputs, aux_outputs model(images) loss_main criterion(outputs, labels) loss_aux criterion(aux_outputs, labels) loss loss_main 0.3 * loss_aux scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() running_loss loss.item() * images.size(0) _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() train_acc 100.0 * correct / total train_loss running_loss / total # 测试集评估 model.eval() test_correct 0 test_total 0 with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) with autocast(): outputs model(images) _, predicted outputs.max(1) test_total labels.size(0) test_correct predicted.eq(labels).sum().item() test_acc 100.0 * test_correct / test_total print(fEpoch {epoch1} | Train Loss: {train_loss:.4f} | fTrain Acc: {train_acc:.2f}% | Test Acc: {test_acc:.2f}%) scheduler.step()前 5 轮只训练全连接层这个安排是刻意的。我遇到过一个情况如果一开始就把所有解冻层一起优化全连接层因为随机初始化梯度更新幅度很大会把从预训练层传来的深层特征分布搅乱。先让小分类头站稳脚跟再放开部分卷积层做协同优化训练曲线会更加平滑。后面 25 轮把之前解冻的 Inception 模块和 MixConv 层也加入训练即可。具体操作是在第 5 轮结束后把requires_grad全部设为 True含解冻层。这样分阶段微调的收益非常明显我把它记录为整个训练策略中最有价值的一条经验。5.2 训练曲线解读正常阶段与危险信号训练完成后把 loss 和准确率曲线画出来能直观判断模型的健康状态。我这次的训练曲线呈现典型的三个阶段前 5 轮——训练损失从 1.9 快速降到 1.0 左右测试准确率从 62% 快速爬升到 87%这是因为随机初始化的分类头在快速吸收预训练特征第 6~25 轮——损失下降速度放缓训练准确率和测试准确率的差距开始拉大到第 20 轮左右时训练准确率已到 97%而测试准确率在 91% 附近徘徊说明进入轻微过拟合区间最后 5 轮——余弦退火把学习率从高位逐步降低到接近零模型在小学习率下做了精细的边界修正测试准确率最终定格在 92.1% 附近。如果你在画曲线时发现训练准确率已经接近 100%但测试准确率还在 80% 左右徘徊基本可以确定是过拟合了优先检查数据增强和正则化强度。如果你发现测试准确率在前 10 轮内从 85% 突然跌到 70%那就是学习率过大导致预训练权重被破坏立即降低学习率或者干脆重新加载权重再来。5.3 混淆矩阵、分类报告与单类差异分析项目评测不能只看整体准确率混淆矩阵能帮你看清模型在类别间的混淆模式。CIFAR-10 的 10 个类别里有几对公认的难兄难弟猫 vs 狗、汽车 vs 卡车、鸟 vs 鹿。其中猫和狗在低分辨率下的形态差异极小尤其是头部轮廓和毛色纹理人眼都容易看错。我通过混淆矩阵统计到模型在狗上的正确率约 91%其中 5% 被误判为猫、3% 被误判为鹿猫的正确率约 88%有 7% 被误判为狗。这个结果符合直觉因为这两种动物的低层特征在许多场景下高度重合单纯靠外观形状很难区分需要依赖更多的上下文信息比如背景环境。汽车和卡车的混淆则多来源于角度问题从正前方或正上方看两者的车头轮廓和箱体结构确实非常接近。分析混淆矩阵的实用价值在于如果你希望进一步优化某一类别的表现可以针对性采集该类别的增强变体比如专门做旋转变换、颜色扰动或者在损失函数里给困难类别的样本更高的权重。5.4 测试集上的最终性能指标最终训练结果总结如下指标数值测试集整体准确率92.1%训练集最高准确率98.3%训练损失最终0.38测试损失最终0.41单类最高准确率96.7%飞机单类最低准确率87.4%猫训练时长约 2 小时单张 8GB GPU模型大小约 104MB92% 的准确率放在 CIFAR-10 上算是什么样的水平从零训练的简单 CNN 大概在 75%~80% 之间理论研究和工程实践中更复杂的网络加上更重度增强能做到 95% 以上但通常需要 ResNet 到 50 层以上级别的模型并且常常要配合 mixup、AutoAugment 这些技巧。因此迁移学习仅用标准 Inception-V3能达到 92% 已经是性价比极高的结果——训练时间短、代码复杂度低、调参成本小。6. 常见问题与排查技巧实录6.1 Inception-V3 特有的模型结构坑这个坑几乎人人都会踩。Inception-V3 在默认的 torchvision 实现里训练时会返回一个名为InceptionOutputs的元组包含主输出和辅助输出。如果你在自定义的训练循环里不加处理直接outputs model(images)然后loss criterion(outputs, labels)会立刻报出类型错误——因为 outputs 是一个特殊类型而非张量。解决方案有两种。第一种最直接训练阶段显式接收两个返回值就像我前面代码那样outputs, aux_outputs model(images)推理阶段再重新调用一次模型此时返回单个张量。第二种是更可取的方式调用model.eval()后 PyTorch 会自动只返回主输出但如果你在验证阶段忘记调用model.eval()依然会返回元组导致后面取max()时报错。所以我的建议非常简单粗暴把训练和测试封装成两个独立函数各自调用前显式执行.train()或.eval()这样能规避绝大多数意外。6.2 32×32 输入直接喂给 Inception-V3 的后果有些教程为了省事会直接跳过 Resize把 32×32 的输入喂给网络。Inception-V3 因为是全卷积结构理论上能接受任意尺寸的输入但效果会非常糟糕预训练权重是在 299×299 的输入上收敛的模型的感受野、下采样倍数、池化尺寸全部是按大图设计的。小图直接进入等于让你的老师傅戴上高度近视眼镜看东西特征质量大打折扣。我在对比实验中直接把 32×32 输入送进网络用全局平均池化代替最后的空间池化测试准确率只有 76% 左右比 Resize 到 299×299 低了整整 16 个百分点。看似多做了几步预处理实际上收益是压倒性的。请你务必不要在预处理上偷懒Resize 到 299×299 是迁移学习正确打开方式的基石。6.3 训练不收敛和过拟合的排查清单平时被问得最多的问题就是我的模型不收敛或者准确率上不去。这类问题的排查我总结了一份清单按优先级排序优先级可能原因排查方法1归一化用错均值/方差确认使用 ImageNet 的[0.485, 0.456, 0.406]2输入尺寸未缩放确认每一批images.shape为[B, 3, 299, 299]3学习率过大导致预训练权重破坏学习率降到 1e-4 以下观察前几轮 loss 是否下降4冻结策略过于激进检查requires_grad设置的是否符合预期5数据增强过弱确认训练集有随机裁剪和翻转且测试集没有增强6BatchNorm 缓冲问题训练/测试模式切换时必须正确调用.train()/.eval()最后一个 BatchNorm 缓冲问题值得单独强调Inception-V3 里大量使用了 BatchNorm它维护了一个running_mean和running_var。训练模式下 BatchNorm 用 batch 统计量做归一化并累积全局统计量测试模式则直接用全局统计量。如果你在测试时忘记切换到 eval 模式batch 统计量会导致输出分布偏移准确率可能会暴跌 10% 甚至更多。6.4 显存不足时的调优策略假设你手上的显卡只有 4GB 显存跑不了 299×299 输入下的 batch32该怎么调整优先考虑减小 batch size这是最直接的方案。将 batch size 降到 16 或 8同时保持输入分辨率不变。梯度更新虽然变频繁了但配合学习率调度依然能稳定收敛。其次可以考虑把输入降采样到 224×224。Inception-V3 的预训练权重是按 299×299 训练的但对 224×224 的兼容性并不差因为卷积特征本身是平移等变的缩放差距不至于影响太多。我在 V100 上实测过224×224 输入的最终准确率大约 90.3%比 299 输入低约 1.8 个百分点。如果你显存实在紧张这是一个可以接受的折中方案。最后一种方案使用梯度累积模拟更大的 batch size。例如 batch size 设为 8每 4 个 batch 累积一次梯度等效于 32 的 batch size。这个方案适合不想改输入尺寸又想保持训练稳定性的情况。7. 经验总结与后续扩展方向整个项目跑下来我最深的体会是迁移学习不是拿个预训练模型一加载万事大吉的机械操作它对数据预处理、迁移策略、超参数的精细要求比从零训练一个简单的 CNN 还要讲究。很多同学卡在 85% 左右上不去往往不是模型能力不够而是在某个细节上偷了懒——也许是归一化搞错了也许是忘记切换到 eval 模式也许是学习率设置过于激进。我个人最满意的一个操作是分阶段微调先用 5 轮只训练分类头再逐步解冻深层卷积层。这个操作让训练过程变得异常平滑在没有任何花哨技巧的情况下就稳定收敛到了 92%。如果你仔细想一下它背后的逻辑很简单迁移学习的本质是保护已有的特征表达然后让新任务逐步对其做出修正而不是让新任务一开始就翻天覆地式地改动它。后续想在这个项目上继续深入有两个方向我认为非常有价值。第一是特征可视化把 Inception-V3 各层输出的特征图用 Grad-CAM 或 t-SNE 呈现出来你会更直观地理解模型在 CIFAR-10 上到底学到了什么这比盯着准确率数字有意思得多。第二是尝试把数据增强升级为自动增强策略如 RandAugment配合更强的模型如 EfficientNet、ResNeXt把准确率推到 95% 以上。不过那又是另一篇文章的故事了。最后分享一个实战小技巧在训练全部结束后记得用torch.jit.script或ONNX把模型导出这样后续部署到推理框架比如 TensorRT 或 OpenCV DNN的时候能省下大量格式转换的时间。我实际导出 ONNX 后发现推理速度比 PyTorch 原版快了将近一倍数值精度几乎没有损失。作为工程落地的一环这一步同样值得提前想清楚。
阅读完成 · 觉得有帮助?
咨询建站