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

Python深度学习图像分类入门:从数据准备到模型调优的完整指南

Python深度学习图像分类入门:从数据准备到模型调优的完整指南 ★ FEATURED ARTICLE
简介这份资源面向深度学习与计算机视觉的入门学习者提供一套基于 PyTorch 的图像分类完整实践项目。项目采用一个纯净无噪声的遥感图像数据集共 21 类、每类 100 张图像并按 8:2 随机划分为训练集与验证集适合作为分类任务的练手素材。压缩包共 34 个文件以 14 个 py 脚本、10 个 pyc 编译文件、5 个 txt 数据列表及 jpg、eps 图表等为主整体约 727KB涵盖网络定义、学习率调度、数据集加载、精度曲线绘制与 log 记录等模块构成一套可复用的完整 pipeline。目前已有 247 人学习。读者可借此掌握从数据组织、模型训练到指标可视化的全流程并针对不同任务快速修改网络与配置是入门图像分类的实用参考。1. 从一张模糊的猫图说起为什么你的第一个图像分类模型值得认真对待很多刚接触深度学习的朋友第一次跑图像分类用的都是同一套流程找一份现成数据集抄一段卷积网络代码训练几个轮次看到准确率涨到八九成就觉得大功告成。但真正把模型拿去识别自己拍的照片时结果往往惨不忍睹——明明是一只猫模型却信心满满地判成狗。这不是玄学而是因为入门教程里省略了太多关键细节数据怎么组织、预处理做了哪些变换、模型到底在学什么、评估指标该怎么看。基于 Python 的深度学习图像分类入门核心不是背出 ResNet 有多少层而是建立一条从原始图片到可用分类器的完整链路并且知道每一步为什么这么做。这篇文章面向的是有 Python 基础、想亲手跑通第一个图像分类任务的开发者我会把数据准备、模型搭建、训练调参、结果排查这几个环节拆开讲让你不仅能复现还能在翻车时知道去哪里找原因。2. 数据准备从一堆散图到模型能吃的张量2.1 目录结构决定你少写多少胶水代码图像分类任务的第一步永远是把图片整理成模型能读的格式。常见做法是按类别分文件夹每个类别一个子目录目录名就是标签名。这种结构被主流框架的ImageFolder类直接支持省去手写标签映射的麻烦。假设你手头有猫、狗、鸟三类图片整理后应该是这样的dataset/ ├── train/ │ ├── cat/ │ │ ├── cat_001.jpg │ │ └── ... │ ├── dog/ │ └── bird/ └── val/ ├── cat/ ├── dog/ └── bird/训练集和验证集必须分开且每个类别在验证集中都要有样本。我一般按 8:2 划分如果某类样本特别少至少保证验证集每类不少于 20 张否则评估结果波动会大到让你怀疑人生。注意不要用训练集里的图片做验证那等于考试前把答案背下来准确率虚高没有意义。2.2 用 torchvision 做预处理三个必须理解的参数Python 里最常用的图像加载和预处理工具是torchvision.transforms。下面这段代码是训练时的标准预处理流程from torchvision import transforms train_transform transforms.Compose([ transforms.Resize(256), # 将图片短边缩放到256像素 transforms.RandomCrop(224), # 随机裁剪出224x224的区域 transforms.RandomHorizontalFlip(), # 随机水平翻转概率0.5 transforms.ToTensor(), # 转为张量像素值归一化到[0,1] transforms.Normalize( # 按ImageNet均值和标准差标准化 mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225] ) ])Resize(256)加RandomCrop(224)是经典组合先缩放到稍大尺寸再随机裁剪相当于做了数据增强让模型不会对某个固定位置过拟合。RandomHorizontalFlip对猫狗鸟这类左右对称的类别是安全的但如果你分类的是数字或文字翻转会改变语义千万别加。Normalize里的均值和标准差来自 ImageNet 数据集如果你用预训练模型这三个数字必须保持一致否则输入分布对不上微调效果会打折扣。验证集不需要随机裁剪和翻转只做Resize(224)加中心裁剪即可。2.3 用 DataLoader 把内存管起来图片全部读进内存再训练对稍大一点的数据集就是灾难。DataLoader负责按批次加载配合多进程读取能显著加快训练速度from torch.utils.data import DataLoader from torchvision.datasets import ImageFolder train_dataset ImageFolder(dataset/train, transformtrain_transform) val_dataset ImageFolder(dataset/val, transformval_transform) train_loader DataLoader( train_dataset, batch_size32, # 每批32张图显存不够就降到16或8 shuffleTrue, # 训练集打乱顺序防止模型记住样本顺序 num_workers4, # 4个子进程读数据Windows下建议设为0 pin_memoryTrue # 锁页内存加速GPU传输 ) val_loader DataLoader( val_dataset, batch_size32, shuffleFalse, # 验证集不需要打乱 num_workers4 )batch_size是最需要根据硬件调整的参数。显存 8GB 以下建议从 16 开始试如果训练时出现 CUDA out of memory优先降 batch_size 而不是改模型。num_workers在 Linux 下设 4 或 8 都行Windows 下如果报错就改成 0这是血泪经验。pin_memoryTrue只在用 GPU 时有效能减少数据从内存到显存的拷贝时间。3. 模型搭建别急着从零写卷积层3.1 迁移学习是入门阶段最划算的选择自己从零搭一个卷积网络在几千张图片上训练准确率通常很难超过 70%。而用预训练模型做迁移学习同样的数据量准确率能轻松到 90% 以上。原因很简单预训练模型在百万级图片上已经学会了提取边缘、纹理、形状等通用特征你只需要让它适应你的类别即可。常见做法是加载 ResNet18 或 ResNet50把最后的全连接层替换成你的类别数import torch.nn as nn from torchvision import models def build_model(num_classes3, pretrainedTrue): model models.resnet18(pretrainedpretrained) # 冻结前面的卷积层只训练最后的分类层 for param in model.parameters(): param.requires_grad False # 替换全连接层输入维度是ResNet18的512 model.fc nn.Linear(512, num_classes) return modelpretrainedTrue会下载 ImageNet 预训练权重第一次运行需要联网。冻结卷积层是为了防止小数据集把预训练特征破坏掉如果你的数据量超过每类 1000 张也可以不冻结做全网络微调效果可能更好。model.fc的输入维度取决于具体模型ResNet18 是 512ResNet50 是 2048换模型时记得改。3.2 损失函数和优化器的选择逻辑分类任务用交叉熵损失优化器入门阶段用 Adam 或 SGD 都可以。Adam 收敛快适合快速验证想法SGD 配合动量在调好后泛化能力往往更好。我一般先用 Adam 跑通流程再换 SGD 精调import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model build_model(num_classes3).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam( filter(lambda p: p.requires_grad, model.parameters()), lr1e-3, # 初始学习率迁移学习常用1e-3到1e-4 weight_decay1e-4 # 权重衰减防止过拟合 )filter(lambda p: p.requires_grad, ...)这行很关键它只把需要更新的参数传给优化器冻结的层不会被误更新。lr1e-3是 Adam 的常用起点如果训练损失震荡厉害降到 1e-4。weight_decay相当于 L2 正则小数据集上建议加上。3.3 训练循环里必须记录的三件事训练循环本身不复杂但有三样东西必须每轮记录训练损失、验证损失、验证准确率。只看训练损失下降就以为模型在变好是新手最容易踩的坑def train_one_epoch(model, loader, criterion, optimizer, device): model.train() running_loss 0.0 for images, labels in loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() * images.size(0) return running_loss / len(loader.dataset) def evaluate(model, loader, criterion, device): model.eval() correct 0 total 0 val_loss 0.0 with torch.no_grad(): for images, labels in loader: images, labels images.to(device), labels.to(device) outputs model(images) loss criterion(outputs, labels) val_loss loss.item() * images.size(0) _, predicted torch.max(outputs, 1) correct (predicted labels).sum().item() total labels.size(0) return val_loss / total, correct / totalmodel.train()和model.eval()必须成对出现前者启用 Dropout 和 BatchNorm 的训练模式后者切换到推理模式。torch.no_grad()在验证时关闭梯度计算省显存也提速。如果验证损失开始上升而训练损失还在降说明过拟合了该加数据增强或提前停止。4. 训练调参与排错那些教程不会告诉你的细节4.1 学习率怎么设才不翻车学习率是训练中最敏感的参数。设大了损失震荡甚至发散设小了收敛慢到你以为代码卡住了。一个实用的做法是用学习率扫描从 1e-5 到 1e-1 各跑几十个批次看哪个量级损失下降最快。迁移学习场景下1e-3 对 Adam 通常是安全的起点。如果训练几个轮次后准确率卡住不动试试学习率衰减scheduler optim.lr_scheduler.StepLR(optimizer, step_size7, gamma0.1) # 每7个epoch学习率乘以0.1step_size和gamma需要根据总轮次调整。总共跑 20 轮的话step_size7 意味着第 7 和第 14 轮各降一次比较合理。4.2 过拟合和欠拟合的快速判断训练准确率 99%验证准确率 60%这是典型过拟合。解决办法按优先级排加数据增强、加 weight_decay、减小模型复杂度、提前停止。反过来训练和验证准确率都只有 50%那是欠拟合需要增大模型、提高学习率、减少正则化。我习惯每轮打印两个准确率的差值差值超过 15 个百分点就要警惕。4.3 类别不平衡的处理方式如果猫有 1000 张、鸟只有 100 张模型会倾向于把什么都预测成猫。最简单的办法是在损失函数里给少数类更高权重class_counts [1000, 500, 100] weights 1.0 / torch.tensor(class_counts, dtypetorch.float) weights weights / weights.sum() criterion nn.CrossEntropyLoss(weightweights.to(device))权重取类别频率的倒数再归一化这是常见做法。更彻底的办法是对少数类做数据增强比如旋转、裁剪、颜色抖动把样本量补上来。5. 避坑与排查五个让模型精度暴跌的常见问题5.1 验证集准确率远高于训练集现象训练时准确率 70%验证时 95%。原因验证集太小或与训练集分布太接近比如验证集图片是从训练集里随机抽的且同一场景的图片被分到了两边。解决确保验证集和训练集来自不同时间段、不同设备或不同场景验证集每类至少 20 张且人工检查有没有重复图片。5.2 损失变成 NaN现象训练几个批次后损失突然变成 nan。原因学习率太大导致梯度爆炸或者输入数据没有归一化像素值范围差异过大。解决先把学习率降到 1e-4 试试检查Normalize是否加了如果自定义了网络检查有没有除零操作。加上梯度裁剪也能缓解torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)5.3 预测结果全是一个类别现象模型对任何输入都输出同一类。原因类别严重不平衡或者最后一层全连接没有正确初始化。解决检查各类样本数量加类别权重确认model.fc的输入维度与模型输出一致ResNet18 是 512别写成 256。5.4 训练速度慢到无法忍受现象一个 epoch 要跑半小时。原因num_workers0导致数据加载阻塞或者图片分辨率太大。解决Linux 下把num_workers设为 4 或 8Windows 下如果多进程报错就保持 0 但改用更小的图片尺寸。另外检查是否误用了 CPU 训练model.to(device)和images.to(device)都要确认。5.5 换了数据集后准确率骤降现象在 A 数据集上 95%换到 B 数据集只有 60%。原因两个数据集的图片风格、光照、背景差异太大预训练特征不匹配。解决在 B 数据集上做全网络微调不冻结卷积层学习率设小一点1e-4多跑几个轮次。如果 B 数据集很小先冻结训练几轮再解冻微调。6. 进阶技巧用混淆矩阵和预测置信度定位模型弱点训练完模型只看一个准确率数字远远不够。我习惯做两件事画混淆矩阵看模型在哪些类别之间混淆检查预测置信度分布看模型在哪些样本上犹豫不决。混淆矩阵用 sklearn 几行就能画from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) _, preds torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) sns.heatmap(cm, annotTrue, fmtd, xticklabelstrain_dataset.classes, yticklabelstrain_dataset.classes) plt.xlabel(Predicted) plt.ylabel(True) plt.show()如果猫和狗之间混淆严重说明模型学到的特征还不足以区分这两个类别。这时候可以针对性补充猫狗差异大的图片比如不同姿态、不同光照条件下的样本。另一个技巧是看预测置信度如果模型对错误样本的置信度也很高说明它过度自信需要加标签平滑label smoothingcriterion nn.CrossEntropyLoss(label_smoothing0.1)label_smoothing0.1会把真实标签的 one-hot 向量稍微平滑防止模型对某个类别输出极端概率通常能提升 1 到 2 个百分点的验证准确率。最后说一个我自己的习惯每次训练完一定从验证集里挑出预测错误的样本逐张看。十次里有八次你能一眼看出问题——要么标签标错了要么图片本身模糊到人都认不出。模型不背这个锅。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站