资讯动态

PyTorch实战:CNN图像分类从原理到训练优化全指南

发布时间:2026/10/17 2:46:00 来源:尧图企业网站定制
开篇先聊个事。不少同学在PyTorch里写完线性回归、搓完多层感知机之后会卡在卷积神经网络这一关。原因倒不是代码多难写而是“为什么要用卷积”这个动机没打通。我在带团队做图像分类项目时也见过写了几个全连接层硬塞图片的代码——准确率惨不忍睹不说参数量还差点把显存撑爆。所以这篇入门第十一篇我打算把CNN的来龙去脉、核心组件、PyTorch实现和训练技巧完整串一遍重点放在“每一步选择背后的理由”上。这篇适合两类人看一类是把PyTorch基础语法过完了、想用CNN做图像任务的初学者另一类是已经在跑现成模型、但遇到准确率上不去、显存不够、维度报错时不知道怎么排查的人。我会用一个小型图像分类任务作为贯穿案例把卷积、池化、全连接、训练循环、过拟合应对全部过一遍。读完你不仅能写出自己的CNN还能理解改哪一层会影响什么。1. 为什么全连接层处理图像时会翻车——从一次实战失败的教训说起1.1 图像数据的特性平移不变性与局部相关性先想一个问题一张224x224的RGB图片如果把它展开成一维向量送给全连接网络会发生什么这个向量长度是224x224x3150528。假设第一层隐藏单元是1024个那这一层的权重数量就是1.5亿左右。按32位浮点算光这一层的参数就要占用约600MB内存训练起来别说反向传播前向计算都够呛。参数爆炸只是表面问题。更深层的矛盾在于全连接层假设输入特征之间是互相独立的每个输出单元都和所有输入单元相连这等于“一视同仁”地看待整张图的每个像素。可图像数据有个根本特性——局部相关性。一张图里的猫猫耳朵周围的像素和猫耳朵关系紧密离得很远的背景像素几乎不影响对耳朵的判断。全连接结构完全没利用这个先验强迫模型去学习“远端像素之间的无意义关系”这就是它处理图像低效的根本原因。还有平移不变性。一只猫不管是出现在图片左上角还是右下角它都还是一只猫。全连接网络对输入位置极其敏感猫稍微挪个位置输出可能就完全变了。要让全连接学到“猫在不同位置都是猫”这个规律需要海量不同位置的样本这在现实中不划算。1.2 全连接处理图片的三大硬伤把上面这些特性翻译成工程语言全连接处理图像有三个硬伤参数量过大每层的连接数是“输入维度x输出维度”图像像素动辄十万、百万级别第一层权重轻松过亿模型训练和部署成本都极高。忽视局部结构图像天然是二维的像素在空间上有远近关系全连接硬把它拉成一维空间结构信息直接丢失。不具备平移不变性同样的目标换个位置就要重新学习泛化能力差。我在一次实际项目中踩过这个坑。当时为了快速验证一个二分类想法没有引入卷积直接用了三层全连接输入是64x64的灰度图。训练集准确率倒是不低但验证集始终在75%左右徘徊。后来换了带卷积的模型验证集直接跳到94%以上。这件事给我的教训是模型结构本身往往比调参更关键符合数据规律的归纳偏置inductive bias是深度学习落地时最先要考虑的东西。1.3 从MLP到CNN的思路转变CNN解决这三个问题的思路其实很朴素既然图像有局部相关性那就让每个神经元只连接局部区域的一块像素既然同一类特征可能出现在图像的不同位置那就让不同位置共享同一组权重既然空间尺寸太大那就逐步缩减分辨率把“位置信息”浓缩成“特征存在与否”的信息。这三板斧对应到具体操作就是局部感受野、参数共享、池化下采样。理解了这三件事CNN的骨架就算立起来了。接下来逐个拆解它们在PyTorch里的实现。2. 卷积层的设计哲学与PyTorch关键参数2.1 卷积到底在算什么参数共享与局部感受野的具体含义很多人把卷积理解成“提取特征”这话对但不具体。我更喜欢用一个更直白的说法卷积核就是一组小模板它在整张图上滑动逐块匹配“这个区域像不像某种模式”。拿3x3卷积核举例它一次只看输入图上3x39个像素这9个像素组成的区域叫“感受野”。卷积核的9个权重就是这组模板。滑动到图片每个位置做一次点乘求和得到一个输出值。这个输出值越大说明当前区域和模板越匹配。把整张图都扫一遍就得到一张“响应图”也叫特征图。关键在“参数共享”。同一个卷积核在所有位置都用同一组权重无论这个模板在图的左上角匹配还是在右下角匹配用的都是那9个数。这直接解决了平移不变性问题——同一特征无论出现在哪里都能被同一个检测器捕捉到。同时参数数量也骤降一个3x3卷积核只有9个权重加1个偏置哪怕输出100个通道也才1000个参数和全连接动辄上亿形成鲜明对比。2.2 nn.Conv2d核心参数逐个拆解PyTorch里最常用的卷积层是nn.Conv2d。它的核心参数我逐个说透。nn.Conv2d( in_channels3, # 输入通道数RGB图就是3 out_channels16, # 输出通道数即卷积核个数 kernel_size3, # 卷积核尺寸3表示3x3 stride1, # 步长每次滑动几个像素 padding1, # 输入四周补几圈0 dilation1, # 空洞卷积暂时先不管 groups1, # 分组卷积默认1即可 biasTrue # 是否使用偏置 )in_channels每一层的输入通道数。第一层通常是图片的通道数RGB为3灰度图为1之后每一层的输入通道数等于上一层的输出通道数。out_channels这一层用了多少个卷积核。每个卷积核输出一张特征图所以输出通道数等于卷积核个数。我习惯把这一项理解为“这一层想提取多少种不同特征”。kernel_size卷积核的尺寸。3x3是当前最主流的选择两个3x3堆叠能获得5x5的感受野但参数量更少、非线性更强。stride卷积核每次滑动多远。stride1是标准滑动stride2输出尺寸减半相当于在卷积的同时做了下采样。padding输入四周补零的圈数。它最直接的作用是控制输出尺寸。如果不padding3x3卷积会把宽高各减2多层堆叠后特征图会迅速变小甚至比卷积核还小。输出尺寸的公式是[ \text{out_size} \lfloor \frac{\text{in_size} 2 \times \text{padding} - \text{kernel_size}}{\text{stride}} 1 \rfloor ]刚开始不用死记PyTorch运行时会自动算好但你要能理解这个公式的含义。padding1、stride1、kernel3时输出尺寸不变stride2时输出尺寸减半没有padding的3x3卷积会让尺寸每层减2。2.3 通道数到底怎么理解从RGB到特征映射通道数这个概念是新手最容易绕晕的地方。我拿RGB图举例输入通道是3表示一个像素由红色、绿色、蓝色三个分量构成。经过第一层16个卷积核后输出变成16个通道——但此时这16个通道不再是“红绿蓝”而是16种不同的局部特征响应比如有的通道对边缘敏感有的对纹理敏感。所以你千万别把后面的通道数继续理解成“几张颜色的图”。它们是特征映射feature map每个通道对应一种特征检测器在全图范围内的响应。从这层意义上看out_channels越大网络能提取的特征种类越丰富但参数量和计算量也随之上升。实际工程里通常从32、64开始网络越深通道数越大这是为了对应越来越抽象、越来越宏观的特征。3. 池化层的真实作用与常用网络配置3.1 为什么用池化空间降维与平移鲁棒性卷积层把“特征图”越铺越厚但空间尺寸如果一直不变后面的计算量会持续膨胀。池化层就是用来“压缩”空间尺寸的。最常用的nn.MaxPool2d(2)表示把一个2x2区域内的最大值取出来输出尺寸直接减半。池化有两个价值。第一是空间降维把特征图从大变小减少后续层的计算量第二是让特征检测更鲁棒——2x2区域内只要有一处响应强最大值就被保留下来相当于允许目标在小范围内轻微移动而不影响检测结果。这给模型又注入了一点平移不变性的先验。3.2 最大池化与平均池化的挑选逻辑PyTorch里常用的池化有两种MaxPool2d和AvgPool2d。最大池化取区域内最大值保留“是否存在这种特征”的信息平均池化取区域内均值保留“整体响应强度”的信息。图像分类的浅层和中间层我几乎只用最大池化因为分类任务关心的是“关键特征有没有出现”。最大池化对强特征更敏感而且能天然起到一定的特征选择作用。平均池化则更多用在网络的最后一层比如把特征图全局平均池化成一个向量再送进分类头这种设计在不少经典结构里都能看到目的是强行让每个通道的信息都贡献到最终输出而不是只保留最大值。在实战里很多现代网络干脆用stride2的卷积代替池化来做下采样。原因之一是池化没有可学习的参数表达能力有限但池化简单、稳定、无需训练。我的建议是入门阶段先用池化把流程跑通后续再尝试stride2卷积对比两种方式在你任务上的差异。3.3 一个可复现的CNN结构配置我们用一个小型图像分类任务来贯穿全文。假设输入是3通道、32x32的彩色图目标是10分类。下面这个结构是我在实际中验证过、适合入门复现的配置class SimpleCNN(nn.Module): def __init__(self, num_classes10): super().__init__() self.features nn.Sequential( # 输入: (3, 32, 32) - 输出: (16, 32, 32) nn.Conv2d(3, 16, kernel_size3, padding1), nn.BatchNorm2d(16), nn.ReLU(inplaceTrue), # 输出: (16, 16, 16) nn.MaxPool2d(2), # 输入: (16, 16, 16) - 输出: (32, 16, 16) nn.Conv2d(16, 32, kernel_size3, padding1), nn.BatchNorm2d(32), nn.ReLU(inplaceTrue), # 输出: (32, 8, 8) nn.MaxPool2d(2), # 输入: (32, 8, 8) - 输出: (64, 8, 8) nn.Conv2d(32, 64, kernel_size3, padding1), nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), # 输出: (64, 4, 4) nn.MaxPool2d(2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Dropout(0.5), nn.Linear(64 * 4 * 4, 128), nn.ReLU(inplaceTrue), nn.Linear(128, num_classes), ) def forward(self, x): x self.features(x) x self.classifier(x) return x结构设计遵循一个常见套路通道数递增16→32→64空间尺寸递减32→16→8→4。卷积负责提取和组合特征池化负责压缩尺寸最终把64个4x4的特征图展平成1024维向量经过全连接层输出10个类别的得分。这里有两个细节值得注意每个卷积层后面紧跟BatchNorm2d以及分类器里加了Dropout(0.5)它们的作用我在第5章会专门讲。4. 用PyTorch搭建CNN的完整代码与踩坑记录4.1 训练完整流程数据加载、损失函数、优化器与训练循环有了网络结构接下来就是把数据喂进去。我用的数据集是一个10类、每类若干张32x32彩色图的公开图像数据集结构包含训练集和测试集。先写数据加载import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader import torchvision.transforms as transforms transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ]) train_dataset YourDataset(root./data, trainTrue, transformtransform) test_dataset YourDataset(root./data, trainFalse, transformtransform) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse)ToTensor会把图片从0~255的整数像素值转成0~1的浮点张量并且把通道维度放到前面。Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))表示把每个通道标准化到-1~1范围。这一步不是可选的它会显著加速模型收敛。然后是训练循环。我给出一个比较规范的写法包含每轮训练后立刻评估验证集的逻辑device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleCNN(num_classes10).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr1e-3) for epoch in range(20): model.train() running_loss 0.0 correct 0 total 0 for inputs, labels in train_loader: inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * inputs.size(0) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() train_loss running_loss / total train_acc correct / total # 验证集评估 model.eval() val_loss 0.0 val_correct 0 val_total 0 with torch.no_grad(): for inputs, labels in test_loader: inputs, labels inputs.to(device), labels.to(device) outputs model(inputs) loss criterion(outputs, labels) val_loss loss.item() * inputs.size(0) _, predicted torch.max(outputs, 1) val_total labels.size(0) val_correct (predicted labels).sum().item() val_loss / val_total val_acc val_correct / val_total print(fEpoch {epoch1:02d}/{20} | fTrain Loss: {train_loss:.4f} | Train Acc: {train_acc:.4f} | fVal Loss: {val_loss:.4f} | Val Acc: {val_acc:.4f})这里有两个新手常犯的错误我直接指出来忘记optimizer.zero_grad()如果不清零梯度梯度会在多个batch上累加loss会震荡得很厉害甚至直接发散。model.train()和model.eval()切换BatchNorm和Dropout在训练和推理时的行为不一样。训练时BatchNorm用当前batch的均值和方差Dropout随机丢弃神经元推理时BatchNorm用训练累积的全局统计量Dropout不丢弃任何神经元。不切换模式验证结果会不稳定且不准。4.2 维度不匹配的排查思路从报错到解决的全过程我在第一版代码里犯过一个经典错误全连接层输入维度写错了。报错信息通常是RuntimeError: mat1 and mat2 shapes cannot be multiplied (64x4096 and 1024x128)这种报错出现时别急着猜老老实实算一遍。我的排查思路是这样的输入图片是(3, 32, 32)。第一个卷积padding1、kernel3、stride1尺寸不变输出(16, 32, 32)。第一次池化尺寸减半输出(16, 16, 16)。第二个卷积尺寸不变输出(32, 16, 16)。第二次池化输出(32, 8, 8)。第三个卷积尺寸不变输出(64, 8, 8)。第三次池化输出(64, 4, 4)。所以展平后向量长度是64*4*41024。报错里说mat1是64x4096说明我输入给全连接的特征图空间尺寸是8x8而不是4x4——根源是我漏写了第三个池化层或者多写了一个卷积层。遇到这类问题最快的办法是在model.forward里加几行print(x.shape)逐个阶段打印形状确定是在哪一步形状对不上。def forward(self, x): print(finput: {x.shape}) x self.features(x) print(fafter features: {x.shape}) x self.classifier(x) return x跑一个batch就能看到形状变化链路。这种“打印形状”的排查方法比盯着代码猜有效率得多。确认维度后把nn.Linear(1024, 128)改成和实际展平维度一致即可。4.3 为什么要用CrossEntropyLoss分类任务的损失函数选择分类任务的最终输出是一个10维向量每个位置表示对应类别的“得分”。nn.CrossEntropyLoss做的事情是先把得分向量做softmax转成概率分布然后计算预测概率和真实标签的交叉熵。这里要注意CrossEntropyLoss已经内置了softmax所以网络最后一层不需要再手动加nn.Softmax。如果你加了等于做了两次softmax训练时梯度传播会受影响而且数值表现不会更好。推理时如果要拿到概率值可以临时加一个nn.Softmax(dim1)但训练阶段一定不要放在网络里。选择交叉熵的理由也很直白它和softmax组合后梯度形式简洁而且对“预测概率置信度”非常敏感——分类错了梯度大分类对了但置信度低梯度依然存在会推动模型把正确类别的得分拉得更高。相比之下如果回归任务用MSE收敛速度会明显偏慢。5. 训练CNN必看的损失曲线与过拟合应对技巧5.1 从损失曲线判断网络是否在学习很多初学者训练完只看一两个数字比如“最终准确率92%”。但在训练过程中观察训练集和验证集的损失曲线走向能提前发现大量问题。训练损失和验证损失都下降验证准确率同步上升正常继续训练。训练损失下降验证损失先降后升验证准确率停滞过拟合需要增加正则化或数据增强。训练损失和验证损失都在小范围震荡不降学习率可能太大或者网络结构有问题。训练损失从第一步开始就剧烈跳动甚至出现NaN学习率过大的可能性极高先把学习率降1个数量级试试。训练损失缓慢下降但验证损失几乎不动可能是特征提取能力不足网络太浅也可能是数据分布有偏差。我在训练自己的网络时会每轮打印loss和acc同时用一个小工具记录训练过程中的曲线数据。哪怕只是用Excel手动记录每个epoch的数值画图也比只看最终结果强。曲线能告诉你“什么时候该停”而准确率只能告诉你“停在了哪里”。5.2 数据增强用最便宜的方式扩大训练集CNN参数量大如果不加约束很容易把训练集背下来却对没见过的数据毫无泛化能力。这是图像领域过拟合的典型表现。数据增强是缓解过拟合最直接的手段。它是在训练时对原始图片做随机变换相当于用一张图生成大量“看起来不完全一样但语义不变”的样本。PyTorch里的实现就是transformstransform_train transforms.Compose([ transforms.RandomCrop(32, padding4), # 随机裁剪 transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), # 颜色扰动 transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ])RandomCrop配合padding实现了一种“平移等价”的样本扩充RandomHorizontalFlip让模型不依赖物体的朝向ColorJitter增强对光照变化的鲁棒性。这些变换都只应该在训练时使用验证和测试时只做ToTensor和Normalize。数据增强不是随便加的。如果任务本身对镜像敏感比如识别左右方向的箭头RandomHorizontalFlip就不能开。我在一个缺陷检测项目里就因为这个操作把“左偏缺陷”和“右偏缺陷”搞混了验证集准确率反而下降。所以增强策略要依据数据语义来选不是越猛越好。5.3 BatchNorm和Dropout的使用心得我在网络里每个卷积层后面都加了BatchNorm2d。它的作用是把每个通道的响应在batch维度上做归一化让数据分布稳定在0附近。没有它的时候网络中间层的分布会随着训练漂移导致收敛慢、对学习率敏感。加了它之后学习率可以适当调大训练稳定性也明显提高。注意BatchNorm2d的输入参数是上一层的输出通道数写作nn.BatchNorm2d(16)就表示对16个通道分别做归一化。Dropout(0.5)则在分类器里。它的原理是训练时随机让一半神经元失活迫使网络不依赖某几个特定神经元相当于一种隐式的集成。PyTorch里的nn.Dropout有一个容易忽略的参数p表示的是“失活概率”。Dropout(0.5)就是每50%概率把神经元置零。测试时要记得model.eval()否则Dropout依然会随机失活输出就不确定了。关于BatchNorm和Dropout的位置我的经验是卷积层的特征图通常用BatchNorm全连接层之后用Dropout。BatchNorm在卷积层后面效果最明显但Dropout加到卷积层后面反而会破坏特征的空间连续性效果往往不如加在分类器里。5.4 学习率与优化器的实战选择入门阶段我推荐直接用Adam它自带自适应学习率对超参数没那么敏感。但Adam也不是完全不用调。我用lr1e-3起步比较稳妥如果训练损失下降太慢可以尝试lr3e-3如果训练损失震荡严重就降到3e-4。ShuffleNet、ResNet这类结构对学习率更敏感一些场景下SGD加Momentum反而表现更好但那通常是调参高手的选择入门先掌握Adam再说。还有一个很实用的小技巧学习率衰减。训练到后期恒定学习率会让损失在一个小范围内反复震荡很难完全收敛。一个简单的做法是在训练到一半epoch数时把学习率降10倍scheduler optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.1)每个epoch结束后调用scheduler.step()即可。这样前10轮用比较大的学习率快速下降后10轮用小学习率精细调整。在我的实际任务里这种做法通常能再涨1到2个百分点。6. 训练完整CNN时的三个典型坑与我的排查过程6.1 显存OOM的排查链路我第一个像样的CNN模型跑到第4个回合就报OOMOut of Memory了。这个坑几乎每位接触CNN的开发者都会踩到区别只是踩得多深。我当时的第一反应是“我的模型太大了”但仔细一想模型总共不到200万参数占不了那么多显存。排查链路是这样的先用torch.cuda.memory_summary()看显存分配情况发现占用最多的是激活值而非模型参数。激活值指每一层输出的中间特征图。我用了batch_size128、输入是3x224x224的大图中间层特征图数量可观占用的显存自然就大。单纯调小batch size是最直接的方案从128减到32OOM立刻消失。进一步优化如果输入尺寸固定可以把torch.backends.cudnn.benchmarkTrue开启让cuDNN自动选择最优卷积算法有时能省下不少显存和计算时间。帮助更彻底理解一点GPU显存里同时存三样东西——模型参数、优化器状态Adam会额外存一阶动量和二阶动量、每一层前向计算的中间结果用于反向传播。很多人只知道参数会占显存却忽略了中间结果才是最占空间的。所以模型“深度”对显存的影响比“宽度”更大。6.2 模型收敛缓慢且验证准确率停滞在60%一次训练中我遇到了比OOM更让人头疼的问题模型训练20轮训练损失下降很慢验证准确率也一直卡在60%左右。这显然不正常因为类似结构在其他数据集上验证过应该能到90%以上。我逐层排查后发现了两个问题第一没有对输入做Normalize标准化。原始像素值在0~255之间网络第一层卷积的初始化权重通常默认是“适配0~1输入”的量级大数值输入会导致激活值过大、梯度异常收敛自然慢。改成Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))之后问题立刻缓解。第二学习率太小。用lr1e-4时每轮loss只能下降一点点调大10倍后前几个batch的loss下降速度肉眼可见。很多时候模型“不工作”并不是结构错了而是训练超参数根本不在合理区间。6.3 验证准确率高于训练准确率的反常现象还有一种情况偶尔会出现验证集准确率比训练集还高。很多人看到这个现象就懵了以为代码写错了。其实这通常是Dropout和BatchNorm造成的。训练时Dropout会随机失活神经元BatchNorm也用当前batch的统计量导致训练过程本身就存在“不确定性”计算出的训练准确率往往偏低。而验证时Dropout关闭、BatchNorm用更平滑的全局统计量模型表现反而更好。另外如果测试集比训练集更“简单”比如训练集中有难分类的样本测试集大多是清晰样本也会出现这种现象。这不是bug但如果你想让训练准确率更接近验证准确率可以尝试在训练阶段结束后再计算一次训练集的准确率此时用model.eval()模式来算会更真实。7. 从跑通到调优进一步改进CNN的几个方向跑通一个基础CNN之后下一步不是急着换模型而是先做几个“低成本改进”实验。我按性价比排序说下思路。第一个改动是把普通卷积替换成深度可分离卷积。这个改动能显著降低参数量和计算量尤其适合在资源受限的设备上跑。PyTorch里可以用nn.Conv2d(..., groupsin_channels)再跟一个1x1卷积模拟实现不过入门阶段知道这个概念即可不必急着替换。第二个改动是引入残差连接。基础CNN层数一多梯度在深层传播时会越来越弱训练准确率反而下降。ResNet的残差结构x conv_block(x) x让梯度有一条“高速公路”直达浅层有效解决了深层网络训练难的问题。PyTorch里实现残差块其实只有几行代码关键在于forward里要把输入和输出相加因此设计网络时要注意通道数和空间尺寸在相加前保持一致。第三个改动是增加数据增强强度。如果当前模型已经接近过拟合训练准确率高、验证低使用更丰富的数据增强往往比改模型结构更有效。CutOut、MixUp这些增强策略在某些数据集上有奇效但需要先确认它们不违背任务的语义约束。第四个改动是换用预训练模型做迁移学习。如果你的任务和ImageNet这类大规模数据集有一定相似性直接用预训练的ResNet18、EfficientNet做特征提取或微调往往比从零训练自己的CNN收敛更快、效果更好。PyTorch里用torchvision.models加载预训练权重只需要一行代码import torchvision.models as models model models.resnet18(pretrainedTrue)但要注意预训练模型的最后一层全连接输出是1000类需要根据自己的分类数替换最后一层。同时如果数据量不大建议冻结前面所有层只训练新的分类器层等效果满意了再逐步解冻更多层做微调。8. 我的实操总结与一些建议玩CNN入门最重要的不是一次把原理全搞懂而是快速把“数据→模型→训练→评估”的闭环跑通。我在这个闭环上的经验是先跑一个极小的网络确保代码逻辑不出错再逐步增加层数和宽度。直接上大模型一旦出了问题你根本分不清是结构问题、数据问题还是超参问题。关于实验记录我强烈建议每次训练时把网络结构、数据集、学习率、batch size、数据增强配置、最终准确率完整记录下来。这件事听起来不起眼但在多轮迭代中作用巨大。我试过因为忘了记录某次实验的数据增强配置后来想复现当时的效果折腾了一整天。最后说一个关于学习顺序的心得CNN入门阶段与其追求调参技巧不如扎实理解卷积核、特征图、感受野这些基础概念。把nn.Conv2d的每个参数都亲手实验过一遍把输入输出维度亲手算过一遍把训练脚本从头到尾敲过一遍这些“笨功夫”积累起来后面学ResNet、目标检测、语义分割时会顺畅很多。这篇写到的代码和思路都是我踩过多遍坑之后沉淀下来的希望能让你的CNN入门之路少一些磕绊。

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

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

免费获取报价 →
↑