恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
深度学习实战入门:PyTorch+CIFAR-10图像分类跑通指南
首页
资讯中心
/
深度学习实战入门:PyTorch+CIFAR-10图像分类跑通指南
深度学习实战入门:PyTorch+CIFAR-10图像分类跑通指南
发布时间:2026/10/21 17:04:09
很多人Python基础学得不错一翻开深度学习的内容就卡住了。不是公式看不懂而是根本不知道从哪里开始写代码。我当初也有过这种体验网上教材排山倒海环境却怎么都装不顺好不容易装上又不知道第一行代码该敲什么。后来我换了个思路与其死磕理论不如先让一个最简单的项目完整跑起来哪怕那个模型“笨得可以”。这个系列我打算就按这个路子来——不堆公式不抄论文带着你把环境、数据、训练、评估这条路从头到尾走一遍。今天这篇是第一篇目标是让你在本地把一个真实的图像分类任务跑通并在跑通的过程中把那些真正重要、书上又很少讲清楚的细节逐个讲明白。无论你是刚入门的学生还是在职想转行的人只要你会一点Python语法这篇就能陪你走起来。1. 为什么深度学习实践要从搭建环境说起1.1 实践和理论的分岔路大多数教材的第一章都在讲反向传播和梯度下降但这些内容在没有“手感”之前听很容易变成一种负担。你还没见过一个模型长什么样就要先搞明白误差怎么往回传这其实是把顺序搞反了。我的建议是先跑通再抠原理。跑通一个最简单的模型之后你再去读反向传播脑子里会有一个具体的画面知道“哦原来就是这几行代码在做这件事”学习成本一下子降下来。这个系列就是按这个思路设计的。每一步都给出能直接运行的代码代码跑通了我们再回头解释这一段到底在干什么。这样做的好处是你始终有正反馈——打印出一个loss值、看到准确率往上涨、模型真的把一张图分对了这些都会让你有动力继续往下走。而单纯看书看到第二章往往就放弃了。1.2 框架选型和项目选题做深度学习实践第一步就是选框架。现在市面上主流的就是两个选择PyTorch和TensorFlow。我个人的建议是新手上手选PyTorch。原因很朴素它的代码风格更像写普通Python调试时打印中间变量非常方便而且学术和工业社区的资料更新很快遇到问题随手一搜就能找到答案。TensorFlow当然也很强尤其在部署环节有自己的优势但对第一次跑通项目的人来说PyTorch的学习曲线明显缓一些。框架定了项目也该选一个“性价比”高的。所谓性价比高就是数据容易获取、输入输出清晰、训练速度快、结果直观可见。我选了图像分类作为第一个实战主题用经典的CIFAR-10数据集。这个数据集一共包含6万张32x32的彩色图片分10个类别比如猫、狗、飞机、汽车这些。训练集5万张测试集1万张规模不大不小普通笔记本用CPU也能跑完一个简单模型有独立显卡的更是几分钟一个epoch。对入门来说没有比它更合适的了。这里顺便说一句硬件问题。很多人一听到深度学习就觉得自己必须买一张好显卡其实不然。这个系列第一篇文章的代码纯CPU也能跑只是慢一些。我自己最初就是在只有CPU的笔记本上跑的一个epoch要等几分钟但完全能接受。等到你真的想做大模型、跑大Batch再加硬件也不迟。2. 完整环境搭建虚拟环境、GPU驱动与PyTorch安装2.1 环境准备清单动手写代码之前先把环境理清楚。这里说的环境包括三部分Python解释器、包管理工具、PyTorch及其依赖库。很多新手在这步翻车不是因为操作难而是因为电脑里Python版本太多、依赖太乱装到最后自己都不知道在用什么环境跑代码。所以我强烈建议用Anaconda来管理虚拟环境。虚拟环境相当于给当前项目划一个独立的小房间你在里面随便装东西不会污染系统Python也不会和别的项目互相打架。创建环境的命令很简单conda create -n dl python3.10接着激活环境conda activate dlPython版本我建议选3.10或者3.11。太新的版本偶尔会遇到某个依赖库还没有适配的情况没必要为了尝鲜给自己挖坑。环境名字取一个顺手的比如“dl”代表deep learning记住它后面所有操作都在这个环境里做。2.2 PyTorch安装的完整流程PyTorch的安装很考验耐心因为它的版本需要和CUDA版本匹配。CUDA是显卡厂商提供的并行计算平台PyTorch在GPU上跑就是靠它。先别急着安装检查一下自己的硬件支持什么版本。在命令行输入nvidia-smi如果提示找不到命令说明你的电脑没有NVIDIA显卡或者驱动没装好。这种时候不用慌直接用CPU版PyTorch即可功能上没有区别只是训练慢一些。如果有显卡看输出右上角的“CUDA Version”比如显示12.1那你就装支持CUDA 12.1的PyTorch版本。安装命令我推荐用pip而不是conda因为pip在安装PyTorch这种大型包时通常更快也更好控制版本。以CUDA 12.1为例pip install torch torchvision如果下载速度很慢可以加上国内镜像源。常见的PyPI镜像源都可以加上-i参数指定即可比如pip install torch torchvision -i https://pypi.tuna.tsinghua.edu.cn/simple装完之后一定要验证一下是否成功。这一步很多人跳过了结果后面跑代码时才发现问题定位起来特别麻烦。请务必执行python -c import torch; print(torch.__version__, torch.cuda.is_available())如果输出类似2.1.2 True说明安装成功且GPU可用。如果输出的是2.1.2 False说明装的是CPU版或驱动没识别到但也别急后面代码一样能跑只是慢点。2.3 开发环境配置环境装好了接下来就是写代码的地方。我建议直接用Jupyter Notebook或Jupyter Lab尤其是对于初学者来说可以一段段执行代码看到中间结果比写一大个脚本然后从头跑一遍舒服得多。在虚拟环境里安装并注册内核pip install jupyter ipykernel python -m ipykernel install --user --name dl之后打开Jupyter新建Notebook时选择内核“dl”就能保证当前Notebook使用我们刚配置好的虚拟环境。这里有一个细节如果你打开Jupyter后忘记选内核默认用的可能是系统Pythonimport torch直接报ModuleNotFoundError。别问我怎么知道的这个坑我见过太多人踩了。最后当所有依赖都装好后把当前环境列表保存下来方便以后恢复pip freeze requirements.txt这一步看着简单但如果你以后换电脑、换环境拿着一份requirements.txt就能全部装回来省去很多手工记忆。3. 从数据到DataLoader动手前必须打通的环节3.1 数据集的获取与探索深度学习的训练本质上就是“喂数据”。但很多新手拿到数据集就不知道该干什么了甚至第一步下载就会卡住。CIFAR-10在PyTorch里可以直接用内置接口下载代码非常简单from torchvision import datasets, transforms transform transforms.Compose([ transforms.ToTensor() ]) train_set datasets.CIFAR10(root./data, trainTrue, downloadTrue, transformtransform) test_set datasets.CIFAR10(root./data, trainFalse, downloadTrue, transformtransform)代码本身没什么难度真正的坑在于下载速度。CIFAR-10的压缩包约170MB如果网络状况不好可能下到一半断掉或者连接超时。我的建议是先手动用浏览器或下载工具把压缩包下载下来放在./data目录下再运行上面的代码。PyTorch检测到本地已有文件就不会重复下载了省时省力。数据集下好之后别急着训练。先做点“探索性工作”——看看图片是什么样子的这对建立直觉非常有帮助。我通常会写一个小段代码随机抽出9张训练图片用matplotlib显示出来import matplotlib.pyplot as plt import numpy as np fig, axes plt.subplots(3, 3, figsize(6, 6)) for i, ax in enumerate(axes.flat): img, label train_set[i] img img.permute(1, 2, 0).numpy() ax.imshow(img) ax.set_title(str(label)) ax.axis(off) plt.show()看到图片的那一刻你会对整个任务有非常直观的感受原来模型要做的是分辨这些32x32的小图里到底是飞机还是鸟。张小图分辨率不高人类都难免看错更别说机器了所以不要对准确率期待过高能到70%以上就算入门成功。3.2 Dataset与DataLoader实现细节PyTorch处理数据的核心是两个类Dataset和DataLoader。Dataset负责定义“如何拿到一条数据”DataLoader负责将数据打包成批次并供模型读取。用内置的CIFAR-10时Dataset已经实现好了你只需要关心DataLoader怎么用。DataLoader有几个关键参数直接影响训练的速度和效果from torch.utils.data import DataLoader train_loader DataLoader(train_set, batch_size64, shuffleTrue, num_workers2) test_loader DataLoader(test_set, batch_size64, shuffleFalse, num_workers2)batch_size每次喂给模型多少张图片。64是一个比较稳妥的起步值显存不够就调到32或16没有硬性要求。shuffle每个epoch开始前打乱数据顺序。训练集必须设为True否则模型容易学到数据顺序里的虚假模式测试集不需要打乱。num_workers用几个子进程加载数据。设成2或4都能明显加快数据读取。但Windows系统下如果这个值太大容易莫名报错新手建议先设成0跑通了再调。在实际使用中你会在训练循环里一次次地取数据for images, labels in train_loader: print(images.shape, labels.shape) break输出的(64, 3, 32, 32)表示64张图、3个颜色通道RGB、宽高都是32像素。这个shape今后会反复出现请记牢。3.3 归一化与数据增强的正确打开方式新手最容易忽略的就是归一化。图像数据是0到255的整数或0到1的浮点数如果不做处理不同特征的数值范围差异巨大会让梯度下降的路径变得非常扭曲收敛也自然变慢。PyTorch里常见的做法是对图像做标准化即减均值再除以标准差让数据分布落到0附近transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize(mean[0.4914, 0.4822, 0.4465], std[0.2470, 0.2435, 0.2616]) ])这里的mean和std是CIFAR-10数据集在RGB三个通道上的统计值直接抄下来用就好。对于其他数据集你可以自己算也可以先不管精确值用经验值0.5和0.5代替效果相差不大。数据增强则是在训练时对图片做随机的变换比如水平翻转、随机裁剪、色差调整等让模型看到更多样化的输入从而减轻过拟合。注意数据增强只应该用在训练集上测试集要保留原始图片确保评估结果可靠。常见的组合是train_transform transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding4), transforms.ToTensor(), transforms.Normalize(mean[0.4914, 0.4822, 0.4465], std[0.2470, 0.2435, 0.2616]) ])RandomHorizontalFlip有50%的概率把图片左右翻转RandomCrop先在四周补4像素再随机裁剪回32x32。这两种增强方式简单有效几乎是图像分类入门的标配。不过要小心如果在物体识别或人脸相关任务中水平翻转让文字颠倒或者让左右不对称的目标变得混乱反而有害。图像增强不是万能药需要结合具体任务来判断。4. 训练一个完整模型从网络定义到结果可视化4.1 网络结构从零写一个小型卷积网络说实话第一次上手深度学习不需要一上来就搬ResNet、VGG这种大模型。CIFAR-10用一个小型的卷积神经网络CNN就足够了重点是要让你理解网络的每一层到底做了什么。我设计的这个微型网络如下import torch.nn as nn class SimpleCNN(nn.Module): def __init__(self): super().__init__() self.conv1 nn.Conv2d(3, 16, kernel_size3, padding1) self.conv2 nn.Conv2d(16, 32, kernel_size3, padding1) self.pool nn.MaxPool2d(2, 2) self.fc1 nn.Linear(32 * 8 * 8, 128) self.fc2 nn.Linear(128, 10) def forward(self, x): x self.pool(torch.relu(self.conv1(x))) x self.pool(torch.relu(self.conv2(x))) x x.view(x.size(0), -1) x torch.relu(self.fc1(x)) x self.fc2(x) return x解释一下这个结构。输入是(64, 3, 32, 32)的批次先经过第一个卷积层用16个3x3的卷积核提取特征padding1保证输出尺寸仍是32x32。经过ReLU激活和2x2最大池化后变成16通道的16x16特征图。第二次卷积把通道数扩大到32再次池化变成32通道的8x8特征图。随后把特征图展平得到32882048个值送入两个全连接层最后输出10个类别的分数。为什么要用卷积而不是全连接因为图片是二维结构全连接层会把每个像素都独立对待忽略邻居像素之间的空间关系而且参数数量巨大。一个32x32的彩色图如果直接展平就是3072个输入后面接全连接层参数会爆炸。卷积层通过局部连接和共享权重的方式用极少的参数捕捉局部模式比如边缘、纹理。你可以这样理解全连接是“每个人单独见老板”卷积是“一个班集体汇报”效率完全不一样。顺带看一下参数量。第一个卷积层的参数量是3*16*3*3 16其中16是偏置等于448个。整个模型加起来也就几万个参数相比动辄千万级的大模型训练起来毫无压力。4.2 训练管线搭建模型有了数据有了接下来就是把它们接起来跑一个完整的训练流程。这里需要确定三件事损失函数、优化器和评估指标。CIFAR-10是10分类问题损失函数直接用CrossEntropyLoss。一个需要特别说明的细节是PyTorch的交叉熵损失函数内部已经包含了softmax操作所以模型最后一层不需要再接softmax直接输出原始分数即可。新手经常在这一点上绕晕明明加了softmax反而训练不收敛就是这个原因。优化器有很多选择最常用的是SGD和Adam。SGD配momentum是很多论文里的标配收敛效果好但对学习率比较敏感。Adam则自带自适应学习率新手用起来更省心往往一两百个epoch就能看到效果。我给的示例用Adamimport torch.optim as optim model SimpleCNN() criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001)学习率设为0.001是Adam的默认推荐值。太大容易震荡不收敛太小则训练速度慢得让人失去耐心。训练循环的代码长这样def train_one_epoch(model, loader, criterion, optimizer): model.train() total_loss 0 correct 0 total 0 for images, labels in loader: optimizer.zero_grad() outputs model(images) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() return total_loss / len(loader), correct / total这段代码里最容易忽略的是optimizer.zero_grad()。PyTorch的梯度是默认累积的如果不清零上一批数据的梯度会加到下一批上loss会变得莫名其妙。每走一个批次这四个步骤缺一不可前向计算、算损失、反向传播、更新参数。训练时我在每个epoch结束后都打印一次平均loss和准确率for epoch in range(10): train_loss, train_acc train_one_epoch(model, train_loader, criterion, optimizer) print(fEpoch {epoch1}: loss{train_loss:.4f}, acc{train_acc:.4f})你会看到loss在下降、准确率在上升。如果连续跑了十个epoch准确率还没过40%不是代码问题就是学习率出了问题按我给的参数跑正常几分钟内就能有50%以上的训练准确率。4.3 评估与可视化训练完成之后一定要做测试集评估因为模型在训练集上表现好不代表在没见过的数据上也好。测试时不需要计算梯度用torch.no_grad()包起来可以省下大量内存和计算def evaluate(model, loader): model.eval() correct 0 total 0 with torch.no_grad(): for images, labels in loader: outputs model(images) _, predicted torch.max(outputs, 1) total labels.size(0) correct (predicted labels).sum().item() return correct / total test_acc evaluate(model, test_loader) print(fTest accuracy: {test_acc:.4f})这里又有一个细节model.train()和model.eval()的区别。有些层比如Dropout、BatchNorm在训练和测试时行为不一样必须显式切换否则测试结果可能非常离谱。新手如果漏了model.eval()常常会得到莫名其妙的低准确率。建议你把每个epoch的训练准确率和测试准确率都记录下来画成折线图import matplotlib.pyplot as plt plt.plot(train_accs, labeltrain acc) plt.plot(test_accs, labeltest acc) plt.legend() plt.show()从这个图里你能直观地看到两个经典现象一是训练准确率通常会高于测试准确率这是正常的二是如果训练准确率继续上升测试准确率却开始停滞甚至下降说明模型开始过拟合了。过拟合问题这个系列后面的文章会专门讲现在你只需要知道一个常见的应对手段是数据增强另一个是Dropout正则化——这两个手段在你的工具箱里已经有一个了。模型训练完记得保存不然关掉终端一切归零。保存和加载的代码就三行torch.save(model.state_dict(), cifar10_ckpt.pth) model.load_state_dict(torch.load(cifar10_ckpt.pth))其实整个训练流程至此已经闭环了。你有了数据有了模型有了训练循环还有了评估和保存。这个骨架稍加改动就可以迁移到其他图像分类问题上甚至能够为以后做目标检测、语义分割打底。5. 新手最常踩的坑与排查技巧5.1 环境与安装问题我见过非常多的人卡在环境上几乎占了入门困难的一大半。下面这张表是我自己整理的高频问题按概率排序问题现象常见原因解决办法pip install下载超时网络波动换国内镜像源或使用代理下载工具手动下载whl文件import torch报ModuleNotFoundError当前环境没装PyTorch或Jupyter选错内核确认虚拟环境已激活pip list查看是否安装Jupyter选择对应内核torch.cuda.is_available()返回False装成了CPU版或显卡驱动版本过旧安装对应CUDA版本的PyTorch更新显卡驱动安装时版本冲突包之间有依赖冲突用pip install --upgrade升级依赖或用conda安装try解决Jupyter无法启动或内核崩溃内核注册失败重新执行python -m ipykernel install --user --name dl这里我想特别强调镜像源的重要性。国内直接用默认源装PyTorch经常一个晚上都装不完。加上-i参数之后几分钟就能搞定。这是提高幸福感最简单的一步。5.2 训练过程常见异常环境跑通之后下一个战场就轮到训练过程中的各种问题。我同样整理成表问题现象常见原因解决办法loss一直是NaN学习率太大把学习率调低一个数量级比如0.001降到0.0001loss一直是0.x不动模型能力不足或输入没归一化检查数据是否做Normalize尝试加大模型容量显存不足out of memorybatch_size太大降低batch_size或用torch.no_grad()包住验证过程训练集准确率只有10%随机水平学习率过小或过大或者模型没前向传播成功用少量数据跑几个step观察loss变化先确认能过拟合这批数据训练集准确率很高但测试集很低过拟合增加数据增强、加Dropout、减少模型参数显卡利用率很低数据读取成为瓶颈调大num_workers确保数据增强不占用太多CPU排查训练问题时有一个很实用的方法先拿100张图片过拟合。如果模型连这100张图片都没法记住说明管线有bug如果轻松记住了说明代码正常剩下的就是调参问题。这个思路能帮你区分“代码问题”和“模型问题”排查效率大大提高。5.3 个人调试经验总结带过一些朋友上手之后我发现大多数问题都出在“只猜不查”。报错了就直接问别人而不是先看错误信息。实际上深度学习框架的错误提示已经非常友好了80%的问题都能通过仔细阅读报错信息解决。模型训练时你看到的loss和准确率就是最重要的诊断信号。我自己的调试习惯是这样的先在小数据上快速跑通把完整流程走一遍再切换到完整数据集在训练循环里每100个batch打印一次loss观察趋势是否正常每个epoch结束后记录训练和测试准确率模型崩了就回滚到最近的checkpoint而不是从头重来。另外保存模型的时机也很有讲究。不要只在最后一个epoch保存最好每个epoch存一份文件名带上epoch编号。中途想看某个epoch的效果或者某个epoch之后出现了问题都能迅速回退。这个习惯在以后做更大项目时非常重要。最后再多说一句数据的问题。很多人在跑通CIFAR-10之后会感叹“这个数据集太干净了”。实际情况是你做真实项目时数据会带噪声、样本不均衡、标签有错误这些都需要在数据预处理阶段处理。但好消息是你在这篇文章里学到的Dataset、DataLoader、归一化和增强这些基本工具在面对真实数据时一样能用。下一篇在这个系列里我会继续往下讲如何调整模型结构、如何系统性地防止过拟合以及如何把训练好的模型接到一个小应用里。先把这篇文章的代码亲手跑通一遍你就已经比昨天前进了一大步。