ARTICLE DETAIL

资讯详情

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

PyTorch入门实战:从理论到代码,用CNN实现手写数字识别

PyTorch入门实战:从理论到代码,用CNN实现手写数字识别 从非常粗略的认知角度来说很多自学深度学习的人在听课时觉得自己已经明白了神经网络是什么、反向传播怎么算梯度、卷积核为什么有效。但真正坐到电脑前面对一个空白的.py文件时却不知道第一行代码该写什么。这是深度学习入门过程中最普遍、也最容易被低估的一道坎。理论看完不意味着能写出模型而第八讲要跨过去的正是这个从“看懂公式”到“写出代码”的断层。这一讲的内容是 PyTorch 入门与手写数字识别实战。选择这个话题有一个明确判断深度学习入门的分水岭不在于数学推导而在于你能不能把一个已经懂的神经网络结构翻译成一段可以运行、可以看到损失下降、可以评估准确率的代码。手写数字识别是完成这个跨越的最小闭环——数据集简单、网络结构直观、训练速度快非常适合作为第一个完整的实战项目。读完这篇文章你会得到一条可复制的路径搭建 PyTorch 环境、加载数据、定义 CNN 模型、训练、验证、排查常见错误以及把这段经验迁移到其他图像分类项目的方法。1. 为什么要从“看懂公式”跨到“写出代码”前面七讲如果按常规顺序来上大概率已经覆盖了感知机、多层神经网络、激活函数、损失函数、梯度下降、反向传播以及卷积神经网络的基本概念。听课的时候你觉得每一步都顺理成章但一旦自己动手最常见的问题马上就出现了维度对不上、损失不下降、模型训练完准确率只有百分之十几、甚至数据加载这一步就卡住了。这些问题的本质不是理论理解不到位而是缺少“把理论翻译成代码”的训练。神经网络在公式里是抽象的层次结构在代码里是一系列具体的数组形状、张量运算和参数更新逻辑。举个例子你在纸上推导反向传播时通常只需要关心“梯度是从输出层往输入层传”但写代码时你必须清楚每一个中间张量的 shape 是什么view之后特征维度有没有算错CrossEntropyLoss内部到底有没有帮你做 softmax。这些细节只有在真正写完代码并运行之后才会变成你的直觉。所以第八讲的核心任务不是继续讲更深的算法而是迫使你完成一次完整的“理论到工程”的闭环。一旦跑通手写数字识别你后续学习更复杂的模型时就有了一个稳定的参照系所有深度学习项目的基本流程都是数据加载、模型构建、损失函数、优化器、训练循环、验证评估。换模型只是换网络结构换任务只是换数据集和损失函数整体骨架是不变的。从学习路径来看这一讲的定位也很特殊。前面是“看懂”这一讲是“做出来”后面如果要学目标检测、图像分割、自然语言处理需要的恰恰是这种“能做出来”的能力。很多人在学完理论后会下意识地继续找更高级的课程而不是停下来动手写一个最简单的项目结果越学越虚。第八讲存在的意义就是让你在复杂度最低的阶段先把一个完整项目跑通建立对深度学习工程流程的真实体感。2. PyTorch 四大核心概念张量、自动求导、nn.Module、DataLoader在进入代码之前需要先理解 PyTorch 的四个核心概念。这四个概念几乎是所有 PyTorch 项目的公共底座理解了它们你再看任何开源模型代码都会轻松很多。2.1 张量Tensor带设备信息的多维数组张量在最直观层面可以理解成 NumPy 数组的“深度学习版本”。它同样支持多维数组运算、切片、广播但它比 NumPy 多出两个关键能力。第一个是自动求导。只要张量的requires_gradTruePyTorch 就会在运算过程中记录计算图之后调用backward()就能自动算出梯度。第二个是设备感知。张量可以在 CPU 上也可以在 GPU 上通过.to(device)在两者之间切换。深度学习训练中模型参数、输入数据、标签都必须先放到同一个设备上才能参与计算。下面是对比示意便于理解它和 NumPy 的差异对比维度NumPy 数组PyTorch 张量存储位置只能在 CPU 内存CPU 或 GPU 显存自动求导不支持通过 requires_grad 支持与深度学习框架的关系需要手动拼装原生集成到训练链路常见用途数据处理模型输入输出、参数存储2.2 自动求导Autograd反向传播的“隐形引擎”反向传播是深度学习的核心算法但它不该由你手动实现。PyTorch 的autograd机制会在你执行张量运算时自动构建一个计算图然后在你调用loss.backward()时沿着这个图把梯度从损失函数反向传播到每一个需要更新参数的节点。实际训练循环里你几乎感觉不到它的存在但你必须理解它带来的三个约束第一默认情况下每次backward()之后梯度不会自动清零所以需要手动调用optimizer.zero_grad()否则梯度会累加在一起。第二定义了神经网络之后只有nn.Parameter类型的参数会默认开启requires_grad普通张量默认不开启。第三验证阶段要使用torch.no_grad()或在model.eval()环境下运行因为验证时不需要计算梯度关闭梯度计算既能省内存也能避免某些层如 Dropout、BatchNorm在推理阶段产生不一致行为。2.3 nn.Module一切模型的基类PyTorch 中所有神经网络模型都继承自nn.Module。这个类帮你做了几件重要的事情管理模型内部的参数、提供forward()方法定义前向传播路径、支持.to(device)整体搬运、支持model.eval()和model.train()切换训练模式。写一个自定义模型最核心的步骤是两步在__init__里定义需要的层在forward里描述数据从输入到输出的流动过程。千万不要在forward里临时创建层对象因为这样可以确保模型内部的状态被统一管理也方便后续保存和加载。2.4 DataLoader批数据加载器训练神经网络很少一次把所有数据都塞进显存而是把数据集划分成多个 batch每个 batch 送入模型计算一次。DataLoader负责完成这个工作它从Dataset中取样本、按batch_size组织成一个批次、在训练时可以打乱顺序、还可以用多进程并行加载数据。这里要区分Dataset和DataLoader。Dataset定义“数据长什么样、怎么读取”DataLoader定义“怎么按批次把数据交给模型”。初学者先把 Dataset 理解成一个能按索引取样本的列表DataLoader 是给它加了一个批次调度器后续使用开源数据集时会更容易理解。3. 环境准备用 Anaconda 搭建深度学习开发环境写深度学习代码的第一步不是打开编辑器而是准备一个干净、可复现的开发环境。强烈建议使用 Anaconda 或 Miniconda 作为Python 环境管理工具因为它可以创建多个互不干扰的虚拟环境不同项目使用不同 Python 版本和依赖也不会冲突。这一步虽然不复杂但很多人因为环境问题在项目里浪费了大量时间。3.1 安装 Miniconda 并创建虚拟环境在官网下载对应操作系统的 Miniconda 安装包安装过程保持默认即可。安装完成后打开终端或命令行窗口先确认 conda 可用conda --version然后创建一个新的虚拟环境建议为第八讲单独建一个环境避免和已有项目冲突conda create -n dl-study python3.9 -y conda activate dl-studydl-study是环境名称可以自己改。Python 版本不需要刻意追求最新3.9 到 3.11 都属于当前 PyTorch 支持比较稳定的范围具体版本以实际安装时的官方说明为准。激活环境后终端提示符前面会出现环境名称后面的所有操作都在这个环境内进行。3.2 安装 CPU 版 PyTorch 与 torchvision对刚入门的学习者如果手头没有 NVIDIA GPU完全不需要因为硬件条件而放弃动手。CPU 版本 PyTorch 足够训练一个手写数字识别模型只是训练速度慢一些。安装 CPU 版本的方法如下实际命令以 PyTorch 官网生成的安装指令为准但通用形式是pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu如果你的网络下载速度比较慢也可以使用国内镜像源安装pip install torch torchvision torchaudio -i https://pypi.tuna.tsinghua.edu.cn/simple上述命令中torchvision是 PyTorch 官方的视觉工具库提供常用数据集、预处理变换和预训练模型手写数字识别的数据集就由它提供。安装完成后执行验证命令python -c import torch; print(torch.__version__)如果能看到版本号比如2.x.x说明 PyTorch 安装成功。再验证一下torchvisionpython -c import torchvision; print(torchvision.__version__)3.3 开发工具推荐环境配置好后还需要一个开发工具。对于深度学习的初学者推荐在项目早期使用 Jupyter Notebook 或 JupyterLab理由很简单你可以一段一段地运行代码实时查看张量的 shape 和中间结果这对排查维度问题非常友好。但本文的示例会整理成标准的.py文件形式因为完整脚本更容易保存、复现和迁移到项目中。如果你更喜欢 IDEVS Code 配上 Python 插件同样可以但调试深度学习代码时掌握好print(tensor.shape)这种基本功比依赖调试器更重要。3.4 工作目录结构建议为项目建立一个清晰的工作目录统一管理代码、数据和模型文件dl-study/ ├── mnist_cnn.py # 训练脚本 ├── data/ # 数据集下载后存放的位置 └── models/ # 训练好的权重存放位置实际创建目录的命令mkdir dl-study cd dl-study mkdir data mkdir models把后续代码保存为mnist_cnn.py放在这个目录下即可。4. 准备第一个数据集MNIST 与 DataLoader4.1 MNIST 是什么MNIST 是深度学习领域最经典的入门数据集包含 0 到 9 的手写数字灰度图片。训练集有 6 万张测试集有 1 万张每张图片大小为 28×28 像素。它被广泛使用的原因非常清晰数据量适中任务简单模型在普通 CPU 上几分钟就能完成训练可以快速验证你写的代码是否正确。从工程角度看MNIST 是一个“任务本身简单但流程完整”的数据集。你需要处理的数据下载、数据预处理、批次打乱、迭代取数、模型训练、准确率计算跟做真实项目时的流程完全一致。它只是把图像识别任务本身的难度降到了最低。在 PyTorch 中加载 MNIST 只需要一行代码因为torchvision.datasets已经封装好了下载和读取逻辑。但这一行代码背后有几个概念需要理解清楚。4.2 transform 的作用原始 MNIST 图片是 PIL 图像对象而神经网络期望的输入是张量并且通常需要归一化。因此我们要定义一组预处理变换transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])ToTensor()会把 PIL 图像或 NumPy 数组转换为张量同时把像素值从 0 到 255 缩放到 0 到 1 区间。Normalize则对每个像素做标准化让它变成接近均值为 0、方差为 1 的分布这样能让神经网络的训练更加稳定。这里使用的(0.1307,)和(0.3081,)是 MNIST 数据集的全局均值和标准差属于官方通用设置初学者直接使用即可。4.3 DataLoader 参数详解加载训练集和测试集的完整代码是from torch.utils.data import DataLoader import torchvision import torchvision.transforms as transforms train_dataset torchvision.datasets.MNIST( root./data, trainTrue, transformtransform, downloadTrue ) test_dataset torchvision.datasets.MNIST( root./data, trainFalse, transformtransform, downloadTrue ) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse)batch_size64表示每次取出 64 张图片作为一个批次。shuffleTrue表示每个 epoch 训练之前都打乱数据顺序这样可以避免模型学习到样本顺序带来的偏差。测试集不需要打乱所以设置shuffleFalse。downloadTrue表示如果本地没有数据集就自动下载下载后的文件会存放在root./data指定的目录中。这里有一个初学者容易忽略的细节trainTrue拿到的是训练集trainFalse拿到的是测试集。如果你误把测试集也设成trainTrue训练完的模型在测试集上表现会很奇怪因为模型“见过”的数据和用来评估的数据不是独立的。4.4 数据加载的常见问题最典型的问题是下载慢或者下载失败。MNIST 数据集文件不大但有时连接外网不稳定。解决方案可以手动下载数据文件后放到./data/MNIST/raw/目录下或者更换网络环境重试下载。另外Windows 系统上使用DataLoader时如果num_workers设置大于 0可能遇到多进程相关的报错此时最简单的做法是删除num_workers参数使用默认值 0。5. 完整实战PyTorch 实现 CNN 手写数字识别接下来进入本讲的核心部分。我们将用 PyTorch 实现一个经典的 CNN 模型在 MNIST 数据集上完成训练和验证。为了让整个过程清晰可复现这里给出一个完整的mnist_cnn.py文件建议阅读完再运行。5.1 导入依赖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这段代码引入 PyTorch 的核心库、神经网络模块、优化器、视觉工具库和数据加载器。如果前两步环境安装成功导入不会出现错误。5.2 定义 CNN 网络结构class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv1 nn.Conv2d(1, 32, kernel_size3, padding1) self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) self.pool nn.MaxPool2d(2, 2) self.fc1 nn.Linear(64 * 7 * 7, 128) self.fc2 nn.Linear(128, 10) self.relu nn.ReLU() def forward(self, x): x self.pool(self.relu(self.conv1(x))) x self.pool(self.relu(self.conv2(x))) x x.view(-1, 64 * 7 * 7) x self.relu(self.fc1(x)) x self.fc2(x) return x这个网络包含两个卷积层、两个池化层和两个全连接层。输入是 1×28×28 的灰度图片。第一个卷积层把通道数从 1 提升到 32第二个卷积层再把通道数提升到 64。每次卷积之后接一个 2×2 的最大池化图像尺寸从 28×28 变成 14×14再变成 7×7。因此最后一个池化层输出的是 64 个通道、7×7 大小的特征图展平后长度为64 * 7 * 7。x.view(-1, 64 * 7 * 7)这一步把四维张量展平成二维-1表示自动推断 batch 大小。最后fc2输出 10 个值对应 0 到 9 十个数字类别。这里有一个非常重要的原理nn.CrossEntropyLoss内部已经包含了 softmax 操作所以网络最后一层不需要再额外加 softmax。很多初学者会在输出层后面手动加nn.Softmax然后在损失计算时又用CrossEntropyLoss相当于对概率分布又做了一次 log softmax结果会导致模型训练不收敛。正确做法是模型输出原始 logits把 logits 交给CrossEntropyLoss。5.3 定义训练函数def train_one_epoch(model, train_loader, criterion, optimizer, device): model.train() running_loss 0.0 total_samples 0 for images, labels in train_loader: images images.to(device) labels labels.to(device) outputs model(images) loss criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss loss.item() * images.size(0) total_samples images.size(0) return running_loss / total_samplesmodel.train()把模型切换到训练模式。随后遍历训练集的每一个批次把图片和标签放到设备上。前向传播得到输出计算损失然后执行反向传播和参数更新。optimizer.zero_grad()这步非常关键每次迭代前都要把梯度清零否则 PyTorch 默认会将梯度累加。累加梯度是一个特色特性它让你可以手动模拟“大 batch 小批次”的训练方式但如果你不理解这个机制就很容易造成模型更新方向混乱。损失函数CrossEntropyLoss的输出是一个标量张量loss.item()取出它的 Python 数值用来打印和监控。5.4 定义验证函数def evaluate(model, test_loader, device): model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in test_loader: images images.to(device) labels labels.to(device) outputs model(images) _, predicted torch.max(outputs, dim1) total labels.size(0) correct (predicted labels).sum().item() return correct / total验证函数不更新参数只做前向传播并统计预测正确的样本数。torch.max(outputs, dim1)返回每个样本在所有类别上的最大值索引也就是模型预测的类别。model.eval()会让 Dropout 等训练专用层关闭保证验证结果的稳定性。5.5 主程序训练流程def main(): device torch.device(cuda if torch.cuda.is_available() else cpu) print(Using device:, device) transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset torchvision.datasets.MNIST( root./data, trainTrue, transformtransform, downloadTrue ) test_dataset torchvision.datasets.MNIST( root./data, trainFalse, transformtransform, downloadTrue ) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse) model SimpleCNN().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) epochs 5 for epoch in range(epochs): train_loss train_one_epoch( model, train_loader, criterion, optimizer, device ) test_acc evaluate(model, test_loader, device) print(fEpoch [{epoch1}/{epochs}], Loss: {train_loss:.4f}, fTest Accuracy: {test_acc:.4f}) torch.save(model.state_dict(), ./models/mnist_cnn.pth) print(模型已保存到 ./models/mnist_cnn.pth) if __name__ __main__: main()主程序的逻辑非常清晰先检测设备再准备数据变换和数据加载器然后创建模型、损失函数、优化器最后循环训练并验证。optim.Adam是目前最常用的优化器之一学习率设为 0.001 是它的常见默认值。训练结束后用torch.save保存模型的参数模型文件会写入./models目录后续推理或继续训练都能复用。5.6 完整代码汇总将以上所有代码合并到mnist_cnn.py中就得到了一个可以完整运行的训练脚本。建议不要直接复制粘贴后运行而是逐行阅读一遍确保理解每一部分在整个训练流程中的位置。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 class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv1 nn.Conv2d(1, 32, kernel_size3, padding1) self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) self.pool nn.MaxPool2d(2, 2) self.fc1 nn.Linear(64 * 7 * 7, 128) self.fc2 nn.Linear(128, 10) self.relu nn.ReLU() def forward(self, x): x self.pool(self.relu(self.conv1(x))) x self.pool(self.relu(self.conv2(x))) x x.view(-1, 64 * 7 * 7) x self.relu(self.fc1(x)) x self.fc2(x) return x def train_one_epoch(model, train_loader, criterion, optimizer, device): model.train() running_loss 0.0 total_samples 0 for images, labels in train_loader: images images.to(device) labels labels.to(device) outputs model(images) loss criterion(outputs, labels) optimizer.zero_grad() loss.backward() optimizer.step() running_loss loss.item() * images.size(0) total_samples images.size(0) return running_loss / total_samples def evaluate(model, test_loader, device): model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in test_loader: images images.to(device) labels labels.to(device) outputs model(images) _, predicted torch.max(outputs, dim1) total labels.size(0) correct (predicted labels).sum().item() return correct / total def main(): device torch.device(cuda if torch.cuda.is_available() else cpu) print(Using device:, device) transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) train_dataset torchvision.datasets.MNIST( root./data, trainTrue, transformtransform, downloadTrue ) test_dataset torchvision.datasets.MNIST( root./data, trainFalse, transformtransform, downloadTrue ) train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size64, shuffleFalse) model SimpleCNN().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) epochs 5 for epoch in range(epochs): train_loss train_one_epoch( model, train_loader, criterion, optimizer, device ) test_acc evaluate(model, test_loader, device) print(fEpoch [{epoch1}/{epochs}], Loss: {train_loss:.4f}, fTest Accuracy: {test_acc:.4f}) torch.save(model.state_dict(), ./models/mnist_cnn.pth) print(模型已保存到 ./models/mnist_cnn.pth) if __name__ __main__: main()6. 运行、验证与效果评估6.1 运行训练脚本在命令行中进入dl-study目录并激活环境然后执行python mnist_cnn.py第一次运行时程序会自动下载 MNIST 数据集到./data目录。下载完成后训练会逐轮输出类似下面的信息具体数值因随机性会有浮动Using device: cpu Epoch [1/5], Loss: 0.2356, Test Accuracy: 0.9621 Epoch [2/5], Loss: 0.0825, Test Accuracy: 0.9770 Epoch [3/5], Loss: 0.0584, Test Accuracy: 0.9823 Epoch [4/5], Loss: 0.0461, Test Accuracy: 0.9851 Epoch [5/5], Loss: 0.0382, Test Accuracy: 0.98706.2 怎么判断训练是成功的判断训练成功的关键标准有两个。第一训练损失在逐轮下降。如果损失从一开始就居高不下或者来回震荡不收敛说明代码或超参数有问题。第二测试集准确率明显高于随机猜测。MNIST 有 10 个类别随机猜测的准确率是 10%。如果最终准确率还在 50% 以下几乎可以确定代码里有 bug而不是模型能力不足。正常的训练结果测试准确率应该在 95% 以上。如果只有 90% 左右通常是因为训练轮数太少、学习率设置不合理或模型结构过于简单。如果准确率异常高例如 100%反而要警惕大概率是数据泄露或者测试集和训练集发生了重合。6.3 训练失败第一步看哪里如果运行报错第一步不是上网搜索而是先看最后一个 traceback 指向的位置。绝大多数新手报错来自三个地方数据维度不匹配、设备不一致、文件路径不正确。把错误信息中提到的张量 shape 和你的预期对照一下再检查是否所有输入都执行过.to(device)能解决大部分问题。7. 常见问题与排查思路深度学习代码有一个特点报错信息往往很长但真正的原因很集中。下表汇总了第八讲实战中最常见的几类问题以及对应的排查和解决路径。问题现象可能原因排查方式解决方案RuntimeError: shape mismatch全连接层输入维度计算错误打印最后一个池化层的输出 shape用x.view(x.size(0), -1)自动展平损失不下降或直接报 NaN学习率过大、数据未归一化打印前几个 batch 的 loss 和梯度值降低学习率检查 transform 是否包含 Normalize测试准确率徘徊在 10% 左右标签与模型输出维度错位、训练顺序错乱检查 DataLoader 的 batch 输出和模型输出 shape确认类别数为 10输出层改为nn.Linear(128, 10)CUDA out of memorybatch_size 过大或模型过大查看显存占用情况减小 batch_size使用更小的输入尺寸Windows 下 DataLoader 报多进程错误num_workers设置不当查看是否设置在 Windows 上不适用的值去掉num_workers或设为 0下载 MNIST 一直失败或非常慢网络连接受限或未使用国内镜像检查文件是否部分下载到本地手动下载并放到./data/MNIST/raw/目录或更换网络环境model.eval()后结果波动模型中包含 Dropout 或 BatchNorm确认推理阶段是否切换了训练模式始终在验证和推理前调用model.eval()模型保存后再次加载出现参数不匹配保存的是 state_dict加载时模型类不一致打印 state_dict 的 key 和模型权重层级确保加载前创建同结构模型实例需要强调的一点是遇到报错时不要先怀疑“PyTorch 有 bug”几乎所有的 RuntimeError 都是因为张量形状、设备或者数据类型不符合预期。把报错信息里的 shape 信息打印出来对照一遍通常比重新写代码更高效。8. 工程化最佳实践从“跑通”到“能上真实项目”手写数字识别跑通以后真正的学习才刚刚开始。很多人在这一步就开始背代码、换数据集却忽略了把代码从“能跑”变成“可靠”的工程习惯。这一节的内容是让你的模型从“课堂演示”走向“能迁移到真实项目”的关键。8.1 设置随机种子保证结果可复现神经网络训练涉及随机初始化、数据打乱等多个随机过程同样一份代码每次运行结果都会略有不同。如果你希望训练结果可复现需要在代码开头设置随机种子import random import numpy as np def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) if torch.cuda.is_available(): torch.cuda.manual_seed_all(seed)在main()开头调用set_seed(42)可以让同一环境下的训练结果基本保持一致。这对调试和对比实验非常重要因为你能确定实验效果的差异来自模型改动而不是随机波动。8.2 模型保存与加载要配合结构一致训练完成后保存模型通常推荐保存state_dict而不是整个模型对象。加载时需要先创建相同结构的模型实例再载入权重# 保存 torch.save(model.state_dict(), ./models/mnist_cnn.pth) # 加载 model SimpleCNN() model.load_state_dict(torch.load(./models/mnist_cnn.pth, map_locationcpu)) model.eval()map_locationcpu的作用是即使在 GPU 上保存的权重也能加载到 CPU 机器上推理。这在模型跨机器迁移时非常实用。新版 PyTorch 的load_state_dict方法默认不允许权重 key 不匹配所以如果报错说“Missing key(s)”通常意味着加载代码里定义的模型结构和保存时不一致。8.3 训练日志与可视化真实项目里你不可能每训练一个 epoch 只打印一行文字。更规范的做法是记录每一次迭代的损失在训练后绘制损失曲线和准确率曲线。下面的代码演示了如何收集训练日志并进行基础可视化import matplotlib.pyplot as plt history {train_loss: [], test_acc: []} # 在训练循环内记录 history[train_loss].append(train_loss) history[test_acc].append(test_acc) # 训练结束后绘制 plt.figure(figsize(12, 4)) plt.subplot(1, 2, 1) plt.plot(history[train_loss]) plt.title(Training Loss) plt.xlabel(Epoch) plt.ylabel(Loss) plt.subplot(1, 2, 2) plt.plot(history[test_acc]) plt.title(Test Accuracy) plt.xlabel(Epoch) plt.ylabel(Accuracy) plt.show()如果你在中国大陆的 Matplotlib 绘图时遇到中文标签显示为方块通常需要设置中文字体例如使用plt.rcParams[font.sans-serif] [SimHei]并加一行plt.rcParams[axes.unicode_minus] False。这是 Windows 用户最常遇到的绘图问题之一。8.4 判断过拟合与早停训练集准确率高而测试集准确率明显偏低是典型的过拟合信号。第八讲的 MNIST 任务简单两层 CNN 出现过拟合的概率较低但需要养成检查训练集和测试集差距的习惯。如果发现两者的准确率差距持续扩大常见的解决办法包括增加数据增强、添加 Dropout 层、降低模型容量、或者提前结束训练。“早停”是一种简单的正则化策略每个 epoch 后评估验证集准确率如果连续多个 epoch 准确率不再上升就提前终止训练并回退到历史最优模型。在真实项目里这个策略比固定训练 100 个 epoch 更可靠。8.5 从 MNIST 迁移到真实图像分类项目MNIST 是一个单通道、小尺寸、粗对齐的数据集真实项目往往复杂得多。当你尝试把它迁移到口腔病理图像识别、遥感图像分类或其他业务场景时需要关注的差异集中在三个方面。第一数据通道和尺寸。真实图片通常是三通道 RGB且尺寸往往大于 28×28网络第一层需要从1改成3全连接层的输入维度也要根据卷积后的特征图尺寸重新计算。第二数据量。MNIST 有 6 万张训练图片真实项目可能只有几千张典型的做法是使用预训练模型迁移学习而不是从零训练。第三类别不平衡。真实项目的类别分布往往不均衡此时不能只看准确率还要关注精确率、召回率和 F1 分数。这里的核心观点是骨架流程不变变的只是数据集、模型结构和评估指标。如果你的基座能力足够扎实迁移到真实项目时遇到的新问题只会集中在数据和业务层面而不是纠结“训练循环该怎么写”。8.6 安全与备份意识深度学习项目同样需要备份意识。训练脚本的每一版改动都建议提交到 Git 仓库模型权重文件放在独立目录中并按训练时间或 epoch 数命名例如mnist_cnn_epoch5.pth。如果未来要复现实验只有代码没有权重或者只有权重没有代码都会变成一种麻烦。目录命名也要尽量避免中文和特殊字符减少跨平台出现路径编码问题的可能。9. 第八讲小结与下一步学习方向第八讲的真正收获是把前面所有理论落实成一个可以运行、可以验证、可以继续扩展的项目。你现在应该已经理解PyTorch 不是靠背 API 学出来的而是靠一遍遍运行、修错、调整参数建立体感的MNIST 训练脚本也不是一个孤立的小 demo而是所有深度学习图像项目的共同起点。如果这一讲的内容你已经完全跑通那么下一步可以按难度递进尝试三条路径。第一条继续在 MNIST 上做改动比如改网络结构、换优化器、加数据增强直观观察各种因素对准确率的影响。第二条换一个稍复杂的数据集例如 CIFAR-10体验彩色图片、更多类别和小尺寸带来的变化。第三条去读一个开源项目的主文件带着“数据加载在哪、模型定义在哪、训练循环在哪、验证逻辑在哪”四个问题去拆解代码。这三条路都能有效检验第八讲的成果。如果运行过程中还没有一次完整跑通也不用急着看下一篇教程。把报错信息截图按照第四节和第七节的检查步骤逐个排查你会发现报错信息本身就是最好的学习材料。深度学习入门没有一步到位的捷径但只要你完成了这个最小闭环后续每个主题都会变得更容易落地。建议把这篇文章保存到收藏夹配置环境或写代码卡住时再翻一遍对应的章节。
返回列表