ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PyTorch从零实现LeNet-5:CIFAR-10图像分类实战全攻略

PyTorch从零实现LeNet-5:CIFAR-10图像分类实战全攻略 计算机视觉是人工智能中最贴近真实世界的方向之一也是很多零基础同学想入门深度学习的首选入口。这个方向看起来内容很多图像处理、神经网络、图像分类、训练优化还有 LeNet-5、AlexNet 这些经典模型。真正难的地方在于这些知识点如果不串起来很容易陷入“看了很多概念但不知道怎么跑一个完整项目”的困境。这篇文章会沿着一条完整链路展开先理解图像在计算机里如何表示再搞清楚神经网络为什么能识别图像然后用 PyTorch 从零实现一个 LeNet-5 网络在 CIFAR-10 上完成图像分类最后补上训练优化技巧、经典模型演进和常见问题排查。零基础读者按顺序学完后可以对深度学习图像分类有一个完整、可复现的认知。1. 计算机视觉入门第一步理解图像如何变成计算机能处理的数据1.1 图像的本质像素、通道与数据形状计算机视觉处理的对象是图像而图像在计算机里并不是一张“图片”而是一个多维数组。理解这一点后面所有模型和代码才有一个稳固的基础。一张灰度图像可以看作一个二维矩阵矩阵中每个数值表示该位置的亮度取值范围通常是 0 到 255。0 表示黑色255 表示白色中间值表示不同程度的灰色。彩色图像通常使用 RGB 三通道表示也就是三个二维矩阵叠加在一起分别表示红色、绿色、蓝色三个通道的亮度。三个通道同时取值组合出人眼看到的颜色。在 Python 中图像数据最常见的表示方式有两种NumPy 数组形状为(H, W, C)其中 H 是高度W 是宽度C 是通道数。OpenCV 读取出来的图像就是这个顺序。PyTorch 张量形状为(C, H, W)。PyTorch 中的卷积层默认接受这种通道优先的布局。这种形状差异是零基础入门时最容易踩的坑。例如 OpenCV 读取一张彩色图片返回的形状是(height, width, 3)而直接把它塞给 PyTorch 模型前需要转换成(3, height, width)的格式。下面用一段代码演示图像的基本读取、形状查看和像素访问。import cv2 import numpy as np # 读取图像OpenCV 返回的通道顺序是 BGR image cv2.imread(cat.jpg) print(图像形状:, image.shape) # (H, W, 3) print(第一个像素点的值:, image[0, 0]) # 转换为 RGB 通道顺序 image_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 获取高度、宽度、通道数 h, w, c image.shape print(f高度: {h}, 宽度: {w}, 通道数: {c})这段代码说明了一个常见事实图像就是矩阵图像处理本质上是在做矩阵运算。后续的缩放、裁剪、归一化等操作都是在调整矩阵的取值或形状。1.2 图像处理基础操作灰度、缩放与归一化在把图像交给神经网络之前通常需要做一些基础预处理。这些操作不是“为了做而做”而是有明确的现实原因。灰度化把彩色图像变成单通道灰度图目的是减少计算量。只有在颜色本身是重要特征时才保留 RGB 通道。缩放不同图片尺寸不同而神经网络的输入尺寸通常是固定的。例如 LeNet-5 原始输入是 32x32AlexNet 的输入是 224x224。图片进入网络前必须缩放到目标尺寸。归一化把像素值从 0 到 255 的范围缩放到某个固定范围例如 0 到 1或者 -1 到 1。这样做的原因是模型内部大量使用梯度计算像素值过大或分布不均会导致梯度不稳定训练难以收敛。下面以 OpenCV 为例演示常用的预处理操作。import cv2 image cv2.imread(cat.jpg) image_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # 缩放将图像统一调整为 32x32 resized cv2.resize(image_rgb, (32, 32)) # 转灰度 gray cv2.cvtColor(image_rgb, cv2.COLOR_RGB2GRAY) print(灰度图形状:, gray.shape) # 归一化将像素值从 0-255 缩放到 0-1 normalized image_rgb / 255.0 print(归一化后的像素范围:, normalized.min(), -, normalized.max())在真正的训练代码中归一化通常使用transforms.Normalize它不仅缩放到 0-1 之间还会把每个通道进一步调整为均值为 0、标准差为 1 的分布。这样能让梯度更平稳。注意OpenCV 读取的彩色图像通道顺序是 BGR不是 RGB。如果直接用 OpenCV 读取的图像做可视化不进行cvtColor转换会出现颜色偏蓝或偏红的现象。这个问题在调试图像分类模型时经常出现。1.3 从像素到特征为什么直接使用全连接网络不够好图像由像素组成一个很自然的问题是能不能把所有像素值直接拉平作为全连接神经网络的输入理论上可以但实践效果很差。假设一张彩色图片尺寸是 32x32三个通道总共有 32x32x3 3072 个像素。如果把像素值全部拉平第一层全连接网络有 3072 个输入。如果第一层有 1024 个神经元这一层的参数数量就是 3072x1024 3145728约 315 万。对于一张小尺寸的 32x32 图片来说参数已经很多。如果输入是 224x224 的彩色图片参数数量会更惊人训练时数据量不够就会严重过拟合。更重要的是图像特征具有明显的局部性和平移不变性一只猫的耳朵出现在左上角还是右下角都应该被识别为“耳朵”这一局部特征。全连接网络把每个像素独立对待并没有利用这种局部结构。卷积神经网络通过卷积核在图像上滑动天然只关注局部区域并且同一个卷积核会在不同位置共享参数。因此卷积神经网络更适合图像分类。这一节引出的关键结论是图像处理做的是“数据整理”卷积网络做的是“特征提取”。理解了这一点再进入神经网络部分就不会觉得知识点散。2. 神经网络和卷积神经网络的核心机制2.1 神经元、激活函数与图像分类输出神经网络的基本单元是神经元。一个神经元做的事情可以简化为两步先把输入乘以权重并累加再经过一个激活函数输出。用公式表达就是z w1*x1 w2*x2 ... wn*xn b a activation(z)其中x是输入w是权重b是偏置activation是激活函数。如果没有激活函数无论网络有多少层最终都只是线性组合表达能力有限。因此激活函数的作用是给网络引入非线性。常见的激活函数包括 Sigmoid、Tanh 和 ReLU。现代卷积网络中最常用的是 ReLUf(x) max(0, x)。它计算简单能缓解梯度消失问题。图像分类任务的输出通常是每个类别的概率值。以 CIFAR-10 数据集为例共有 10 个类别飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车。网络最后一层会输出 10 个值然后通过 Softmax 函数把这 10 个值转换成概率分布所有概率之和为 1。2.2 损失函数与反向传播模型如何学会识别网络输出的概率分布与真实标签之间是有差距的。衡量这个差距的函数叫损失函数图像分类最常用的是交叉熵损失。对于一张真实类别为猫的图片如果网络输出的“猫”的概率是 0.8那么损失会比较小如果输出“猫”的概率只有 0.2损失会比较大。模型训练的目标就是让这个损失尽可能小。要减小损失需要知道每个权重应该往哪个方向调整。这个计算过程依赖于反向传播算法它从输出层的损失开始应用链式法则逐层计算每个参数的梯度。然后优化器使用梯度更新参数。整个训练循环可以概括为输入一批图片。前向传播得到预测结果。计算损失。反向传播计算梯度。使用优化器更新权重。这个过程会反复进行直到损失下降到合理水平。2.3 卷积层、池化层与全连接层一个典型的卷积神经网络由三类层组成卷积层、池化层、全连接层。卷积层是特征提取的核心。它使用一组卷积核在输入图像上滑动每个卷积核可以看作是一个小窗口窗口中的像素值与卷积核权重相乘后求和得到特征图上的一个值。通过多个卷积核可以提取多种不同的特征例如边缘、纹理、颜色块等。卷积层有两个关键超参数Padding在输入边缘填充零控制输出尺寸同时利用边缘信息。Stride卷积核每次滑动的步长步长越大输出尺寸越小计算量越小。池化层通常跟在卷积层后面作用是降低特征图的空间尺寸保留主要特征同时减少计算量。最常用的是最大池化取窗口内的最大值。池化没有可学习参数但它能提供一定的平移不变性。全连接层位于网络末尾负责把前面提取到的高层特征映射到具体的类别概率。例如 LeNet-5 最后的三层全连接层就是把特征向量映射到 10 个类别得分。2.4 为什么卷积神经网络适合图像分类卷积神经网络相比全连接网络的核心优势来自两个特性。第一个是局部连接。每个卷积核只关注输入的一个局部窗口而不是整张图片。这样更符合图像特征的空间局部性也能大幅减少参数数量。第二个是参数共享。同一个卷积核会在整张特征图上滑动也就是说同一组参数被整个图像区域共用。无论目标出现在图像的哪个位置相同的卷积核都可以检测到相同的局部特征因此具有一定的平移不变性。此外通过堆叠多个卷积层低层网络能学习到边缘、颜色等低级特征高层网络可以组合出更有语义的特征例如眼睛、轮子、窗户等。这种从局部到整体、从低级到高级的特征表示是 CNN 在图像分类任务上表现出色的根本原因。3. 环境准备把学习环境搭到能跑模型为止3.1 Python 虚拟环境与依赖安装零基础入门不建议在系统自带的 Python 环境里直接安装一堆包那样容易造成版本冲突。推荐为这个项目创建一个独立的 Python 虚拟环境。在终端中执行以下命令python -m venv ai-cv-env source ai-cv-env/bin/activate pip install numpy opencv-python matplotlib torch torchvision如果你的操作系统是 Windows激活命令略有不同ai-cv-env\Scripts\activate安装完成后可以检查主要依赖的版本确认没有安装错误。import torch import torchvision import cv2 import numpy as np print(PyTorch 版本:, torch.__version__) print(Torchvision 版本:, torchvision.__version__) print(OpenCV 版本:, cv2.__version__) print(NumPy 版本:, np.__version__)如果这些版本都能正常打印说明基础环境已经准备好。注意PyTorch 的安装命令会因系统是否支持 CUDA 而不同。如果原始项目没有明确指定 CUDA 版本建议先使用 CPU 版本跑通项目再考虑 GPU 加速。学习阶段 CPU 完全足够。3.2 确认训练设备CPU 还是 GPU深度学习中训练速度主要受设备性能影响。虽然零基础入门阶段用 CPU 也可以训练 CIFAR-10 这样的数据集但训练时间会明显变长。因此需要确认当前环境是否能使用 GPU。import torch device torch.device(cuda if torch.cuda.is_available() else cpu) print(训练设备:, device) if torch.cuda.is_available(): print(GPU 名称:, torch.cuda.get_device_name(0))如果输出cuda那么之后训练代码可以方便地把模型和数据放到 GPU 上。如果输出cpu也不影响学习只是训练速度慢一些。3.3 项目目录结构设计一个清晰的项目目录能让后续调试和维护更高效。这里给出一个适合入门项目的目录结构ai-cv-project/ ├── data/ # 数据集存放目录 ├── models/ │ └── lenet5.py # 模型定义 ├── train.py # 训练脚本 ├── predict.py # 预测脚本 └── utils.py # 工具函数在本文示例中为了保持代码可读性会直接把模型定义和训练逻辑放在一段代码里。实际项目开发时建议按模块拆分避免一个文件太长。区分学习环境和生产环境很重要。学习环境的目的是尽量简化操作快速看到模型训练过程生产环境还需要考虑数据校验、日志记录、模型版本管理、分布式训练、线上监控、自动回滚等问题。本文以学习环境为主但会逐步补充生产环境需要的注意事项。4. 用 PyTorch 实现 LeNet-5 并完成 CIFAR-10 图像分类4.1 数据集准备加载 CIFAR-10 并做预处理CIFAR-10 是一个经典的图像分类数据集包含 10 个类别每张图片尺寸为 32x32 的彩色图像。训练集有 5 万张图片测试集有 1 万张图片。这个数据集非常适合零基础入门因为图片尺寸小训练速度快同时又是彩色图像能真实反映图像分类任务的复杂度。使用 torchvision 可以方便地下载和加载 CIFAR-10 数据集。代码如下import torch import torchvision import torchvision.transforms as transforms transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) trainset torchvision.datasets.CIFAR10( root./data, trainTrue, downloadTrue, transformtransform ) trainloader torch.utils.data.DataLoader( trainset, batch_size64, shuffleTrue, num_workers2 ) testset torchvision.datasets.CIFAR10( root./data, trainFalse, downloadTrue, transformtransform ) testloader torch.utils.data.DataLoader( testset, batch_size64, shuffleFalse, num_workers2 ) classes (plane, car, bird, cat, deer, dog, frog, horse, ship, truck)这里transform做了两件事。ToTensor()会把 PIL 图像转换为 PyTorch 张量并将像素值从 0 到 255 缩放到 0 到 1。Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))会把每个通道的数据从[0, 1]调整到[-1, 1]公式是(x - 0.5) / 0.5。这样做能让输入数据的分布更稳定有助于模型收敛。batch_size64表示每次输入 64 张图片。shuffleTrue表示每个 epoch 开始前打乱训练数据顺序防止模型记住固定顺序。4.2 LeNet-5 网络结构实现LeNet-5 是经典的卷积神经网络最早用于手写数字识别结构简单特别适合入门。原始 LeNet-5 接受 32x32 大小的灰度图像这里为了处理 CIFAR-10 的 RGB 彩色图像将输入通道改为 3最后全连接输出层改为 10。网络结构如下第一个卷积层输入 3 通道输出 6 个特征图卷积核大小 5x5。第一个池化层平均池化窗口 2x2步长 2。第二个卷积层输入 6 通道输出 16 个特征图卷积核大小 5x5。第二个池化层平均池化窗口 2x2步长 2。三个全连接层输出维度分别是 120、84、10。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(3, 6, kernel_size5) self.pool1 nn.AvgPool2d(kernel_size2, stride2) self.conv2 nn.Conv2d(6, 16, kernel_size5) self.pool2 nn.AvgPool2d(kernel_size2, stride2) self.fc1 nn.Linear(16 * 5 * 5, 120) self.fc2 nn.Linear(120, 84) self.fc3 nn.Linear(84, num_classes) def forward(self, x): x F.relu(self.conv1(x)) x self.pool1(x) x F.relu(self.conv2(x)) x self.pool2(x) x x.view(x.size(0), -1) x F.relu(self.fc1(x)) x F.relu(self.fc2(x)) x self.fc3(x) return x计算一下特征图的尺寸变化32x32 输入经过 5x5 卷积且没有 padding得到 28x28平均池化后变成 14x14。第二个卷积层输入 14x14经过 5x5 卷积得到 10x10池化后变成 5x5。因此第二个卷积层输出的特征总数是 16x5x5400对应fc1的输入维度。原始 LeNet-5 使用 Sigmoid 或 Tanh 激活函数这里改用 ReLU主要是为了训练更稳定、收敛更快。4.3 训练函数和主循环有了模型和数据接下来编写训练循环。训练中需要用到的组件有三个损失函数、优化器、学习率调度器。import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader device torch.device(cuda if torch.cuda.is_available() else cpu) model LeNet5(num_classes10).to(device) criterion nn.CrossEntropyLoss() optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9) scheduler optim.lr_scheduler.StepLR(optimizer, step_size5, gamma0.5) num_epochs 10 for epoch in range(num_epochs): running_loss 0.0 model.train() for i, (inputs, labels) in enumerate(trainloader): inputs, labels inputs.to(device), labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() running_loss loss.item() if (i 1) % 100 0: print(fEpoch {epoch 1}, Batch {i 1}, Loss {running_loss / 100:.4f}) running_loss 0.0 scheduler.step() print(fEpoch {epoch 1} finished)这段代码的关键点如下optimizer.zero_grad()必须在每次反向传播前清空梯度。否则 PyTorch 会累积梯度导致参数更新错误。loss.backward()计算梯度所有梯度会累积到每个参数的.grad属性中。optimizer.step()使用当前梯度更新参数。scheduler.step()在每个 epoch 后调整学习率。这里StepLR表示每 5 个 epoch 学习率乘以 0.5。交叉熵损失在 PyTorch 中同时包含了 Softmax 计算所以模型最后不需要再手动添加 Softmax直接输出原始得分即可。4.4 模型评估、保存与单张图片预测训练完成后需要在独立测试集上评估模型效果。这是因为训练集上的准确率不能真实反映模型泛化能力。correct 0 total 0 model.eval() with torch.no_grad(): for inputs, labels in testloader: inputs, labels inputs.to(device), labels.to(device) outputs model(inputs) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() accuracy 100.0 * correct / total print(fTest Accuracy: {accuracy:.2f}%)评估时要调用model.eval()并且使用torch.no_grad()。前者会切换模型到评测模式影响 Dropout 和 BatchNorm 等层的行为后者禁止 PyTorch 构建计算图节省内存并加快计算。保存模型时一般只保存state_dict也就是模型权重字典方便之后加载使用。torch.save(model.state_dict(), lenet5_cifar10.pth)加载模型并预测单张图片from PIL import Image def predict_image(image_path, model, classes, transform): image Image.open(image_path).convert(RGB) image_tensor transform(image).unsqueeze(0).to(device) model.eval() with torch.no_grad(): outputs model(image_tensor) _, predicted torch.max(outputs, 1) return classes[predicted.item()]需要特别注意的是预测时使用的预处理必须与训练时保持一致。如果训练时使用了transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))预测时也要使用完全相同的 transform否则输入分布不一致预测结果会受影响。5. 训练基础与优化技巧让模型收敛得更稳定5.1 学习率、批量大小与优化器的选择训练神经网络时有几个超参数直接决定模型能否收敛。学习率控制参数更新的步长。学习率过小模型收敛缓慢需要更多训练轮数。学习率过大损失函数可能在最优值附近震荡甚至发散到 NaN。零基础入门时可以先用较小的学习率例如 0.001 到 0.01观察训练损失变化后再调整。批量大小决定每次更新参数时使用多少张图片。批量大小过小梯度估计噪声大训练不稳定批量大小过大可能占用过多内存并且同样迭代次数下参数更新次数减少。常见选择是 32、64、128。优化器决定了参数更新规则。SGD 加上动量是最经典的做法动量让更新方向更平滑。Adam 可以自动调整每个参数的学习率对超参数更不敏感适合快速验证模型。下表整理了这组超参数的常见范围和影响超参数常见值设置过小设置过大学习率0.0001 - 0.01收敛慢震荡或发散批量大小32 - 128梯度噪声大内存占用高更新次数少动量0.9 左右收敛速度慢可能错过极小点训练轮数10 - 50欠拟合容易过拟合没有绝对最优的组合。入门阶段建议先使用 Adam 默认学习率跑通流程再尝试 SGD momentum 观察效果差异。5.2 数据增强与正则化图像分类模型很容易过拟合尤其是在训练数据量有限的情况下。数据增强是一种有效且低成本的缓解手段。它通过对训练图片做随机变换让模型看到更多样化的数据从而提高泛化能力。常见的图像数据增强方式有随机水平翻转随机裁剪随机改变亮度、对比度、饱和度随机旋转在 PyTorch 中可以这样扩展训练集的预处理transform_train transforms.Compose([ transforms.RandomCrop(32, padding4), transforms.RandomHorizontalFlip(), transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ])需要注意的是数据增强一般只用于训练集测试集只需要做 ToTensor 和 Normalize保证评估结果可复现。正则化方面常用手段是权重衰减和 Dropout。权重衰减会在损失函数中增加参数平方和惩罚项迫使模型权重不会过大。Dropout 在训练时随机丢弃一部分神经元的输出降低模型对某些特征的依赖。在 LeNet-5 中可以在两个全连接层之间加入 Dropoutself.dropout nn.Dropout(0.5) def forward(self, x): x F.relu(self.fc1(x)) x self.dropout(x) x F.relu(self.fc2(x)) x self.dropout(x) x self.fc3(x) return x加入 Dropout 后评估时无需手动关闭model.eval()会自动让它失效。5.3 学习率调度与早停训练过程中固定学习率往往不是最优选择。训练初期可以使用较大学习率快速下降训练后期需要较小学习率精调。PyTorch 提供了多种学习率调度器。最常见的有StepLR每隔固定轮数将学习率乘以一个系数。ReduceLROnPlateau当某个指标不再提升时降低学习率。示例scheduler optim.lr_scheduler.ReduceLROnPlateau( optimizer, modemin, factor0.5, patience3 ) for epoch in range(num_epochs): train_loss run_one_epoch(model, trainloader, optimizer, criterion) val_loss evaluate_loss(model, valloader, criterion) scheduler.step(val_loss)早停是一种简单而实用的技巧在验证集损失连续多轮不下降时提前终止训练并保留历史最优模型权重。它既能节省训练时间也能避免过拟合。5.4 优化技巧的实际对比思路入门阶段可以通过两个小实验感受优化技巧的作用。第一个实验不使用数据增强和 Dropout训练 LeNet-5 在 CIFAR-10 上跑 10 个 epoch。第二个实验在完全相同的情况下加入随机裁剪、随机翻转和 Dropout。常见结果是未加增强的模型训练准确率很高但测试准确率明显偏低接近过拟合加入增强和 Dropout 后测试准确率会有明显提升。这类对比实验可以训练你对“过拟合”的直觉。不要只盯着训练集准确率测试集准确率才是衡量模型真实能力的关键。6. 从 LeNet-5 到 AlexNet经典 CNN 的演进与借鉴6.1 两个经典网络的结构对比LeNet-5 和 AlexNet 在深度学习历史上都具有重要地位。LeNet-5 在 1998 年提出了卷积、池化、全连接组合的基本范式AlexNet 在 2012 年通过加深网络和多种训练技巧在 ImageNet 图像分类竞赛中取得突破让深度学习重回主流。下面用表格对比两者的核心差异特性LeNet-5AlexNet输入尺寸32x32224x224卷积层数25激活函数Sigmoid / TanhReLU池化方式平均池化最大池化Dropout无有局部响应归一化无有数据增强简单多种组合GPU 训练无双 GPU 并行主要数据集MNISTImageNet这个对比说明了从 LeNet-5 到 AlexNet 的演进方向网络更深、激活函数更高效、正则化手段更丰富、训练设备更强大。6.2 AlexNet 的关键创新为什么有效AlexNet 能取得突破不只是因为网络更深还因为解决了深层网络训练中的一系列实际问题。第一个关键创新是 ReLU 激活函数。相比 Sigmoid 和 TanhReLU 在正区间的梯度恒为 1计算简单能有效缓解梯度消失问题使深层网络可以更稳定地训练。第二个关键创新是 Dropout。AlexNet 在全连接层使用了 Dropout随机让一部分神经元失活显著降低了全连接层的过拟合风险。这个思想被后续大量网络采用。第三个关键创新是最大池化。LeNet-5 使用平均池化AlexNet 使用最大池化。最大池化能保留窗口内最强的特征响应在特征提取效果上更优。第四个关键创新是数据增强。AlexNet 在训练时使用了随机裁剪、水平翻转、颜色扰动等手段相当于扩大了有效训练数据规模缓解了过拟合。这些创新对零基础入门的启发是模型效果不仅取决于网络结构也取决于训练技巧。LeNet-5 虽然结构简单但加入 ReLU、Dropout、数据增强后也能在 CIFAR-10 上获得更好的表现。6.3 零基础如何从经典网络迁移到现代项目理解 LeNet-5 和 AlexNet 之后下一步可以接触更现代的架构例如 VGG、ResNet、DenseNet、EfficientNet以及目前常用的预训练模型迁移学习。现代图像分类项目很少再从零随机初始化训练整个大网络而是使用在 ImageNet 上预训练好的模型作为特征提取器然后针对自己的任务微调。PyTorch 中可以用两行代码加载预训练模型import torchvision.models as models model models.resnet18(pretrainedTrue)零基础阶段不需要立刻理解每个现代模型的全部细节重点是保持“先跑通再理解”的节奏。LeNet-5 能帮你建立对卷积网络的基本认识而后续模型都是在“更深的卷积层 更好的训练技巧”基础上演化而来。7. 常见问题排查环境、训练与数据7.1 环境安装阶段常见问题安装依赖时最容易遇到的问题有两个安装超时和版本不匹配。如果pip install很慢或超时可以更换国内镜像源。以清华源为例pip install -i https://pypi.tuna.tsinghua.edu.cn/simple torch torchvision如果运行代码时提示缺少某个模块先确认是否处于虚拟环境再检查模块是否安装成功pip list | grep torch如果torch.cuda.is_available()返回False通常是因为安装的 PyTorch 是 CPU 版本或者显卡驱动版本过低。学习阶段可以继续使用 CPU不必要一开始就追求 GPU。下表总结了常见环境问题和处理建议问题现象常见原因处理建议pip 安装超时网络原因使用镜像源找不到 torch 模块未激活虚拟环境先激活虚拟环境再执行代码CUDA 不可用安装的是 CPU 版 PyTorch重新安装 GPU 版或先使用 CPU 训练OpenCV 读取图片颜色异常通道顺序是 BGR使用 cvtColor 转换为 RGB7.2 训练阶段 Loss 不下降或出现 NaN训练时发现损失长期不变首先要排除的是输入数据和标签是否配对正确。例如 CIFAR-10 数据集中类别索引是否和标签字典一致。如果损失变成 NaN常见原因是学习率过大导致梯度更新幅度过大。解决方法是调低学习率例如从 0.01 降到 0.001并检查输入数据是否包含 NaN。排查顺序建议查看输入 batch 是否有 NaN。查看模型输出的数值范围。降低学习率。检查损失函数是否选择了合适类型。7.3 过拟合问题过拟合的表现是训练集准确率很高但测试集准确率很低。这是因为模型记住了训练集中的细节特征没有学到泛化规律。处理过拟合的常见路径增加训练数据量。使用数据增强。加入 Dropout。增加权重衰减。使用早停。简化网络结构。实际操作时不要同时修改所有参数否则很难判断哪个改进真正有效。一次只改一个变量对比实验效果。7.4 显存不足或训练速度慢训练较大的模型或使用较大 batch 时显存不足是常见问题。解决方式包括减小 batch_size。降低输入图片分辨率。使用更小的模型。使用混合精度训练。清理运行过程中未释放的缓存。CPU 训练速度慢时可以减少训练轮数使用小数据集做验证比如从训练集中取 2000 张图片先跑通流程再扩展数据量。7.5 数据预处理相关错误最容易被忽视的问题是数据形状和通道顺序。PyTorch 模型输入要求是(B, C, H, W)其中 B 是批量大小。如果数据是多张图片组成的 batch形状会由 DataLoader 自动决定。另一个常见错误是训练和预测使用了不同的预处理。比如训练时做了 Normalize预测时忘记做 Normalize模型输入分布与训练不一致导致预测准确率下降。正确的做法是把 transform 抽成公共函数训练和预测都复用它。8. 可复用的零基础入门清单和下一步学习路径8.1 环境与训练清单在开始任何图像分类项目前可以按下面的清单逐项确认Python 虚拟环境已经创建并激活。numpy、opencv-python、matplotlib、torch、torchvision 已安装。训练设备明确是 CPU 还是 CUDA。数据集已经下载并确认类别数量。输入图像尺寸和模型输入尺寸一致。训练集和测试集的预处理保持一致。模型定义了正确的输出类别数。优化器、损失函数、学习率已经设置。是否加入了数据增强和 Dropout。是否记录了训练损失和测试准确率。这张清单适合每次开始新实验时都过一遍能减少大量低级错误。8.2 模型与训练循环代码审查清单写训练代码时重点关注以下位置optimizer.zero_grad()是否在每次反向传播前调用。输入数据是否放到了与模型相同的设备上。使用model.train()和model.eval()是否正确切换。评估流程是否使用torch.no_grad()。保存和加载模型时是否只使用state_dict。学习率调度器更新时机是否放在 epoch 后。这些细节直接影响训练是否能稳定复现。8.3 下一步可以扩展的方向零基础入门完成一个 LeNet-5 图像分类项目后可以考虑以下几个方向。第一换成现代网络。尝试把 LeNet-5 替换成 ResNet18观察准确率和训练时间的变化。理解残差连接如何解决深层网络退化问题。第二做目标检测。图像分类只回答“图片里有什么”目标检测还要回答“物体在哪里”。可以学习 YOLO、Faster R-CNN 等经典算法。第三做图像分割。语义分割要求对每个像素分类可以理解完全卷积网络和上采样结构。第四做模型部署。训练好的模型最终要放到服务器或移动端运行可以学习 ONNX、TensorRT、Android NNAPI 等部署方案。第五研究训练稳定性。例如学习率预热、余弦退火、自适应优化器、混合精度训练等技巧对提升训练效率和最终效果很有帮助。对于零基础读者来说最重要的不是一次性掌握所有模型而是把“数据 - 模型 - 训练 - 评估 - 优化”这条链路跑通。只要这一轮完整流程走下来后面再接触更复杂的网络结构都会觉得有迹可循。图像分类只是计算机视觉的一个起点。真正有价值的是你通过 LeNet-5 建立了对 CNN 的基本直觉理解了数据预处理、模型结构、训练循环和调优之间的关系。下一个项目不妨选一个自己感兴趣的小数据集替换掉 CIFAR-10重新走一遍完整流程。这样你能留下更具体的实践记忆也更容易发现问题、解决问题。
返回列表