ARTICLE DETAIL

资讯详情

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

Python图像分类实战:从数据准备到模型推理的完整流程

Python图像分类实战:从数据准备到模型推理的完整流程 简介这份资源面向学习深度学习与图像分类的本科生、课程设计实践者及入门开发者围绕使用Python与PyTorch完成图像分类任务展开帮助读者理解如何依据图像特征将不同类别目标区分开替代人工视觉判读。压缩包共7个文件约239KB包含4个py源码文件、1个md说明、1个docx设计报告和1个license源码中涵盖线性分类器、多层感知机与卷积神经网络等典型模型实现设计报告则记录实验思路与结果分析便于对照代码理解算法原理。目前已有2091人学习下载说明该案例在课程设计与入门实践中具有一定参考价值。读者可借助完整源码与报告快速搭建实验框架梳理从数据到模型训练的分类流程并在此基础上修改网络结构或调参完成自己的图像分类课程设计任务。1. 从一份图像分类压缩包说起Python 到底能把它跑多快你拿到一个叫「基于Python实现图像分类.zip」的压缩包解压后大概率是几个 .py 文件、一个 requirements.txt、可能还有一两个示例图片文件夹。别急着双击运行先想清楚一件事图像分类这件事从 2012 年 AlexNet 到现在工程上的核心矛盾从来没变过——数据怎么进来、模型怎么选、结果怎么验证。这个压缩包能帮你省掉搭骨架的时间但省不掉理解流程的时间。这篇文章面向两类人一是刚学完 Python 基础语法、想找一个完整项目练手的入门者二是已经会写脚本、但没系统跑过图像分类全流程的开发者。我会按「数据准备 → 模型选型 → 训练与推理 → 避坑 → 进阶技巧」的顺序把一份典型图像分类项目从解压到跑通再到调优的路径拆开讲。你不需要 GPU 也能跟完前几章CPU 跑小数据集完全够用。2. 数据管道从一堆图片到模型能吃的张量2.1 图像分类数据集的标准目录结构与加载逻辑绝大多数图像分类项目的目录结构遵循同一个约定每个类别一个文件夹文件夹名就是类别标签。比如dataset/ ├── cats/ │ ├── cat_001.jpg │ └── cat_002.jpg ├── dogs/ │ ├── dog_001.jpg │ └── dog_002.jpg └── birds/ ├── bird_001.jpg └── bird_002.jpg这种结构不是随便定的PyTorch 的ImageFolder和 TensorFlow 的image_dataset_from_directory都默认按这个规则解析。如果你拿到的压缩包里数据目录不是这样第一步就是写个脚本重新组织。常见做法是用pathlib遍历按文件名前缀或原始目录名归类。from pathlib import Path import shutil raw_dir Path(raw_images) target_dir Path(dataset) target_dir.mkdir(exist_okTrue) # 假设原始文件名格式为 cat_001.jpg下划线前是类别 for img_path in raw_dir.glob(*.jpg): class_name img_path.stem.split(_)[0] class_dir target_dir / class_name class_dir.mkdir(exist_okTrue) shutil.copy(img_path, class_dir / img_path.name) print(f整理完成类别数{len(list(target_dir.iterdir()))})这段代码的逻辑很直接从文件名提取类别前缀创建对应子目录复制图片。参数上唯一需要注意的是split(_)[0]这个切分规则如果你的文件名格式不同比如用短横线或数字编号改这里就行。复制而不是移动是为了保留原始数据万一整理错了还能重来。2.2 用 torchvision 做训练/验证集划分与增强数据整理好之后不能直接全部丢给模型训练。标准做法是划分训练集和验证集比例通常是 8:2 或 7:3。torchvision.datasets.ImageFolder配合random_split可以几行搞定。import torch from torchvision import datasets, transforms from torch.utils.data import random_split, DataLoader # 训练时的增强随机裁剪、翻转、颜色抖动 train_transform transforms.Compose([ transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) # 验证时只做缩放和归一化不做随机增强 val_transform transforms.Compose([ transforms.Resize(256), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) full_dataset datasets.ImageFolder(dataset, transformtrain_transform) train_size int(0.8 * len(full_dataset)) val_size len(full_dataset) - train_size train_set, val_set random_split(full_dataset, [train_size, val_size]) # 验证集要覆盖 transform实际项目中建议用 Subset 配合独立 transform train_loader DataLoader(train_set, batch_size32, shuffleTrue, num_workers2) val_loader DataLoader(val_set, batch_size32, shuffleFalse, num_workers2)这里有几个参数值得展开说。RandomResizedCrop(224)里的 224 是 ImageNet 预训练模型的标准输入尺寸如果你用的模型不是 ImageNet 预训练的这个值可以改。Normalize的均值和标准差是 ImageNet 统计出来的用预训练权重时必须保持一致否则相当于给模型喂了分布偏移的数据。batch_size32是 CPU 训练的保守值GPU 显存够可以加到 64 或 128。num_workers在 Windows 上有时会出问题设成 0 最稳Linux 上可以设成 CPU 核数。注意random_split之后两个子集共享同一个 dataset 对象的 transform验证集也会被随机增强。正确做法是分别创建两个 ImageFolder或者用Subset配合自定义 wrapper 来切换 transform。这是新手最容易翻车的地方之一。3. 模型选型从 ResNet 到 Transformer 图像分类的落地判断3.1 小数据集优先用预训练 ResNet别一上来就 Transformer如果你手头的数据集只有几千张图甚至几百张直接用torchvision.models里的预训练 ResNet18 或 ResNet50 做迁移学习是最稳的路径。原因很简单Transformer 类模型包括 ViT需要大量数据才能训出有意义的注意力权重小数据集上从头训练基本等于随机输出。import torch.nn as nn from torchvision import models def build_model(num_classes, model_nameresnet18, freeze_backboneTrue): if model_name resnet18: model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) elif model_name resnet50: model models.resnet50(weightsmodels.ResNet50_Weights.DEFAULT) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) else: raise ValueError(f不支持的模型{model_name}) if freeze_backbone: for name, param in model.named_parameters(): if fc not in name: param.requires_grad False return model model build_model(num_classes3, model_nameresnet18, freeze_backboneTrue)freeze_backboneTrue的含义是冻结除最后全连接层之外的所有参数只训练分类头。这样做的好处是训练快、不容易过拟合适合数据量小的场景。等分类头收敛后可以解冻部分层做微调学习率调小一个数量级。num_classes必须和你的类别数一致这个值从len(full_dataset.classes)拿。3.2 训练循环里必须监控的三个指标训练循环本身不复杂但有几个指标不盯着出了问题你都不知道从哪查。import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model model.to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr1e-3) for epoch in range(10): model.train() running_loss 0.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() running_loss loss.item() _, predicted outputs.max(1) total labels.size(0) correct predicted.eq(labels).sum().item() train_acc correct / total avg_loss running_loss / len(train_loader) # 验证阶段 model.eval() val_correct 0 val_total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) outputs model(images) _, predicted outputs.max(1) val_total labels.size(0) val_correct predicted.eq(labels).sum().item() val_acc val_correct / val_total print(fEpoch {epoch1}: loss{avg_loss:.4f}, train_acc{train_acc:.4f}, val_acc{val_acc:.4f})三个必须看的指标训练 loss 是否持续下降、训练准确率是否上升、验证准确率是否跟得上。如果训练 loss 降但验证准确率不涨说明过拟合了该加数据增强或加 dropout。如果训练 loss 都不降检查学习率是不是太大或太小或者数据标签有没有问题。optimizer里用filter只传需要梯度的参数这是冻结骨干网络后的标准写法。4. 避坑与排查图像分类项目里最常见的五个翻车现场4.1 现象训练准确率一直卡在类别数分之一原因标签没对上或者数据加载时图片和标签错位。常见于自己重写 Dataset 类时索引搞混。解决先打印一个 batch 的图片和标签肉眼确认。用ImageFolder的话检查class_to_idx映射是否符合预期。4.2 现象验证集准确率比训练集还高原因验证集的 transform 太简单或者验证集里混入了训练集图片。random_split是随机划分如果数据集本身有重复图片可能两边都出现。解决去重后再划分或者用Subset时固定随机种子。验证集 transform 不要加随机增强。4.3 现象GPU 显存爆了报 CUDA out of memory原因batch_size 太大或者图片分辨率太高。224×224 的图batch_size32 在 4GB 显存上跑 ResNet50 基本会爆。解决降 batch_size 到 16 或 8或者换 ResNet18。也可以用梯度累积模拟大 batch。4.4 现象训练 loss 变成 NaN原因学习率太大或者输入数据没有归一化。如果图片像素值在 0-255 之间直接送进模型梯度会爆炸。解决检查ToTensor()和Normalize()是否都加了。学习率从 1e-4 开始试不要一上来就 1e-2。4.5 现象推理时单张图片预测结果和验证集不一致原因推理时的预处理和验证时不一致。比如验证用了CenterCrop(224)推理时直接Resize(224)图像内容分布不同。解决把验证集的 transform 单独抽成一个函数推理时复用同一个函数。5. 进阶技巧用混淆矩阵和单张推理把模型行为看清楚训练跑通只是第一步真正要判断模型能不能用得看它在哪些类别上犯错。混淆矩阵是最直接的工具几行 sklearn 就能出。from sklearn.metrics import confusion_matrix, classification_report import numpy as np model.eval() all_preds [] all_labels [] with torch.no_grad(): for images, labels in val_loader: images images.to(device) outputs model(images) _, predicted outputs.max(1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.numpy()) cm confusion_matrix(all_labels, all_preds) print(混淆矩阵) print(cm) print(\n分类报告) print(classification_report(all_labels, all_preds, target_namesfull_dataset.classes))混淆矩阵的对角线是正确预测数非对角线是错分。如果某一列数值特别大说明模型倾向于把其他类预测成这个类通常是类别不平衡导致的。分类报告里的 precision 和 recall 能进一步定位问题precision 低说明误报多recall 低说明漏报多。单张推理的代码也要固定下来方便部署时复用from PIL import Image def predict_single(image_path, model, transform, class_names, device): model.eval() image Image.open(image_path).convert(RGB) tensor transform(image).unsqueeze(0).to(device) with torch.no_grad(): output model(tensor) prob torch.softmax(output, dim1) conf, pred prob.max(1) return class_names[pred.item()], conf.item() # 使用验证集的 transform不要另写一套 label, confidence predict_single(test.jpg, model, val_transform, full_dataset.classes, device) print(f预测{label}置信度{confidence:.4f})这里的关键是val_transform必须和验证阶段完全一致包括 Resize 的尺寸、CenterCrop 的位置、Normalize 的参数。我自己的习惯是把这个 transform 定义在一个单独的配置模块里训练和推理都从那里导入避免手滑改了一处忘了另一处。还有一个实用技巧把预测置信度低于某个阈值比如 0.6的样本单独存下来人工看一眼。这些通常是模型真正拿不准的边界样本要么是标注错了要么是类别定义本身有歧义。我做过的一个项目里靠这个办法揪出了几十张标错的训练图重新标注后验证准确率直接涨了 4 个百分点。最后说一个我踩过的坑不要用训练集的 transform 去做推理。训练时的RandomResizedCrop和RandomHorizontalFlip会让同一张图每次预测结果都不一样你会以为模型不稳定其实是预处理在随机。推理和验证必须用确定性变换这个习惯从第一个项目就要养成。希望帮到你。本文还有配套的精品资源点击获取
返回列表