十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

基于CNN的猫狗图像分类实战:从数据预处理到模型训练

基于CNN的猫狗图像分类实战:从数据预处理到模型训练 简介图像分类是计算机视觉中最基础也最典型的任务而卷积神经网络CNN通过自动学习边缘、纹理、形状等多层级视觉特征取代了传统人工特征加分类器的繁琐方案成为解决此类问题的核心技术。在实际工程中模型搭建只是其中一环真正决定项目成败的往往在于数据集的整理与预处理、训练流程的细节控制以及评估方法的全面性。本文从通用技术视角切入系统讲解如何组织图片数据集、统一尺寸与归一化、使用数据增强提升泛化能力并深入拆解CNN的卷积层、池化层和全连接层设计原理。在此基础上给出基于PyTorch的完整训练流程、模型保存与加载技巧以及利用混淆矩阵、精确率和召回率进行更全面评估的方法。最后引出迁移学习、目标检测等进阶方向帮助初学者将入门项目经验迁移到更广泛的应用场景中。 第一次用卷积神经网络做猫狗图像分类的时候我最大的困惑不是模型怎么搭而是数据集怎么整理。网上很多“源码数据集”项目下载下来目录乱成一团图片大小不一有的还带水印跑起来全是坑。后来我才明白一个能直接跑通的Python深度学习项目真正值钱的部分往往不在model.py里而在数据预处理和训练流程那些容易被忽略的细节中。这篇文章从项目本身出发围绕基于卷积神经网络实现的猫狗图像分类结合完整源代码和数据集把我们实际动手时最关注的几件事讲清楚数据集该怎么整理、CNN模型该怎么搭、训练过程有哪些坑、评估结果怎么看得更全面。无论你是刚入门的深度学习初学者还是已经在用PyTorch做分类任务、想补全整个项目细节的开发者这篇都能给你一些可以直接抄走的经验。1. 这个猫狗分类项目到底在解决什么问题1.1 从需求场景说起为什么要做图像分类图像分类是计算机视觉里最基础、也最典型的一类任务。给定一张图片模型输出它属于哪个类别在这个项目里就是“猫”或“狗”。听起来简单但背后覆盖了深度学习项目的大部分通用流程数据准备、模型设计、训练、评估、预测部署。你把这个流程跑通了后面换到其他分类任务比如识别车型、判断工业零件缺陷、区分农作物病害路线都是一样的。有些朋友会问“猫狗分类这种入门项目网上不是一抓一大把吗还有必要认真做吗”我的看法恰恰相反。正因为它是入门项目才值得把每一步都吃透。很多人用预训练模型跑通一个demo就觉得自己会了但实际上数据加载为什么要这样写、损失函数为什么选交叉熵、训练时为什么得分batch、验证集和测试集有什么区别这些问题不亲手从头搭一遍是搞不明白的。这个项目的“源码数据集”形态正好补足了纯理论学习的短板。你不需要自己满世界去找图片也不用从零写数据爬虫拿到材料后可以直接从数据预处理开始跑遇到问题时又能对照源码逐行排查。这种完整闭环的学习方式比只跑通一个notebook要扎实得多。1.2 为什么选卷积神经网络而不是传统方法在深度学习普及之前做猫狗识别这类任务通常走的是“人工特征传统分类器”路线。比如先用HOG或者SIFT提取图片特征再训练一个SVM分类器。这个方案有几个明显的毛病特征需要人凭经验设计不同数据集的适配成本高光照、背景、遮挡稍微变一变特征就不稳定模型泛化能力有限。卷积神经网络CNN本质上把“特征提取”这一步也交给了网络自己学习。卷积层在训练过程中自动学到边缘、纹理、形状等不同层级的视觉特征不需要人工去设计特征算子。这个差别非常关键。你可以把传统方法理解为“按图索骥”——你得先知道要找什么线索CNN则是“自己总结规律”——你只给它数据和标签它自己摸索出猫和狗的区别。具体到猫狗分类猫和狗在体型、毛色、耳朵形态、脸部比例上都有差异有些还很相似比如毛发颜色相近的柯基和橘猫。CNN通过多层卷积组合能从像素级细节里提取出足够有区分度的特征这在传统方法下是很难实现的。1.3 项目整体架构与预期效果这个项目的标准流程可以用一条线串起来图片数据 → 数据预处理 → CNN模型 → 训练 → 评估 → 预测。我习惯把整个项目分成几个层级来看数据层原始图片、标签文件、训练/验证划分逻辑。模型层网络结构定义、权重初始化、前向传播。训练层损失计算、反向传播、优化器更新、学习率调整。评估层准确率、混淆矩阵、分类报告。应用层单张图片预测、批量预测接口。预期效果方面如果只用自定义的轻量CNN在几万张猫狗图片上训练验证集准确率通常能到85%~92%左右。如果换上ResNet这类预训练模型做迁移学习达到98%以上也不难。取决于你对模型复杂度和训练时间的取舍。2. 数据集获取与预处理第一次踩坑就在这2.1 数据集来源与目录组织猫狗分类最经典的数据集是Kaggle上的“Dogs vs. Cats”数据集包含25000张训练图片和12500张测试图片猫和狗各占一半。这个数据集的优点是量大、贴近真实场景图片里有各种姿势、光线和背景缺点是图片大小不一、有些还带水印和信息栏需要清洗。源码包里的数据集通常已经整理成类似下面的目录结构dataset/ ├── train/ │ ├── cat.0.jpg │ ├── cat.1.jpg │ ├── dog.0.jpg │ └── dog.1.jpg ├── val/ │ ├── cat.0.jpg │ ├── dog.0.jpg │ └── ... └── test/ ├── 1.jpg └── 2.jpg有些版本会按类别分子文件夹train/ ├── cats/ │ ├── xxx.jpg │ └── ... └── dogs/ ├── xxx.jpg └── ...这个差异会直接影响后面写DataLoader的方式。如果你从网上下载的源码用的是第一种命名方式但你的数据集是第二种结构那训练前就必须改加载逻辑否则标签会全部错乱。我见过不少朋友卡在这一步其实问题不在代码而在目录约定。我的习惯是拿到任何数据集先跑一段快速扫描代码统计每个类别的图片数量、图片格式、是不是有损坏文件确认无误再进模型。这一步能省掉后面无数个深夜排查时间。2.2 图片清洗与尺寸统一CNN的输入尺寸是固定的比如224×224或128×128所以数据预处理里最重要的一件事就是统一尺寸。直接resize当然可以但有几个细节需要注意。首先是图片格式问题。数据集中可能混有灰度图、RGBA图、损坏的截断图片。用OpenCV读取时灰度图直接读出来是单通道RGBA多一个alpha通道模型输入是固定三通道的话就会报维度错误。我一般在数据加载时统一做一次转换import cv2 def load_image(path, target_size(224, 224)): img cv2.imread(path) # 有些图是灰度图需要转成三通道 if len(img.shape) 2: img cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) elif img.shape[2] 4: img cv2.cvtColor(img, cv2.COLOR_RGBA2RGB) img cv2.resize(img, target_size) return img其次是归一化。像素值范围是0~255如果直接丢给模型数值过大容易导致梯度不稳定。一般会把像素值缩放到0~1或者按均值和标准差做标准化。PyTorch里常用transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225])这是ImageNet数据集的均值和标准差对通用图片效果比较好。还有一点容易被忽略图片的方向。手机拍摄的图片经常带有EXIF旋转信息如果不处理缩略图方向是对的但读成数组后是横着的。数据清洗阶段最好统一按EXIF信息校正一下方向不然模型会学到“竖着的猫”和“横着的猫”两种特征影响泛化。2.3 数据增强用有限数据榨出更多泛化能力数据增强是图像分类里性价比最高的技巧之一。它的核心思想是在训练时对图片随机做微小变换让模型看到更多变体从而提高泛化能力。你不需要增加一张真实图片就能让模型学会应对平移、翻转、亮度变化等情况。常用的增强操作包括随机水平翻转猫狗图片左右翻转不影响类别这个操作简单且有效。随机旋转±15度增加角度鲁棒性。随机裁剪缩放模拟不同距离的观察视角。颜色抖动改变亮度、对比度、饱和度提升对光照变化的适应能力。PyTorch里用torchvision.transforms组合即可from torchvision import transforms train_transform transforms.Compose([ transforms.RandomResizedCrop(224, scale(0.8, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])注意数据增强只在训练集上用验证集和测试集通常只做resize和归一化。否则验证集的结果会被增强操作污染无法反映模型在真实图片上的表现。还要提醒一点增强强度不是越大越好。旋转角度太大猫到了上下颠倒的位置虽然理论上模型也能学但分类难度会明显增加训练收敛变慢小数据集上反而容易欠拟合。建议从小的增强强度开始观察验证集准确率的变化。3. 卷积神经网络模型搭建与核心原理拆解3.1 卷积层、池化层、全连接层到底在干嘛CNN的基本组件不多但每个都有明确的职责。卷积层的核心是“局部特征提取”。一个卷积核其实就是一个小的权重矩阵比如3×3或5×5它在输入图片上滑动每一步做一次点积运算生成一个特征图。不同的卷积核会关注不同的特征有的负责边缘有的负责纹理更深的层则组合出眼睛、耳朵、四肢等语义部件。卷积层通过参数共享大大减少了参数量这是它比全连接层更适合图像的核心原因。池化层的作用是“下采样”和“保持平移不变性”。最常用的是最大池化比如2×2窗口取最大值。它把特征图尺寸缩小一半保留了最显著的特征同时也增大了后续卷积层的感受野。你可以把池化理解为“把一张高清图缩小成缩略图主要内容还在细节有所损失但整体语义更集中”。全连接层则负责把卷积层提取的高层特征映射到类别空间。通常在网络最后把特征图展平成一维向量经过若干全连接层最终输出每个类别的得分。在分类任务里全连接层的输出维度等于类别数猫狗分类就是2。一个常见的误解是网络越深越好。实际上单纯堆叠卷积层而不加BatchNorm、残差连接等设计网络会很难训练甚至出现退化问题。所以搭建网络时不要盲目追求层数先把基础结构跑通再逐步加深。3.2 模型结构设计从LeNet到自定义CNN我们可以设计一个轻量级CNN来跑猫狗分类结构参考LeNet思想但做了现代化调整。下面这个结构在源码里比较常见import torch.nn as nn class CatDogCNN(nn.Module): def __init__(self, num_classes2): super().__init__() self.features nn.Sequential( nn.Conv2d(3, 32, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(32, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), nn.Conv2d(64, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), ) self.classifier nn.Sequential( nn.Flatten(), nn.Linear(128 * 28 * 28, 256), nn.ReLU(inplaceTrue), nn.Dropout(p0.5), nn.Linear(256, num_classes) ) def forward(self, x): return self.classifier(self.features(x))输入224×224的图片经过三层卷积和池化后特征图尺寸变成28×28因为224/2/2/228输出通道128所以全连接层第一层的输入维度是128×28×28。这里有几个设计考量卷积核都用3×3这是当前主流做法。两个3×3卷积堆叠的感受野等同于一个5×5卷积但参数量更少非线性表达能力更强。每层卷积后都接ReLU激活引入非线性。没有激活函数的话多层线性变换等价于一层线性变换就没有“深度”的意义了。分类器里加了Dropout随机丢弃一半神经元这是最常用的防过拟合手段。这个模型参数量很小在GPU上训练非常快在CPU上也能跑。如果你觉得效果不够好可以把卷积通道数加倍或者再增加一层卷积但也要注意过拟合问题。3.3 激活函数、损失函数与优化器选择激活函数方面隐藏层用ReLU是默认选择计算简单且能缓解梯度消失。但ReLU有个问题负数区域梯度直接为0如果学习率设置不当很多神经元会“死掉”权重不再更新。实际训练中如果发现损失不降可以检查一下是否出现大量Dead ReLU这时候适当降低学习率或者改用LeakyReLU会有帮助。输出层做二分类一般用LogSoftmax或者直接输出裸logits配合交叉熵损失函数。损失函数选nn.CrossEntropyLoss()是标准做法。它内部把softmax和负对数似然结合在了一起输入不需要手动做softmax。它的设计初衷是优化概率分布之间的差异非常适合分类任务。要注意的是如果你的标签是0/1但网络输出是二维向量CrossEntropyLoss要求标签是类别索引0或1不要传one-hot编码否则会报错。优化器常用Adam学习率初始值取1e-3或1e-4都行。Adam自带了动量机制收敛比较快对学习率的敏感度也比SGD低适合新手。但SGD配合动量在充分调参后往往能取得更好的最终精度。我的建议是项目初期用Adam快速验证流程后期想冲精度再用SGDmomentum配合学习率衰减。学习率衰减也很重要。常见做法是每若干轮把学习率乘一个系数比如每10轮乘0.1或者用ReduceLROnPlateau在验证集指标不再提升时自动降低学习率。我实测下来ReduceLROnPlateau更省心不需要手动扳着指头算epoch。4. 训练流程与代码实现细节4.1 训练集/验证集划分与DataLoader数据准备好后第一步是划分训练集和验证集。验证集不能和训练集重叠否则模型评估结果会虚高。常见的划分比例是8:2或9:1。如果数据集本身已经分了train和val目录直接用就行如果没有可以用train_test_split或者手动按文件名划分。这里有个容易被忽视的问题数据划分时要做分层抽样保证训练集和验证集中猫狗比例都接近1:1。如果随机划分导致验证集里全是猫那模型预测狗的能力就没得到评估准确率指标会失真。PyTorch中把数据加载封装成Dataset和DataLoader。一个简化版代码如下from torch.utils.data import Dataset, DataLoader from PIL import Image class CatDogDataset(Dataset): def __init__(self, file_list, label_list, transformNone): self.file_list file_list self.label_list label_list self.transform transform def __len__(self): return len(self.file_list) def __getitem__(self, idx): image Image.open(self.file_list[idx]).convert(RGB) if self.transform: image self.transform(image) label self.label_list[idx] return image, labelDataLoader设置batch_size的时候要考虑显存大小。以224×224输入、batch_size为32为例在6GB显存的GPU上上面的轻量CNN可以轻松跑。如果换ResNet50可能就得降到16甚至8。num_workers参数也很重要。如果设成0数据加载在主进程里做速度会慢设成4或8可以用多进程预取数据大幅缩短每个epoch的时间。但要注意在Windows上num_workers大于0时代码必须放在if __name__ __main__:保护块里否则会报错。4.2 训练循环里那些容易忽略的细节训练循环的骨架大家都清楚前向传播、算损失、反向传播、更新权重。但有几个细节新手特别容易踩。第一个是optimizer.zero_grad()。PyTorch的梯度是累加的如果每一轮不清零梯度会在多个batch上累积导致更新方向完全错乱。正确做法是每个batch都要清零梯度。我习惯把这三行连着写optimizer.zero_grad() loss.backward() optimizer.step()第二个是模型模式切换。训练前要调model.train()让Dropout和BatchNorm进入训练模式评估前要调model.eval()让它们进入推理模式。如果用torch.no_grad()包裹评估过程能减少内存占用因为不需要计算梯度。第三个是梯度裁剪。在训练初期或者使用大学习率时梯度可能爆炸导致损失变成NaN。这时可以加一个梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm5.0)。这相当于给梯度设置了一个上限防止更新步伐过大。训练过程中最好每一轮都记录损失和验证集准确率。我一般会打印这样的日志Epoch [3/20] train_loss: 0.2384 train_acc: 0.9021 val_loss: 0.2871 val_acc: 0.8817顺带说一句如果发现训练损失一直在降但验证损失开始反弹说明模型过拟合了这时候应该减少训练轮数、增大Dropout或者加强数据增强。4.3 模型保存与加载的坑模型保存看起来简单但坑不少。最常见的保存方式是把模型参数保存为state_dicttorch.save(model.state_dict(), best_model.pth)加载的时候要先实例化模型再load_state_dictmodel CatDogCNN(num_classes2) model.load_state_dict(torch.load(best_model.pth, map_locationcpu))这里有一个容易踩的坑保存模型时用GPU训练加载时如果机器没有GPU必须加map_locationcpu否则会抛出类似“Ran out of memory”的错误。还有一个坑是模型结构定义变了再加载旧权重比如全连接层维度改了load_state_dict会因为key不匹配而报错。所以保存模型时最好连超参数和网络结构信息一起存下来torch.save({ model_state_dict: model.state_dict(), num_classes: num_classes, input_size: 224 }, checkpoint.pth)这样以后加载时还能知道当初是怎么训练出来的避免“代码重写后权重废掉”的尴尬。另外保存模型时不要只保存“最后一个epoch”的模型最好在验证集准确率最高的时候保存一份。我通常用一个简单的逻辑if val_acc best_acc: best_acc val_acc torch.save(model.state_dict(), best_model.pth)这样即使后面过拟合了手里还有一份最优备份。5. 预测与评估只看准确率远远不够5.1 混淆矩阵、精确率、召回率怎么解读分类任务的评估不能只看准确率。在猫狗分类这种类别平衡的数据集上准确率确实有参考价值但如果遇到类别不平衡的数据集准确率会骗人。假设99%是猫模型把全部预测成猫准确率也有99%但显然没有任何实用价值。这时候混淆矩阵能帮你看得更细。它描述了真实类别和预测类别的对应关系预测猫 预测狗 实际猫 TN FP 实际狗 FN TP召回率表示“实际是狗被正确找回来的比例”精确率表示“预测为狗的样本里真正是狗的比例”。在猫狗分类场景中如果你做的是宠物自动喂食器漏掉一只狗可能没关系但误把猫识别成狗可能就会出问题。不同的应用场景对精确率和召回率的侧重完全不同。PyTorch里可以用sklearn.metrics快速生成这些指标from sklearn.metrics import confusion_matrix, classification_report cm confusion_matrix(y_true, y_pred) print(classification_report(y_true, y_pred, target_names[cat, dog]))我每次训练完都会打印这两样东西比单纯看准确率能更早发现模型偏向性问题。比如模型对猫的召回率95%但对狗只有80%那说明它在区分某些体型的狗时遇到了困难可能需要在数据增强里增加相关的变换。5.2 单张图片预测与批次预测的差异训练完成后实际使用场景里有两种预测方式单张图片预测和批量预测。两者的实现细节有很大区别。单张图片预测最容易出的问题是维度。训练时输入是四维张量(batch, channel, height, width)但单张图片读出来是三维(channel, height, width)需要先unsqueeze(0)加一个批次维度image load_image(test_cat.jpg) image torch.tensor(image).permute(2, 0, 1).unsqueeze(0).float() image image / 255.0 with torch.no_grad(): output model(image) prob torch.softmax(output, dim1) pred torch.argmax(prob, dim1).item()这里要注意PIL和OpenCV读取图片的通道顺序不同。PIL读出来是RGBOpenCV读出来是BGR如果混用模型看到的就是蓝红通道互换的图片预测结果会大幅下降。我自己就踩过这个坑明明验证集准确率93%随手拿一张图片测试却总是分错最后发现是用OpenCV读取但训练时却用PIL转换颜色通道错乱了。批量预测则要考虑内存占用。如果图片数量很多不要一次性全部加载到GPU显存应该分批预测并将结果收集到列表。DataLoader的batch_size可以设大一点反正只在推理模式下跑不保存梯度。5.3 可视化中间特征图CNN到底学到了什么很多初学者把CNN当成一个黑盒训练完就算完事。但如果你想把模型调得更好或者向别人解释模型到底在做什么可视化中间特征图是非常重要的手段。方法不复杂在前向传播时把某些层的输出记录下来然后转换成图片显示。以我们前面定义的CatDogCNN为例可以这样获取第一层卷积的输出activation {} def hook_fn(name): def hook(model, input, output): activation[name] output.detach() return hook model.features[0].register_forward_hook(hook_fn(conv1)) output model(image)拿到特征图之后把每个通道画出来。你会发现浅层的特征图大多是边缘、颜色块深层的特征图则对应眼睛、耳朵、身体轮廓等更抽象的语义信息。这个过程能帮你判断模型有没有学到合理特征。有一次我训练完一个猫狗分类模型发现第一层卷积核几乎全是灰的权重值特别小。排查后发现是学习率设置得太高训练一开始就崩了模型退化成只会输出固定类别。后来调低学习率并加了学习率预热特征图才恢复正常。这种问题如果只看准确率很容易误以为是数据问题。6. 源码结构与运行环境配置6.1 环境版本组合建议深度学习项目最让人头痛的问题之一就是环境配置。不同版本的Python、PyTorch、CUDA之间可能存在兼容性问题盲目装最新版不一定好用。以这个猫狗分类项目为例我推荐一套比较稳妥的版本组合Python 3.8 或 3.103.9也可以但有些旧依赖容易出问题PyTorch 1.13 或 2.xtorchvision 0.14如果配合PyTorch 1.132.x的话torchvision版本会对应变化CUDA 11.7 或 12.1取决于显卡驱动numpy、opencv-python、pillow、matplotlib、scikit-learn安装PyTorch时建议直接用PyTorch官网的安装命令比如pip install torch torchvision --index-url https://download.pytorch.org/whl/cu117如果机器上没有GPU或者显卡驱动版本较老安装CPU版本也能跑通整个流程只是训练速度会慢。猫狗分类这种小网络在CPU上也能训练只是每个epoch要等比较久。我以前的旧笔记本没有独立显卡训练20个epoch大约要四五个小时后来换了GPU只要几分钟。6.2 源码文件逐个说明拿到源码包后先别急着运行把目录结构看清楚。一个标准的猫狗分类项目通常包含这些文件project/ ├── data/ │ ├── train/ │ └── val/ ├── src/ │ ├── dataset.py # 数据集加载与预处理 │ ├── model.py # CNN模型定义 │ ├── train.py # 训练脚本 │ ├── predict.py # 单张图片预测脚本 │ └── utils.py # 工具函数画图、日志等 ├── checkpoints/ # 模型保存目录 ├── requirements.txt └── README.mddataset.py里可能定义了CatDogDataset类负责读图、做增强、返回样本和标签。model.py里是网络结构定义。train.py是主入口包含数据加载、训练循环、验证循环、模型保存逻辑。predict.py则是让你传入一张图片输出猫或狗的预测结果。我建议拿到源码后先按顺序读一遍README.md和train.py的主流程不要上来就运行。有些源码包依赖太多需要手动下载预训练权重或者数据集路径写死了不改成相对路径就会报错。你至少要先搞清楚数据集路径在哪设置batch_size和epoch在哪里改模型保存到哪个目录这几个问题搞清楚了运行起来基本就稳了。6.3 运行过程中常见的依赖冲突依赖冲突是运行源码时最常见的拦路虎。很多人会遇到“ImportError: cannot import name xxx from torchvision”这通常是因为torch和torchvision版本不匹配。解决办法很简单pip list看一下两个包的版本然后在torchvision官方文档里确认对应关系。千万不要只升级其中一个要一起升级或者一起降级。另一个常见问题是numpy版本过高导致某些旧代码报错。比如np.float属性在numpy 1.24之后被移除了如果你运行的代码用了np.float会报AttributeError。这时可以降低numpy版本或者把代码里的np.float改成float。我一般会避免在代码里使用带np.float这种过时的写法因为它在新旧版本间很容易出问题。还有opencv-python和opencv-contrib-python不能同时安装否则会有底层库冲突甚至导致程序崩溃。只保留其中一个就好。7. 从基础CNN到实用改进调优思路与后续方向7.1 从普通CNN到迁移学习如果你已经通过自定义CNN跑通了猫狗分类下一步最值得做的改进就是迁移学习。迁移学习的思路很简单利用在ImageNet这种超大数据集上预训练好的模型比如ResNet18、ResNet50或者EfficientNet替换掉最后的分类层然后用自己的数据集微调。PyTorch里实现迁移学习非常方便import torchvision.models as models model models.resnet18(pretrainedTrue) model.fc nn.Linear(model.fc.in_features, 2)预训练模型已经学到了丰富的通用视觉特征即使你的训练数据不多也能达到不错的效果。我在只有几千张猫狗图片的数据集上直接训练自定义CNN准确率大约88%换用ResNet18冻结前面的层、只训练最后的分类层准确率能到95%以上如果解冻全部层微调几个epoch可以接近98%。这里有一个需要权衡的点解冻多少层微调。如果数据集很小解冻太多层容易过拟合数据集大冻结太多层又限制模型表达力。我的经验是先冻结全部层只训练分类头跑通之后逐步解冻最后一两个block观察验证集变化。这样比较稳。另外要注意迁移学习的输入尺寸一般为224×224如果你的数据预处理用了不同的resize尺寸模型效果会受影响。7.2 过拟合与欠拟合的排查思路训练过程中最常见的问题就是过拟合和欠拟合。我总结了一个简单的排查思路。如果训练损失下降很慢准确率也低先怀疑欠拟合。这时候优先检查模型容量够不够。网络太浅、卷积通道太少可能学不到复杂特征。学习率是否合适。学习率太大会振荡太小会龟速。数据预处理对不对。归一化是否做了标签是否对齐增强是否过强。如果训练准确率很高、验证准确率偏低基本就是过拟合。解决手段按优先级排序增加数据增强随机翻转、旋转、颜色抖动。增加Dropout比例从0.2提到0.5。减小模型容量减少卷积层通道数或者换成更浅的网络。降低训练轮数保存最优模型而不是最后几个epoch。用迁移学习替代从头训练。每次只改一个变量不要同时改一堆东西否则你很难知道是哪个改动起了作用。7.3 还可以怎么玩目标检测、风格迁移等猫狗分类只是CNN应用的冰山一角。当你把分类流程吃透之后有很多自然的延伸方向。一个方向是目标检测。分类模型只能回答“图里有没有猫/狗”但目标检测能回答“猫/狗在哪”。YOLO、SSD、Faster R-CNN都是常用的检测框架。你之前的分类数据可以做清洗和标注然后转换成目标检测格式用来训练一个同时定位和分类的模型。很多宠物自动喂食器、智能猫门背后就是这类功能。另一个方向是细粒度分类。猫狗分类只区分两个物种但你也可以扩展成品种识别比如区分拉布拉多、金毛、柯基或者区分波斯猫、暹罗猫、英短。细粒度分类的难度会比猫狗分类高很多因为不同品种的差异更细微需要更精细的特征提取能力。这也是一个很好的挑战项目。还有很多和猫狗分类技术类似的视觉项目可以做医学影像分类、卫星图像识别、工厂质检中的缺陷检测。核心流程都是“数据准备-CNN训练-评估-部署”只是数据领域不同。所以别小看这个入门项目把它的每个环节都搞透后面扩展起来会顺畅很多。最后再分享一个我做这类项目的习惯从拿到数据和源码的第一天起就建一个训练日志表格记录每次实验的网络结构、学习率、数据增强方式、验证准确率。刚开始可能觉得麻烦但一旦你开始调参这个日志能帮你少走很多弯路。猫狗分类虽然简单但把项目流程管理好后面无论做多复杂的模型都能从容应对。本文还有配套的精品资源点击获取
返回列表