图像分类入门实战:从图像处理到卷积神经网络全流程解析
很多刚开始接触 AI 的同学面对计算机视觉、图像处理、神经网络、图像分类这一串名词时常常不知道该从哪里下手。网上的教程要么只讲理论要么直接贴一段完整代码让新手照抄结果环境没搭好、数据格式不理解、模型跑不起来很快就放弃了。这篇文章就是为这样的零基础读者准备的。我会从“计算机视觉到底在做什么”开始逐步拆解图像处理的基础操作、神经网络的基本原理、图像分类的核心思想再通过 LeNet-5 和 AlexNet 两个经典模型带你把一个完整的图像分类项目跑通。最后还会整理训练过程中常用的优化技巧和常见报错排查方法。学完这篇文章你会具备以下能力理解图像在计算机中的存储方式知道为什么要做灰度化、缩放、归一化。看懂卷积神经网络的基本结构知道卷积、池化、全连接层分别负责什么。用 PyTorch 实现 LeNet-5 和 AlexNet并在 MNIST 数据集上完成训练和验证。知道学习率、batch size、过拟合、数据增强等概念如何影响最终准确率。遇到常见报错时能按思路自己排查而不是到处复制别人的代码。如果你是第一次接触 AI 图像相关的项目这篇文章可以当作你的第一份完整学习笔记。1. 从零理解计算机视觉到底在做什么1.1 计算机视觉解决什么问题计算机视觉Computer Vision是让计算机从图像或视频中“看懂”信息的一门学科。这里说的“看懂”并不是像人类一样有意识而是通过算法从像素中提取出可计算的规律。举个例子人眼看到一张猫的照片能立刻判断出画面里有一只猫。计算机看到的却不是“猫”而是一个由数字组成的二维矩阵。每个像素点都有对应的数值彩色图像中每个像素还包含红、绿、蓝三个通道的数值。计算机视觉要做的就是从这些数字矩阵中找出特征再根据特征判断图像内容。常见的计算机视觉任务包括任务类型解决什么问题典型应用图像分类判断整张图像属于哪个类别判断图片中是猫还是狗目标检测找出图像中所有目标的位置和类别自动驾驶检测行人车辆图像分割对每个像素做类别预测医学影像中分割病灶区域图像生成根据条件生成新图像文生图、风格迁移人脸识别判断人脸身份门禁、支付、安防本文重点讲图像分类因为它是其他视觉任务的基础也是最适合入门的方向。1.2 图像处理、神经网络、深度学习、图像分类的关系很多初学者把这些概念混在一起其实它们之间的关系并不复杂。图像处理对图像本身做变换比如去噪、增强对比度、缩放、灰度化。它不一定会用到神经网络即使没有 AI传统的图像处理技术也一直在用。神经网络一种受生物神经元启发设计的数学模型可以通过大量数据自动学习输入与输出之间的映射关系。深度学习使用多层神经网络进行学习的方法统称卷积神经网络CNN就是深度学习中处理图像最常用的一类模型。图像分类一个具体的任务目标可以使用传统特征加分类器实现也可以使用深度学习模型实现。它们的关系可以这样理解图像处理是“数据预处理阶段”的工具帮助我们把原始图片整理成模型更容易学习的格式神经网络和深度学习是“学习阶段”的核心算法图像分类则是我们要完成的“任务”。在真实项目中这几个环节是串联起来的读取图像 → 预处理 → 送入神经网络 → 得到分类结果 → 评估与优化。2. 环境准备一次装好开发环境2.1 你需要准备哪些工具在开始写代码之前先确认自己的电脑环境。本文的示例代码以 Python 为基础使用 PyTorch 作为深度学习框架使用 OpenCV 和 torchvision 处理图像数据。建议的软件环境如下操作系统Windows 10/11、Ubuntu 20.04/22.04、macOS 均可。Python建议使用 3.8 或更高版本推荐通过 Anaconda 管理环境。PyTorchCPU 版本即可跑通本文示例有 NVIDIA 显卡的同学可以安装 CUDA 版本加速训练。OpenCV用于图像读取、显示、灰度化、缩放等基础操作。torchvision提供常用数据集、数据预处理方法和预训练模型。版本号变化比较快我这里不写死具体版本避免你安装时因为版本不匹配踩坑。你只需要在安装时选择当前最新稳定版即可。如果你使用的是 CUDA 版本 PyTorch请先确认自己的显卡驱动和 CUDA 版本支持情况。2.2 创建虚拟环境并安装依赖强烈建议不要直接在系统 Python 里安装这些库而是创建一个独立的虚拟环境。这样即使某个项目破坏了依赖也不会影响其他项目。打开终端或命令行执行以下命令# 创建虚拟环境名字可以随便取 conda create -n cv_learn python3.9 -y # 激活虚拟环境 conda activate cv_learn # 安装核心依赖 pip install torch torchvision opencv-python如果下载速度较慢可以临时使用国内镜像源pip install torch torchvision opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以运行下面这段代码验证环境import torch import torchvision import cv2 print(PyTorch 版本:, torch.__version__) print(torchvision 版本:, torchvision.__version__) print(OpenCV 版本:, cv2.__version__) print(CUDA 是否可用:, torch.cuda.is_available())如果能看到对应的版本号输出说明环境已经准备好了。CUDA 是否可用一行为False时表示当前使用 CPU 训练也能运行本文代码只是速度稍慢。3. 图像处理基础让电脑“看懂”像素3.1 图像在计算机中的表示方式灰度图像是一个二维矩阵每个元素表示一个像素的亮度取值范围通常是 0 到 2550 代表纯黑255 代表纯白。彩色图像则是一个三维数组形状为高 H × 宽 W × 通道数 C其中通道数通常是 3分别对应红色 R、绿色 G、蓝色 B。也就是说每一个像素点都由三个数值组合而成共同决定这个点在屏幕上的颜色。假设有一张大小为 28×28 的灰度图像它在 Python 中就是一个形状为(28, 28)的二维数组。如果是 28×28 的彩色图像则是形状为(28, 28, 3)的三维数组。在深度学习中模型通常要求输入形状是(C, H, W)也就是把通道放在第一个维度。这和我们平时用 OpenCV 读到的(H, W, C)正好相反所以经常需要做维度转换新手很容易在这里踩坑。3.2 使用 OpenCV 读取与显示图像先来看一个最简单的图像处理流程读取图像、显示图像、保存图像。import cv2 # 读取图像默认是彩色模式得到 BGR 顺序的三维数组 img cv2.imread(cat.jpg) # 检查是否读取成功 if img is None: print(图片读取失败请检查文件路径) exit() # 查看图像基本信息 h, w, c img.shape print(f图像高度: {h}, 宽度: {w}, 通道数: {c}) # 显示图像窗口名为 demo cv2.imshow(demo, img) # 等待键盘输入参数 0 表示等待任意按键后继续 cv2.waitKey(0) cv2.destroyAllWindows() # 保存图像 cv2.imwrite(cat_copy.jpg, img)这里要注意OpenCV 默认使用 BGR 通道顺序而 Matplotlib 或其他图像库通常使用 RGB 顺序。如果你用 OpenCV 读图后直接用 Matplotlib 显示颜色会偏蓝偏红这是一种非常常见的现象。3.3 灰度化、缩放与归一化图像分类模型通常不需要原始尺寸和原始色彩而是需要统一尺寸、统一数值范围的输入。因此预处理是训练前必不可少的环节。import cv2 import numpy as np img cv2.imread(cat.jpg) # 转为灰度图 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) print(灰度图 shape:, gray.shape) # 缩放到 32x32这是 LeNet-5 的经典输入尺寸 resized cv2.resize(gray, (32, 32)) print(缩放后 shape:, resized.shape) # 归一化将 0~255 的像素值映射到 0~1 范围 normalized resized.astype(np.float32) / 255.0 print(像素值范围:, normalized.min(), normalized.max())为什么要做归一化因为神经网络在训练时使用的梯度下降算法对输入数值范围比较敏感。如果输入数值太大可能会导致梯度爆炸或模型收敛缓慢。把像素值压缩到 0 到 1 之间可以显著提高训练稳定性。3.4 为什么数据预处理对训练很重要很多新手觉得预处理就是“把图片变小一点”其实它的作用远不止如此。预处理主要解决以下几个问题统一输入尺寸神经网络的全连接层要求输入维度固定不同尺寸的图片无法直接送入同一个模型。减少无关信息灰度化可以降低计算量同时对很多分类任务来说颜色并不是关键特征。提高数值稳定性归一化让数据分布更集中有利于梯度下降算法平稳运行。扩充数据多样性通过随机裁剪、旋转、翻转等操作可以让模型见过更多样的数据减少过拟合。在后面的 PyTorch 实战中我们会用torchvision.transforms来完成这些预处理它比手动使用 OpenCV 更方便而且可以直接嵌入数据加载流程。4. 神经网络与卷积神经网络基础4.1 从神经元到前馈神经网络神经网络的基本计算单元是神经元。一个神经元接收多个输入每个输入都有一个权重神经元内部会计算输入与权重的加权和再加上一个偏置然后通过激活函数输出结果。多个神经元按层组织就构成了神经网络。输入层接收原始数据隐藏层提取特征输出层给出预测结果。数据从输入层逐层流向输出层中间没有循环连接这种网络叫做前馈神经网络。全连接网络是前馈网络中最简单的一种它的特点是当前层的每个神经元都与上一层的所有神经元相连。对于 32×32 的图像如果把它拉直成一个长度为 1024 的向量全连接网络的参数量会非常大而且会忽略图像的局部空间结构。因此在面对图像任务时我们通常使用卷积神经网络。4.2 为什么图像任务常用卷积神经网络卷积神经网络CNN的核心优势是能够自动学习图像的局部特征。在一张猫的图片中边缘、纹理、眼睛、耳朵这些特征是分层组织的。低层卷积核可以检测边缘和颜色变化中层卷积核可以检测纹理和局部形状高层卷积核可以检测更复杂的目标部件。CNN 通过层层卷积自动完成特征的提取和组合不需要人工设计特征。与全连接网络相比CNN 有两个重要特点局部连接每个卷积核只观察输入的一个局部区域感受野逐步扩大。参数共享同一个卷积核在图像的不同位置滑动时权重是相同的这大大减少了参数量。正是这两个特点让 CNN 在图像分类任务中既高效又稳定。4.3 卷积、池化、全连接三件套一个典型的图像分类 CNN 通常由以下结构堆叠而成卷积层使用卷积核在输入图像上滑动提取局部特征并通过激活函数引入非线性。池化层对特征图进行下采样保留主要信息的同时减少计算量常见的是最大池化和平均池化。全连接层将池化后的特征图展平成一维向量通过全连接层映射到类别得分。PyTorch 中最常用的卷积层是nn.Conv2d。假设输入是单通道灰度图我们想要输出 6 个通道使用 5×5 的卷积核代码非常简单import torch.nn as nn conv_layer nn.Conv2d( in_channels1, # 输入通道数灰度图是 1彩色图是 3 out_channels6, # 输出通道数也就是本层使用的卷积核数量 kernel_size5, # 卷积核大小 stride1, # 滑动的步长 padding0 # 边缘填充可以在保持尺寸时使用 )池化层的写法类似pool_layer nn.MaxPool2d(kernel_size2, stride2)池化操作会把每个 2×2 的小区域中最大值保留下来因此特征图的宽高会缩小为原来的一半。理解这三个基本组件后我们已经具备阅读 LeNet-5 和 AlexNet 结构图的足够基础了。5. 图像分类实战从 LeNet-5 到 AlexNet5.1 LeNet-5 网络结构与代码实现LeNet-5 是 Yann LeCun 于 1998 年提出的卷积神经网络最初用于手写数字识别是深度学习历史中最经典的模型之一。它的输入是 32×32 的灰度图像整体结构如下层名称具体操作输出尺寸输入层原图1×32×32C1 卷积层6 个 5×5 卷积核6×28×28S2 池化层2×2 最大池化6×14×14C3 卷积层16 个 5×5 卷积核16×10×10S4 池化层2×2 最大池化16×5×5C5 卷积层120 个 5×5 卷积核120×1×1F6 全连接层84 个神经元84输出层10 个类别10注意C5 卷积层的输出形状是 120×1×1实际上可以把它看作一个 120 维的特征向量然后再送入全连接层。下面是 PyTorch 实现import torch.nn as nn import torch.nn.functional as F class LeNet5(nn.Module): def __init__(self, num_classes10): super(LeNet5, self).__init__() self.conv1 nn.Conv2d(1, 6, kernel_size5, stride1, padding0) self.pool1 nn.MaxPool2d(kernel_size2, stride2) self.conv2 nn.Conv2d(6, 16, kernel_size5, stride1, padding0) self.pool2 nn.MaxPool2d(kernel_size2, stride2) self.conv3 nn.Conv2d(16, 120, kernel_size5, stride1, padding0) self.fc1 nn.Linear(120, 84) self.fc2 nn.Linear(84, num_classes) def forward(self, x): x self.pool1(F.relu(self.conv1(x))) x self.pool2(F.relu(self.conv2(x))) x F.relu(self.conv3(x)) # 将特征图展平成一维向量 x x.view(x.size(0), -1) x F.relu(self.fc1(x)) x self.fc2(x) return x代码里的view(x.size(0), -1)表示保留 batch 维度把每个样本的特征图拉直。这是全连接层前必须做的操作。5.2 AlexNet 网络结构与代码实现AlexNet 在 2012 年 ImageNet 图像分类竞赛中取得第一名大幅刷新了准确率被认为是深度学习热潮的重要推动者。它的关键贡献包括使用 ReLU 激活函数解决梯度消失问题、使用 Dropout 降低过拟合、使用数据增强扩充训练样本。AlexNet 的原始结构包含 5 个卷积层和 3 个全连接层输入是 227×227 的彩色图像。由于原模型是在大规模数据集上设计的直接拿来做小规模分类任务会显得过重。在本文的入门练习中我给出一个简化版的 AlexNet它保留了原始模型的核心思想但参数量更少更容易在 CPU 上跑通import torch.nn as nn import torch.nn.functional as F class SimpleAlexNet(nn.Module): def __init__(self, num_classes10): super(SimpleAlexNet, self).__init__() self.features nn.Sequential( nn.Conv2d(1, 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.Conv2d(128, 128, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.Conv2d(128, 64, kernel_size3, padding1), nn.ReLU(inplaceTrue), nn.MaxPool2d(kernel_size2, stride2), ) self.classifier nn.Sequential( nn.Dropout(p0.5), nn.Linear(64 * 4 * 4, 256), nn.ReLU(inplaceTrue), nn.Dropout(p0.5), nn.Linear(256, num_classes), ) def forward(self, x): x self.features(x) x x.view(x.size(0), -1) x self.classifier(x) return x这个简化版继承了 AlexNet 最核心的设计思路多个小卷积核卷积层堆叠、ReLU 激活、最大池化、Dropout 正则化。对 MNIST 这种比较简单的小数据集来说这种结构已经有足够的学习能力。5.3 完整训练代码为了让你能直接体验完整流程我用 MNIST 手写数字数据集作为例子。MNIST 包含 0 到 9 共 10 类手写数字灰度图每张大小为 28×28是图像分类最经典的入门数据集。首先加载数据并做预处理。这里使用torchvision.transforms把图像缩放到 32×32转成张量再做标准化import torch import torch.nn as nn import torch.optim as optim import torchvision import torchvision.transforms as transforms from torch.utils.data import DataLoader transform transforms.Compose([ transforms.Resize((32, 32)), transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) ]) 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_size64, shuffleFalse)其中Normalize((0.5,), (0.5,))表示把像素值从 0~1 范围转换到 -1~1 范围公式是(像素值 - 0.5) / 0.5。这样可以让数据均值接近 0更有利于模型训练。接下来定义训练函数def train_one_epoch(model, train_loader, criterion, optimizer, device): 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 torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() avg_loss running_loss / len(train_loader) accuracy correct / total return avg_loss, accuracy def evaluate(model, test_loader, criterion, device): model.eval() running_loss 0.0 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) loss criterion(outputs, labels) running_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() avg_loss running_loss / len(test_loader) accuracy correct / total return avg_loss, accuracy然后开始训练device torch.device(cuda if torch.cuda.is_available() else cpu) model LeNet5(num_classes10).to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) epochs 10 for epoch in range(1, epochs 1): train_loss, train_acc train_one_epoch( model, train_loader, criterion, optimizer, device ) test_loss, test_acc evaluate(model, test_loader, criterion, device) print(fEpoch {epoch:02d} | fTrain Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | fTest Loss: {test_loss:.4f}, Test Acc: {test_acc:.4f})如果你把模型换成SimpleAlexNet()训练流程完全一样只需要修改实例化模型的那一行代码。5.4 运行与验证运行上面的代码后第一轮输出可能类似Epoch 01 | Train Loss: 0.3215, Train Acc: 0.9021 | Test Loss: 0.1287, Test Acc: 0.9602随着训练轮数增加训练准确率和测试准确率都会逐步提升。到第 10 轮MNIST 上通常可以达到 99% 左右的测试准确率。手写数字识别相对简单这也是它适合作为入门数据集的原因。训练完成后可以保存模型权重torch.save(model.state_dict(), model.pth)之后使用模型时只需要重新加载模型结构和权重model LeNet5(num_classes10) model.load_state_dict(torch.load(model.pth, map_locationcpu)) model.eval()5.5 LeNet-5 和 AlexNet 怎么选很多初学者会纠结“到底用哪个模型”。这里给一个简单的选型建议如果数据集小、图像简单例如 MNIST、Fashion-MNISTLeNet-5 足够训练速度快也不容易过拟合。如果数据集相对复杂例如 CIFAR-10、自定义彩色图片可以考虑简化版 AlexNet 或 ResNet。如果你的目标是学习经典模型结构建议先手写 LeNet-5再手写 AlexNet不要一开始就使用封装好的预训练模型否则会错过很多细节。在真实项目中大家很少会从零训练 AlexNet 这种大规模模型更多是使用 ImageNet 上预训练好的模型做微调也就是迁移学习。后面我会单独讲这个思路。6. 训练优化技巧提升分类准确率6.1 学习率与优化器学习率是深度学习中最需要关注的超参数之一。它决定了模型参数每次更新的步长。学习率太大损失函数会在最优解附近震荡甚至发散学习率太小训练收敛速度会非常慢。PyTorch 中常用的优化器有 SGD、Adam、AdamW。入门阶段推荐使用 Adam因为它对学习率的敏感度较低默认参数在很多任务上都能工作得不错。optimizer optim.Adam(model.parameters(), lr0.001)如果你希望训练过程更加稳定也可以使用学习率衰减即随着训练轮数增加逐步降低学习率scheduler optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.1)每个 epoch 结束时调用scheduler.step()学习率就会每 5 轮缩小为原来的 0.1 倍。6.2 过拟合与正则化过拟合是指模型在训练集上表现很好但在测试集上表现较差。它的本质是模型“死记硬背”了训练数据中的噪声和细节而没有学到真正的通用规律。判断是否过拟合的一个简单方法是观察每一轮的训练准确率和测试准确率差距。如果训练准确率很高而测试准确率长期不增长甚至下降就说明模型已经过拟合了。常见的缓解方法包括增加数据量收集更多样本或者使用数据增强。降低模型复杂度减少卷积核数量、减少层数。添加 Dropout随机丢弃一部分神经元减少神经元之间的耦合。使用权重衰减在损失函数中加入参数平方和惩罚项限制权重过大。早停法当验证集准确率不再提升时停止训练。PyTorch 中设置权重衰减非常简单optimizer optim.Adam(model.parameters(), lr0.001, weight_decay1e-4)6.3 数据增强数据增强是一种从已有数据中生成更多训练样本的技巧。通过在训练时随机改变图像的尺寸、位置、角度、亮度等模型可以学习到更多不随这些变化而改变的稳定特征。在 torchvision 中数据增强可以直接写进 transform 里transform_train transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,)) ])注意数据增强通常只用于训练集验证集和测试集应该使用固定的预处理流程避免评估结果受到随机变化的影响。对于更复杂的彩色图像数据集数据增强的重要会更加明显。很多实时分类项目仅靠数据增强就能把准确率提升一个档次。6.4 迁移学习思路当你的数据集比较小比如只有几千张图片从零训练一个大模型很容易过拟合。这时可以使用迁移学习。迁移学习的思路是先在一个大规模数据集如 ImageNet上训练好一个模型然后把这个模型已经学会的特征提取能力迁移到自己的任务上。你只需要把模型的最后一层全连接层替换成自己数据集的类别数量然后对整个模型或部分层进行微调。PyTorch 中使用预训练模型非常方便import torchvision.models as models # 加载预训练模型weights 参数表示使用官方预训练权重 model models.resnet18(weightsmodels.ResNet18_Weights.DEFAULT) # 替换最后一层全连接层适配自己的分类任务 num_classes 10 model.fc nn.Linear(model.fc.in_features, num_classes)如果你希望整个模型都参与微调可以定义较小的学习率如果希望只训练最后的分类头可以冻结前面的特征提取层for param in model.parameters(): param.requires_grad False for param in model.fc.parameters(): param.requires_grad True迁移学习是实际工程中使用频率非常高的方法尤其是在工业缺陷检测、医学影像分类等样本量有限的场景中。7. 常见问题与排查思路新手在跑图像分类项目时遇到报错是很正常的事情。这里整理了几个高频问题你可以对照排查。问题现象常见原因解决思路图片读取失败文件路径错误或文件名包含中文使用绝对路径确认避免路径中出现中文和空格训练时 loss 一直是 nan学习率过大或数据没有归一化调低学习率检查像素值是否在合理范围内PyTorch 报维度不匹配模型输入尺寸和实际图像尺寸不一致全连接层前没有展平打印每层输出 shape确认 transform 中 Resize 的尺寸训练准确率很高测试准确率很低过拟合增加数据增强、Dropout、权重衰减减少模型复杂度下载 MNIST 数据集超时网络连接不稳定手动下载数据集文件并放到./data目录使用 GPU 训练时显存不足batch size 太大或模型过大调小 batch size降低图像输入尺寸Adam 优化器效果不明显学习率设置不合理尝试从 0.001 开始并在训练过程中使用学习率衰减如果遇到其他报错建议按以下顺序排查先看完整报错信息不要只看最后一行。检查是不是文件路径或数据格式问题。打印张量的shape和模型要求的输入尺寸对比。检查是不是版本兼容问题比如 PyTorch 和 torchvision 版本不匹配。如果是训练效果不好先看训练集准确率是否正常再分析测试集的问题。8. 最佳实践与工程建议8.1 代码组织与可复现性在实际项目中不建议把训练代码全部写在一个文件里。即使只是个人学习也建议按照以下结构组织project/ ├── data/ # 存放数据集 ├── models/ # 保存训练好的模型 ├── src/ │ ├── dataset.py # 数据加载与预处理 │ ├── model.py # 模型定义 │ ├── train.py # 训练脚本 │ └── utils.py # 公共工具函数 └── config.py # 超参数配置训练实验要保证可复现否则同一个脚本跑两次结果差异很大很难判断优化是否有效。可以在训练前固定随机种子import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed()固定随机种子后大多数情况下训练结果可以复现。不过 GPU 运算本身有一定随机性完全复现并不总是可能但固定的随机种子已经能很大程度减少实验随意性。8.2 训练过程的监控与记录很多同学训练完一轮后只看准确率这是不够的。更科学的方式是记录每一轮的训练损失、验证损失、训练准确率、验证准确率然后绘制曲线。通过观察损失曲线你可以清楚判断模型是欠拟合、过拟合还是学习率设置不合理。如果不想使用 TensorBoard也可以用简单的方式记录日志例如保存到 CSV 文件后面用 Matplotlib 画图。8.3 安全与部署提醒在实际项目中应用图像分类模型时有几个问题需要提前考虑数据权限使用公开数据集时注意遵守数据集许可使用业务数据时确认数据脱敏和处理合规。模型验证模型上线前必须在独立的测试集上做评估不能只用训练时见过的数据。异常输入真实场景中会出现模糊、光照变化、遮挡等复杂情况需要增加针对性的测试样本。灰度发布如果模型要替换线上旧模型建议先小流量验证对比新旧模型的指标后再全量切换。日志监控模型上线后要记录预测分布变化及时发现数据漂移等问题。这些是工程中经常被忽略但非常关键的部分。如果只是学习练手可以暂时不用考虑但如果要做真正的项目一定要尽早养成好习惯。8.4 下一步学习路线当你把 LeNet-5 和 AlexNet 在 MNIST 上跑通之后可以按照下面的路径继续深入学习尝试新的数据集用 CIFAR-10 替换 MNIST这时你就需要处理彩色图像的三通道输入并修改模型第一个卷积层的in_channels3。学习更深的模型熟悉 ResNet 的残差连接思想理解为什么层数加深之后模型仍然可以稳定训练。学习目标检测从 YOLO 系列入手理解目标检测和图像分类的区别。学习图像分割适合对医学影像、遥感图像感兴趣的同学。学习模型部署使用 ONNX 导出模型再用 ONNX Runtime 或 TensorRT 部署到服务端或边缘设备。计算机视觉的知识体系非常庞大但图像分类是绕不开的基础。只要把本文涉及的概念、代码和调参思路吃透后面学习任何视觉任务都会轻松很多。现在你应该已经具备了从图像处理到神经网络再到图像分类的完整知识链。接下来建议不要再花大量时间看教程了直接打开电脑把上面的代码从头到尾跑一遍。遇到报错就对照第 7 节排查跑通了再尝试修改模型结构、调整超参数、加上数据增强。把准确率一点点往上调的过程就是你真正入门深度学习的过程。