ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

卷积神经网络入门:从卷积概念到PyTorch实战

卷积神经网络入门:从卷积概念到PyTorch实战 1. 先别急着翻书“卷积”就是个“加权求和”我第一次见到“卷积”这两个字是在一本图像处理教材的角落里。当时那个公式写了一大串还有积分符号我盯着看了十分钟脑子里只剩下一个念头这东西我能学会吗后来我才明白我被这名字骗了。卷积的数学定义确实可以写得很难看但它在深度学习里干的事情朴素到一句话就能讲完卷积就是一个窗口在数据上滑动每滑到一个位置就把窗口里的数和窗口对应的权重相乘再相加得到一个新的数。你不需要会微积分不需要知道什么翻转不翻转。你只需要记住这个画面一个手电筒在图片上扫来扫去照亮一小块区域对这块区域做一次计算然后移动到下一块区域重复同样的计算。每扫完一整张图你就得到了一张新的“特征图”。就这么简单。为了让你彻底放下防备我举一个特别生活化的例子。假设你有一张黑白照片照片是黑白的话就是一堆0到255之间的数字组成的矩阵。现在你想让照片变模糊最土的办法是什么是把每个像素和它周围的像素取平均。你拿一个3×3的窗口里面每个位置都放1/9然后从图片左上角开始滑每到一个位置就把窗口覆盖的9个像素分别乘以1/9再加起来填到输出图的对应位置。这个过程做完整张图就糊了。恭喜你你已经会做一次完整的卷积了。那个3×3的窗口就是“卷积核”也叫“滤波器”里面的11/9就是权重滑动的间隔就是“步长”。所以在深度学习里卷积神经网络做的事本质上就是用一堆可以自动调整数值的滤波器和图片做乘加运算。传统图像处理里模糊、锐化、边缘检测全是人手工设计滤波器卷积神经网络把这个过程翻了过来——我不管你滤波器里填什么数我只告诉它最终任务是什么然后让它在大量数据里自己把那些数“学”出来。这就是它最迷人的地方也是“卷积”这个概念真正值得你花时间搞明白的原因。这篇内容我会从最基础的卷积概念讲起把图像卷积、卷积层、池化、步长、填充这些关键词全部拆开揉碎再带你用PyTorch从零写一个能跑的小网络最后聊几句现在常见的转置卷积、深度可分离卷积、膨胀卷积这些花式变种。适合刚接触深度学习、想搞懂CNN原理但被各种博客劝退的朋友。我保证不堆公式不改教科书腔调就按我当时弄明白的思路给你捋一遍。2. 卷积在图片上到底怎么“卷”的2.1 一个具体的3×3卷积核计算过程光说“滑动窗口”你可能还是有点虚。来我们把这个过程落到一个具体的数字例子上。假设输入是一张5×5的单通道灰度图像素值我给你随便编一下1 2 0 1 3 3 1 2 1 0 0 2 1 0 1 4 1 0 2 2 2 3 1 1 0再假设卷积核是3×3里面填的数是1 0 -1 1 0 -1 1 0 -1别小看这个核它长得很像经典的垂直边缘检测核。什么意思呢你只要看中间那一列是0左边全是1右边全是-1就能猜到如果某个位置的左边亮右边暗乘加出来的结果就会是个很大的正数说明这里存在一条从左到右的明暗变化边界也就是垂直边缘。现在我们把窗口对准图像左上角就是覆盖第一行到第三行、第一列到第三列这片区域。展开来算输入窗口 卷积核 1 2 0 1 0 -1 3 1 2 1 0 -1 0 2 1 1 0 -1对应位置相乘再相加1×1 2×0 0×(-1) 3×1 1×0 2×(-1) 0×1 2×0 1×(-1) 1 0 0 3 0 - 2 0 0 - 1 1所以输出特征图的左上角第一个值就是1。然后窗口向右移动一个像素步长为1接着算第二列到第四列的部分再算第三列到第五列一行滑完再回到最左边往下移一行继续滑。整个过程下来5×5的输入配3×3的核步长为1且不填充输出的尺寸是(5-31)3也就是3×3。这种计算方式有一个很大的好处它天然具备了“局部感知”和“权值共享”。每次只看一小块局部区域这是局部感知所有位置用的是同一个卷积核这是权值共享。因为权值是共享的所以一个卷积核的参数量就是核里面那些数跟图片多大没关系。这也是卷积网络比全连接网络轻量得多的根本原因。2.2 从图像到多个通道RGB图片怎么卷刚才的例子是单通道灰度图但现实中我们遇到的基本都是彩色图比如RGB三通道。多通道的卷积并没有变复杂多少只是把窗口从二维变成了一个“小立方体”卷积核不再是一个3×3的平面而是一个3×3×3的立方体其中3是输入通道数。官方的写法是K×K×C_in这里的K是核大小C_in是输入通道数。计算时窗口同时覆盖三个通道每个通道做各自的乘加最后把三个通道的结果加起来再加一个偏置项得到输出特征图的一个值。这个过程用PyTorch里一句nn.Conv2d(in_channels3, out_channels6, kernel_size3)就能定义出来。这里的out_channels6意味着你有6个不同的卷积核每个核都会扫遍全部3个输入通道最后得到6张不同的输出特征图。你可能要问为什么一次要弄出好几个输出通道我的理解是一个核只能提取一种特征比如垂直边缘但图片里有水平边缘、纹理、颜色块、形状轮廓特征太多了一个核根本不够用。深度网络的做法就是让每一层都准备几十上百个核各学各的特征然后交给下一层去组合。你完全可以把不同输出通道想象成“不同的视角”同一个画面有人看到的是线条有人看到的是色块有人看到的是纹理。卷积层做的就是把这些视角的结果全部堆叠起来供后续层使用。2.3 二维卷积、三维卷积和一维卷积到底指什么顺手把另一个容易混淆的概念也理清。我们天天说“图像卷积”其实是二维卷积因为输入是高和宽两个空间维度卷积核也在两个方向上滑动。如果你处理的是视频每一帧除了高和宽还有一个时间维度那就可以用三维卷积卷积核不仅在高宽方向滑动还在时间帧方向滑动一次卷多帧。这是做动作识别、视频理解任务的常用手段。如果输入是一段语音或者一维序列那就是一维卷积卷积核只在一个方向上滑动常用于文本分类、音频处理。理解这个的关键在于“几维”指的是数据布局的维度不是卷积本身的数学复杂度。二维卷积的核是矩阵三维卷积的核是立方体一维卷积的核是向量仅此而已。这些变体理解起来不难难的是一开始从二维图像入手时是不是真搞懂了那个滑动过程。3. 卷积神经网络里的完整组件卷积层、池化、激活、全连接3.1 卷积层的核心任务特征提取我们把前面讲的东西先固定下来。在一个典型的CN N里卷积层承担的是一个“编码”工作输入一张原始图片卷积层把它变成一堆特征图。这些特征图的数值本身没什么直观含义但它们代表了“在图像的哪些位置出现了和这个卷积核相似的模式”。一开始的卷积层学到的是边缘、颜色、角点这些底层特征。网络越深后面的卷积层会把前面的特征组合起来学到眼睛、轮子、花瓣这种高层语义特征。这种从low-level到high-level的抽象过程是卷积网络最厉害的地方——你不用手工设计任何特征只要把原始数据丢进去网络自己就知道该关注什么。在实操层面卷积层有两个关键超参数必须学会选一个是卷积核大小另一个是输出通道数。核大小决定感受野范围通道数决定这一层提取多少种特征。我见过很多人一上来就把通道数设得很大结果模型参数爆炸、训练极慢。比较稳妥的做法是浅层用小通道数比如16、32逐层翻倍到64、128避免一步到位。3.2 池化层给特征图“减负”池化层嘛说白了就是降采样。它做的事情也是滑动窗口但窗口里不乘权重了而是直接取最大值或者平均值。最常用的是最大池化Max Pooling典型配置是2×2窗口、步长2。这意味着输出尺寸直接在宽高方向各缩一半比如28×28的特征图变成14×14。为什么要池化首先它减少了后续层的计算量。其次是增加平移不变性——注意这个性质很多人理解歪了。最大池化并不是真的让网络“对平移完全不变”而是当某个特征在一个小区域内出现了不管具体在哪个位置只要窗口里有这个特征最大值就会被保留下来。换个说法网络不再关心“猫的眼睛在左上角还是右上角”而是关心“这个区域里有没有眼睛”。这对图像分类这类任务非常合适。平均池化也有用它比较温和常用于最后的全局平均池化——把整个特征图平均成一个数接全连接层分类。很多经典网络最后就用这个操作能有效防止过拟合。3.3 激活函数没有它深度学习就白搭激活函数不是卷积独有的但它是卷积神经网络里绝对不能少的一环。如果只有卷积的线性计算那不管堆多少层整个网络本质上都还是一个线性变换拟合能力非常有限。加入ReLU这种非线性函数网络才真正能逼近各种复杂函数。ReLU的实现异常简单输入大于0就原样输出小于等于0就输出0。就这么一个操作让网络具备了非线性能力还顺带缓解了梯度消失问题。你在看PyTorch代码时会看到卷积层后面跟着F.relu(x)就是这个作用。初学者容易漏掉的是几乎所有现代CNN都在卷积层和下一个卷积层之间加ReLU而不是只加在全连接层前面。每上一次卷积挤一点非线性进去效果会好很多。3.4 一个最小的CNN长什么样结构实例讲了一堆组件我们把它们拼起来。这里展示一个经典到不能再经典的小网络结构类似简化版LeNet-5用来识别手写数字输入1通道的28×28灰度图第一个卷积层3×3卷积输出6个通道激活后接2×2最大池化第二个卷积层3×3卷积输出16个通道激活后接2×2最大池化展平把16通道的特征图拉平成向量全连接层16×5×5400个输入接120个神经元再激活输出层120个输入接10个神经元对应数字0到9每次卷积和池化之后尺寸怎么变是可以手算的。输入28×28第一个卷积3×3不填充的话输出26×262×2池化后变13×13第二个卷积后变11×11池化后变5×55×5×16400正好接全连接。很多新手照着代码敲一遍不知道自己网络每层输出多大其实你完全可以拿张纸自己推一遍。我会在下一章专门把尺寸计算公式给你包教包会。4. 新手必看核、步长、填充、通道、感受野这五个参数4.1 卷积核大小为什么大家都爱用3×3在很多经典网络里3×3几乎是默认配置。这里面有个重要原因两个3×3卷积堆叠等价于一个5×5卷积的感受野但参数量更少。算一下两个3×3核的参数量是2×3×318而一个5×5核是25相同感受野下省了28%的参数。而且两层之间穿插了激活函数非线性更强。那能不能都用1×11×1卷积不改变空间尺寸但能改变通道数常被用来做通道之间的信息融合和降维比如残差网络里的瓶颈结构。但它没有捕捉空间特征的能力所以不能完全替代大核。我的建议是没有特殊需求卷积层就老老实实配3×3padding设为1这样输出尺寸和输入保持一致。等你熟悉了再去尝试5×5、7×7这种大核用在输入层附近因为早期图片尺寸大大核能更快看到更大范围。4.2 步长一次挪几步尺寸怎么算步长就是卷积核每次滑动的距离。步长为1相邻两次计算有重叠步长为2输出尺寸直接几乎减半相当于用卷积同时做降采样。不少现代网络喜欢用步长为2的卷积替代池化层效果差不多但少了一个需要调的超参数。尺寸计算公式必须会背输出尺寸 floor((输入尺寸 2×填充 - 核大小) / 步长) 1这个公式对宽和高分别计算。比如输入28×28核3×3填充1步长1那么输出是(282-3)/1128尺寸不变。如果步长2就是(282-3)/2114.5向下取整为14尺寸减半。我当年带过不少学员代码报错多半就是这里没算明白矩阵维度对不上一条traceback贴出来全是size mismatch。4.3 填充为什么要给图片“镶边”填充就是在输入的外围补一圈数值最常见的补0。它有两个作用一是防止图像边缘信息被过早丢弃二是在步长为1时让输出尺寸和输入一致。因为卷积核在边缘位置会出现窗口越界的问题不填充的话边缘像素参与计算的次数远少于中间像素这会导致边缘特征被系统性弱化。PyTorch里常用的两种paddingkernel_size // 2。核是3padding就是1核是5padding就是2。这样让输出尺寸保持和输入相同很多人管这叫“same padding”。做语义分割、目标检测这种需要保持空间尺寸的任务这个技巧太常用了。不过要注意补0并不是唯一选择还有补常数、补镜像、补重复边缘等方案但99%的场景用补0就够了。4.4 输入输出通道每个卷积核看一张“专属视角”通道这个概念是从输入图片的RGB三通道自然延伸出来的。第一层卷积的输入通道数等于输入图片的通道数输出通道数可以由你自由设定。每个输出通道对应一个独立的卷积核这个核会是三维的尺寸是K×K×C_in。输出通道数同时决定了这一层输出的特征图数量也决定了下一层卷积的输入通道数。层与层之间的通道数像一条“信息带宽”通道太少网络表达能力不够通道太多计算量成倍上涨。常见的做法是16 → 32 → 64这样的翻倍曲线配合池化让空间尺寸减半、通道数加倍保持信息总量不暴跌。我个人喜欢拿“钱”作类比一张图的信息量是有限的通道多相当于把信息分散到不同的账户里池化相当于把几张账户合并但总额别丢太多。4.5 感受野别看卷积只看局部其实能看到很远感受野是指输出特征图上的一个点对应回输入图像上有多大一块区域。感受野越大说明该点看了原图越大的范围能捕捉更宏观的特征。普通卷积层如果不做特殊处理每经过一层感受野是线性增长的而池化会让感受野成倍扩。理解感受野对设计网络很有用。比如你要检测图片里的大物体感受野就得大检测细纹理感受野反而不能太大。想增大感受野又不增加参数可以用膨胀卷积。这个我在后面变体章节会细说。这里你只需要建立概念卷积网络不是真正的“盲人摸象”它每一层都看比上一层更大的范围最终能组合边缘特征为整体语义。5. 用PyTorch从零写一个能跑的卷积神经网络5.1 环境准备装库、拿数据看完了理论和参数我们进入动手环节。搭一个能跑通的CNN用PyTorch是最快的路径。你先确认环境里有torch和torchvision版本别太老CPU机器也能跑完这个例子只是慢一点而已。这里用MNIST手写数字数据集来演示因为它足够简单、下载快、训练几分钟就能看到不错的效果。下面是准备数据的代码段import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset torchvision.datasets.MNIST( root./data, trainTrue, downloadTrue, transformtransform) test_dataset torchvision.datasets.MNIST( root./data, trainFalse, downloadTrue, transformtransform) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size256, shuffleFalse)这里Normalize((0.1307,), (0.3081,))里的两个数分别是MNIST数据集的均值和标准差是官方推荐的标准化参数。标准化之后数据分布接近0均值、单位方差训练会稳定很多。如果懒得算直接照抄这两个数就行。5.2 定义网络两段卷积加全连接下面这个网络是我在课上最常用来演示的结构简单、效果够看class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv1 nn.Conv2d(1, 16, kernel_size3, padding1) self.conv2 nn.Conv2d(16, 32, kernel_size3, padding1) self.pool nn.MaxPool2d(2, 2) self.fc1 nn.Linear(32 * 7 * 7, 128) self.fc2 nn.Linear(128, 10) def forward(self, x): x self.pool(F.relu(self.conv1(x))) # 14x14 x self.pool(F.relu(self.conv2(x))) # 7x7 x x.view(x.size(0), -1) x F.relu(self.fc1(x)) x self.fc2(x) return x注意看尺寸变化。输入是28×28第一个卷积因为padding1输出还是28×28池化后变成14×14。第二个卷积后保持14×14再池化变7×7。最后全连接层的输入维度是32×7×71568。这个数字你要是没算明白写代码时就会卡在view这一步报错说维度不匹配。有一个小坑我要专门提醒nn.Conv2d(1, 16, kernel_size3, padding1)中的第一个参数1是输入通道数因为MNIST是灰度图所以是1。如果你换成了RGD彩色图这里就要改成3。这是初学者最常踩的第一个坑没有之一。5.3 训练循环十行核心代码跑起来训练代码不用写得很复杂核心就是一个循环反复做前向传播、算损失、反向传播、更新参数device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleCNN().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) def train(epochs): model.train() for epoch in range(epochs): total_loss 0 correct 0 total 0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() print(fEpoch {epoch1}, Loss: {total_loss/len(train_loader):.4f}, fAccuracy: {100*correct/total:.2f}%) train(3)跑3个epoch这个极简模型准确率就能到99%左右。如果跑完发现准确率只有10%左右也就是相当于乱猜那一定是有bug了。最常见的原因有三个一是数据没有归一化二是学习率太大三是model.train()之后忘了model.eval()就去做推理。5.4 验证和测试评估你的模型到底行不行训练完模型要看它在没见过数据上的表现。这时候要切到eval模式并且关闭梯度计算def test(): model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in test_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() print(fTest Accuracy: {100 * correct / total:.2f}%) test()核心就两件事model.eval()告诉网络当前的阶段比如BatchNorm层会用它来切换行为torch.no_grad()则告诉PyTorch别存计算图了省内存也更快。这一步初学者容易忽略导致推理结果不稳定尤其是有Dropout或BatchNorm的模型差别更明显。到这里你已经亲手训练了一个可用的卷积神经网络。别看它小卷积、池化、步长、填充、全连接这些概念全在里面跑通了一遍。接下来我们再往外扩展一下看看现在工业界和学术界常用的几种卷积变体。6. 扩展视野转置卷积、深度可分离卷积、膨胀卷积、三维卷积6.1 转置卷积把特征图“放大”回去普通卷积通常会把空间尺寸缩小转置卷积恰恰相反它会放大尺寸所以也经常被叫“反卷积”虽然这个叫法严格来说不准确。它广泛用在图像生成、语义分割这类需要把特征图恢复成全尺寸的任务里。转置卷积并不是卷积的逆运算它更像是在每个输入像素周围“铺开”权重再进行一次普通卷积。你可以把它理解为每个输入点都按照卷积核的权重向外扩散扩散结果有重叠的地方就相加。代码实现也简单PyTorch里用nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride)stride2时通常可以把特征图尺寸放大一倍。这个操作在生成对抗网络的生成器里是标配你想生成高分辨率图片转置卷积几乎是必经之路。6.2 深度可分离卷积省参数的轻量方案MobileNet之所以能跑在手机上核心机密就是深度可分离卷积。它把标准卷积拆成了两步第一步是深度卷积每个通道单独用一个2D卷积核去卷这步不做通道间的混合第二步是逐点卷积用1×1卷积在通道方向做混合。这样一拆参数量大幅下降。举个例子输入64通道、输出64通道、核大小3×3的标准卷积参数是3×3×64×6436864。深度可分离卷积的参数量是3×3×64深度卷积部分加上1×1×64×64逐点卷积部分一共4096加4096约8192直接省了接近八成。这个变体特别适合资源受限的环境。如果你在移动端做一个图像分类模型优先考虑MobileNet或类似结构不用自己重新发明轮子。6.3 膨胀卷积让卷积核“睁大眼睛”膨胀卷积也叫空洞卷积它通过往卷积核的元素之间插入空洞来扩大感受野。膨胀率为2的3×3核覆盖范围等效于5×5但参数量依然是9个。这就巧妙地做到了“字面参数不变实际视野变大”。这个特性在语义分割里特别吃香。分割任务需要保持特征图分辨率不能频繁池化又想要大感受野来捕捉上下文信息。膨胀卷积正好两头兼顾。DeepLab系列就把这个思路用到了极致。遇到膨胀卷积的代码也就是nn.Conv2d(..., dilation2)一个参数搞定。需要留意的是膨胀率不能乱调膨胀太大会导致核覆盖范围过散有效信息反而稀疏效果可能不如普通卷积。6.4 三维卷积从平面扩展到立体三维卷积用在视频、医学影像、点云这类三维数据上。输入多了一个深度维或者时间维卷积核也变成了三维的立方体。在PyTorch里对应nn.Conv3d常见的用法是输入视频的帧序列形状是(C, D, H, W)其中D是帧数。三维卷积的参数量和计算量比二维体积大很多因为多了一个维度数据量更大了。训练3D卷积网络对显存的要求不低如果显存不够常见方案是先对时间维做分组处理或者用2D卷积逐帧提取特征后再用时序模型建模比如3D CNN配合LSTM、Transformer这类结构。这个领域水很深新手入门时要注意控制输入尺寸别一上来就是几十帧高清视频。6.5 特殊场景的变体自适应图卷积和球面卷积再提两个听起来更高级的变体。自适应图卷积是图神经网络里的概念它处理的是非规则结构数据比如社交网络、分子结构。这类数据不能直接套用常规的二维卷积因为每个节点的邻居数量都不一样。图卷积的做法是在图的邻域上做一种类似于卷积的聚合操作把邻居节点的特征加权重汇总起来。球面卷积处理的是球面数据比如全景图、地球物理数据。常规卷积假设数据在平面上但球面上存在极点畸变直接卷效果很差。球面卷积通过设计特殊的旋转不变核或谐波函数让卷积在球面上也能保持合理的性质。这类变体理解起来需要额外的数学基础如果你只是做普通图像任务先了解概念做储备就够了真正用到时再去深入。7. 卷积入门的常见问题与排查技巧7.1 尺寸算不对输出维度总对不上这是新手遇到最多、最让人头大的问题。明明模型结构写好了一跑就报size mismatch。我以前带学生的时候统计下来至少一半的报错都跟输出尺寸算错有关。解决这个问题的最好办法是养成“先手算尺寸再写代码”的习惯。拿到一张输入图按照输出尺寸公式逐步推每一步卷积或池化都算一次。你甚至可以像我一样写个简单的辅助函数直接在代码里打印每一层的输出形状def print_shape(layer, x): x layer(x) print(x.shape) return x用这个函数逐层打印一眼就能看到维度在哪里不匹配。卡很久的问题可能只要两分钟就能定位。7.2 显存爆了OOM报错怎么破模型稍微大一点训练到一半就报CUDA out of memory。遇到这个不要慌我一般按顺序做四件事第一把batch size调小这是最直接的办法。第二检查代码里有没有不必要的梯度保存比如在no_grad之外把大量中间变量留在了内存里。第三用torch.cuda.empty_cache()清理缓存虽然它不是银弹但能缓解碎片化问题。第四如果模型本身太大考虑换轻量网络结构比如用深度可分离卷积替代标准卷积。我见过不少人死磕batch size从64调到16还不行其实把图片尺寸从224改成128显存占用会降到之前的四分之一左右效果却差不了多少。很多时候问题出在输入尺寸上而不是模型结构上。7.3 通道顺序PyTorch和很多工具不一样图像数据的channel顺序在不同库之间一直有历史包袱。PyTorch用的是NCHW也就是batch维度、channel维度、高度、宽度而OpenCV读出来的图是HWC高宽在前通道在后。如果你用OpenCV读了一张图直接塞给PyTorch的模型几乎必然报错。解决方案很统一读图后立刻转换维度用img.transpose(2, 0, 1)把(H, W, C)变成(C, H, W)。再用Matplotlib显示的时候又得换回来。这个坑我在刚转PyTorch时踩了不知道多少次后来就养成了习惯凡是处理图像数据第一件事先确认通道维度顺序。7.4 训练不收敛Loss不降怎么办模型定义没问题数据也正常但训练几轮后损失函数纹丝不动。这个问题我建议按以下顺序排查先用小数据过拟合拿十几个样本训练看loss能不能降到接近0。如果连小数据都过拟合不了那可能是学习率太大或太小试试用0.001、0.0001缩放。如果小数据能过拟合但全量数据不收敛那大概率是数据归一化或初始化的问题。再一个常见原因是激活函数的选择和位置不对比如把所有激活都放在全连接层后面而卷积层之间没有ReLU网络表达能力就很受限。还有一个很隐蔽的原因标签类别不平衡。这个在MNIST这种平衡数据里不存在但换到真实业务数据就很常见。总之一句口诀先过拟合再谈泛化。模型连训练集都记不住就别指望它对测试集有表现了。7.5 常见问题速查表问题现象可能原因解决方案尺寸不匹配报错padding、stride、kernel算错逐步打印每层输出shape显存溢出batch size过大或输入尺寸过大调小batch、缩小图像、清空缓存通道维度报错OpenCV读图是HWC转成CHW再喂给模型Loss不下降学习率不合适尝试0.001和0.0001两个数量级推理结果不准忘了eval模式加model.eval()和no_grad()过拟合严重数据少或模型大加Dropout、数据增强、早停这张表里的内容几乎都是我实际教学和做项目时反复遇到的。收藏下来以后遇到了直接查。7.6 我的两条独家心得心得一学卷积最好的办法不是看更多博客而是动手算一次。找一张9×9或者5×5的小矩阵拿笔在纸上画一个3×3的核自己算三五个位置的输出。这个过程可能只需要十分钟但比你刷十篇讲解都管用。我当年就是笨办法一格一格算算到第三个位置就彻底懂了。心得二调试模型时一定要把网络结构可视化出来用torchsummary的summary(model, input_size(1, 28, 28))打印每层名称、输出尺寸、参数量。那个输出比你自己记的靠谱多了。遇到结构问题看图说话比看报错信息直观十倍。最后再分享一个小技巧如果你在用PyTorch写卷积模型刚开始的时候把weight_init手动加上比如用nn.init.kaiming_normal_对卷积层初始化很多收敛慢的问题可以瞬间缓解。这个细节很多教程不会提但它确实能让你少走很多弯路。卷积不难只要肯动手几次实验下来它就会变成你工具箱里最顺手的一件工具。
返回列表