深度学习环境搭建与MNIST实战:从零入门PyTorch

发布时间:2026/10/20 7:28:36
深度学习环境搭建与MNIST实战:从零入门PyTorch
1. 从零搭建深度学习环境为什么第一步总是最劝退的搞深度学习的人都有一个共识模型跑不通八成不是代码写错了而是环境没配对。我见过太多人兴致勃勃打开教程结果卡在装环境这一步就再也没打开过第二篇。这不是夸张这是每天都在发生的事。Python 人工智能与深度学习这个方向入门门槛其实不在数学也不在算法理解而在于工具链的搭建和版本管理。你想想一个典型的深度学习项目涉及什么Python 解释器、包管理工具、深度学习框架、CUDA 驱动、cuDNN 加速库、各种科学计算依赖。这些东西之间的版本兼容关系复杂到可以画一张蜘蛛网。任何一个环节版本对不上轻则报错重则直接黑屏。所以这一篇我不打算上来就讲神经网络原理而是先把最脏最累的活干完——把环境搭起来把第一个能跑的模型跑通。这篇文章适合谁看如果你已经会写 Python 基础代码但对深度学习一无所知或者之前尝试过但被环境问题劝退那这篇就是写给你的。如果你已经是老手也可以看看我在版本选型和踩坑经验上的总结说不定能帮你省下几个小时的排查时间。我个人的建议是不要一上来就追求最新版本。深度学习生态有个特点框架适配总是滞后于 Python 新版本发布。你装了最新的 Python 3.13结果发现某个核心框架还没出对应的 wheel 包那就只能从源码编译编译过程中又是一堆依赖问题。这种坑踩一次就够了。2. 环境搭建的核心思路与工具选型2.1 为什么我强烈建议用虚拟环境先说一个最容易被忽略但最重要的事永远不要在系统 Python 里直接装深度学习框架。原因很简单。深度学习框架的依赖非常重一个 PyTorch 装下来连带依赖能占好几个 G。如果你在系统环境里装不同项目之间依赖冲突几乎是必然的。比如项目 A 需要 numpy 1.24项目 B 需要 numpy 1.26你只能保留一个版本另一个项目就跑不了。虚拟环境解决的就是这个问题。它给每个项目创建一个独立的 Python 运行空间各项目之间互不干扰。你可以理解为每个项目住自己的房子水电独立不会因为邻居装修把你家墙砸了。目前主流的虚拟环境工具有三种工具优势适用场景我的推荐度venvPython 自带零依赖简单项目、快速验证入门首选conda能管理非 Python 依赖如 CUDA深度学习、科学计算深度学习首选uv极快Rust 编写追求效率的老手进阶选择对于深度学习的初学者我建议直接用conda。不是因为它最好而是因为它能帮你管理 CUDA 相关的底层依赖这在深度学习场景下省心很多。venv 虽然轻量但它只管 Python 包CUDA 驱动这些东西它管不了。2.2 Python 版本怎么选稳定比新更重要选 Python 版本这件事我的原则是选主流框架官方支持列表里最新的稳定版而不是 Python 官网的最新版。举个例子假设现在 Python 最新是 3.13但 PyTorch 官方只支持到 3.12那你就老老实实装 3.12。为什么因为从源码编译 PyTorch 这件事对新手来说基本等于劝退。你需要装 C 编译器、配置环境变量、处理各种链接错误运气不好折腾一整天都搞不定。具体操作上用 conda 创建环境时可以指定版本conda create -n dl_env python3.11 conda activate dl_env这里我选 3.11 而不是 3.12 或 3.13原因是 3.11 在深度学习生态里的兼容性经过充分验证几乎所有主流框架和工具库都完整支持。你可能会问3.12 不是也支持吗是的但有些小众的辅助库可能还没跟上。入门阶段少一个变量就少一个坑。注意创建环境时一定要指定 Python 版本号不要用默认的。conda 默认可能给你装一个很新的版本后面装框架时就会遇到兼容问题。2.3 深度学习框架选型PyTorch 还是 TensorFlow这个问题在几年前还有争议但现在答案已经比较明确了入门选 PyTorch。不是说 TensorFlow 不好而是 PyTorch 在学术研究和教学场景下的生态更友好。它的代码风格更接近普通 Python调试起来直观报错信息也更容易理解。TensorFlow 的静态图模式对新手来说理解成本偏高虽然现在也支持动态图了但整体学习曲线还是更陡。安装 PyTorch 的时候有一个关键选择CPU 版本还是 GPU 版本。如果你的机器有独立显卡并且是 NVIDIA 的卡那可以装 GPU 版本训练速度会快很多。但如果你只是入门学习跑一些小模型CPU 版本完全够用而且安装简单得多。GPU 版本的安装命令需要去 PyTorch 官网根据你的 CUDA 版本生成不要自己瞎猜命令。我见过有人直接pip install torch然后发现装的是 CPU 版跑了半天发现没用上显卡白白浪费时间。# CPU 版本简单直接 pip install torch torchvision torchaudio # GPU 版本需要指定 CUDA 版本具体命令去官网生成 # 不要照抄网上的命令因为 CUDA 版本不同命令不同实操心得装完 PyTorch 后一定要验证一下是否真的能用 GPU。运行torch.cuda.is_available()返回 True 才算成功。返回 False 的话要么是没装 GPU 版要么是 CUDA 版本不匹配。3. 第一个深度学习项目手写数字识别完整实操3.1 为什么选 MNIST 作为入门项目MNIST 手写数字识别相当于深度学习界的 Hello World。它简单到什么程度数据集小、类别少、图像分辨率低用最简单的卷积网络就能跑到 99% 以上的准确率。但简单不代表没价值。通过这个项目你能完整走一遍深度学习的核心流程数据加载、模型定义、前向传播、损失计算、反向传播、参数更新、模型评估。这个流程走通了后面换更复杂的数据集和模型只是替换零件的事。我建议每个入门的人都老老实实把这个项目手敲一遍不要复制粘贴。手敲的过程中你会遇到各种小错误比如维度对不上、张量类型不对这些错误才是真正让你进步的东西。3.2 数据加载与预处理的关键细节MNIST 数据集包含 60000 张训练图片和 10000 张测试图片每张是 28x28 像素的灰度图。用 PyTorch 加载非常方便import torch from torchvision import datasets, transforms # 定义预处理把图片转成张量并归一化 transform transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ]) # 加载训练集 train_dataset datasets.MNIST( root./data, trainTrue, downloadTrue, transformtransform ) # 加载测试集 test_dataset datasets.MNIST( root./data, trainFalse, downloadTrue, transformtransform )这里有两个细节值得说。第一ToTensor()会把像素值从 0-255 缩放到 0-1 之间这是深度学习的基本操作不缩放的话梯度会爆炸。第二Normalize里的那两个数字 0.1307 和 0.3081 是 MNIST 数据集的全局均值和标准差用它们做标准化能让训练更稳定。你可能会问这两个数字怎么来的它们是统计出来的。理论上你可以自己算一遍但既然前人已经算好了直接用就行。这就像做菜用现成的高汤没必要从熬骨头开始。然后创建数据加载器from torch.utils.data import DataLoader train_loader DataLoader(train_dataset, batch_size64, shuffleTrue) test_loader DataLoader(test_dataset, batch_size1000, shuffleFalse)batch_size设成 64 是个经验值。太小了训练慢太大了显存吃不住。64 在大多数情况下是个稳妥的选择。shuffleTrue对训练集很重要打乱顺序能防止模型学到数据的排列规律这个规律是没意义的噪声。3.3 定义一个能跑的卷积网络模型定义这块我用一个简单的两层卷积加两层全连接的结构import torch.nn as nn import torch.nn.functional as F class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() # 第一层卷积输入1通道输出32通道卷积核3x3 self.conv1 nn.Conv2d(1, 32, kernel_size3, padding1) # 第二层卷积输入32通道输出64通道 self.conv2 nn.Conv2d(32, 64, kernel_size3, padding1) # 池化层2x2最大池化 self.pool nn.MaxPool2d(2, 2) # 全连接层64*7*7 是经过两次池化后的特征维度 self.fc1 nn.Linear(64 * 7 * 7, 128) self.fc2 nn.Linear(128, 10) def forward(self, x): # 第一层卷积 激活 池化 x self.pool(F.relu(self.conv1(x))) # 第二层卷积 激活 池化 x self.pool(F.relu(self.conv2(x))) # 展平 x x.view(-1, 64 * 7 * 7) # 全连接 激活 x F.relu(self.fc1(x)) # 输出层 x self.fc2(x) return x这里解释一下64 * 7 * 7是怎么来的。输入是 28x28经过第一层池化变成 14x14再经过第二层池化变成 7x7。通道数从 1 变成 32 再变成 64所以最终特征图是 64 个 7x7 的矩阵展平后就是 64773136 维。这个计算过程是新手最容易搞错的地方。如果你改了网络结构比如多加了一层池化那这个维度就要重新算。算错了就会报维度不匹配的错误。踩坑记录我第一次写的时候把 padding 忘了结果 28x28 经过 3x3 卷积变成 26x26后面维度全乱了。padding1 的作用是保持输出尺寸和输入一致这个参数在卷积层里很关键。3.4 训练循环深度学习的核心引擎训练循环是整个项目的心脏它的逻辑其实很固定import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model SimpleCNN().to(device) criterion nn.CrossEntropyLoss() optimizer optim.Adam(model.parameters(), lr0.001) for epoch in range(5): model.train() running_loss 0.0 for batch_idx, (data, target) in enumerate(train_loader): data, target data.to(device), target.to(device) # 梯度清零 optimizer.zero_grad() # 前向传播 output model(data) # 计算损失 loss criterion(output, target) # 反向传播 loss.backward() # 更新参数 optimizer.step() running_loss loss.item() if batch_idx % 100 0: print(fEpoch {epoch1}, Batch {batch_idx}, Loss: {loss.item():.4f}) print(fEpoch {epoch1} 平均损失: {running_loss / len(train_loader):.4f})这段代码里有几个关键点。optimizer.zero_grad()必须放在前向传播之前因为 PyTorch 默认会累积梯度不清零的话梯度会越加越大。loss.backward()计算梯度optimizer.step()根据梯度更新参数这两步是配套的。学习率lr0.001是 Adam 优化器的常用值。Adam 的好处是能自适应调整每个参数的学习率对新手来说不用太纠结调参。如果你用 SGD学习率可能要设成 0.01 左右而且需要配合动量。3.5 模型评估与结果分析训练完之后要看看模型在测试集上的表现model.eval() correct 0 total 0 with torch.no_grad(): for data, target in test_loader: data, target data.to(device), target.to(device) output model(data) _, predicted torch.max(output.data, 1) total target.size(0) correct (predicted target).sum().item() print(f测试集准确率: {100 * correct / total:.2f}%)model.eval()和torch.no_grad()是评估阶段必须的。前者把模型切换到评估模式影响 Dropout 和 BatchNorm 的行为后者关闭梯度计算节省显存和计算时间。正常情况这个模型能跑到 99% 左右的准确率。如果只有 90% 出头可能是训练轮数不够或者学习率设得不对。如果低于 80%那大概率是代码有 bug需要检查数据加载和维度变换。4. 常见问题与排查技巧实录4.1 环境类问题速查问题现象可能原因解决方法ImportError: No module named torch没装 PyTorch 或装到了别的环境确认当前环境重新 pip installtorch.cuda.is_available() 返回 False装的是 CPU 版或 CUDA 不匹配卸载重装 GPU 版检查 CUDA 版本下载 MNIST 卡住不动网络问题手动下载数据集放到 ./data 目录RuntimeError: CUDA out of memory显存不够减小 batch_size或改用 CPU4.2 代码类问题排查思路维度不匹配是最常见的错误。报错信息里通常会告诉你期望的维度和实际的维度比如 Expected input batch_size (64) to match target batch_size (32)。遇到这种错误从后往前推先看数据加载出来的形状对不对再看每一层之后的形状变化。损失不下降是另一个高频问题。如果损失一直卡在 2.3 左右不动那基本是模型输出全零或者全同一个类别。检查一下学习率是不是太大导致梯度爆炸或者数据标签是不是有问题。独家避坑技巧在训练循环里加一行打印模型输出的代码看看前几个 batch 的输出长什么样。如果所有样本的输出都一样那模型肯定有问题。正常情况输出应该有正有负不同样本之间有差异。4.3 性能优化的小技巧训练慢的话先确认是不是在用 GPU。用nvidia-smi命令可以看显卡占用情况如果显存占用为 0说明模型根本没跑在 GPU 上。数据加载也可能是瓶颈。DataLoader有个num_workers参数设成 4 或 8 可以并行加载数据。但在 Windows 上设太大可能会报错一般 2 到 4 比较稳妥。如果显存够用可以适当增大 batch_size这样 GPU 利用率更高。但 batch_size 太大会影响模型泛化能力需要配合调整学习率。经验法则是 batch_size 翻倍学习率也翻倍。5. 从入门到进阶的下一步方向跑通 MNIST 之后你算是正式踏进了深度学习的大门。接下来可以沿着几个方向继续深入。第一个方向是换数据集。CIFAR-10 是下一个经典选择32x32 的彩色图片10 个类别。它比 MNIST 难一些因为彩色图片的通道数变成 3而且类别之间的差异更微妙。在 CIFAR-10 上简单 CNN 大概能跑到 70% 多想上 90% 需要用到数据增强和更深的网络。第二个方向是换模型架构。把 SimpleCNN 换成 ResNet、VGG 这些经典结构感受一下不同设计思路带来的效果差异。PyTorch 的 torchvision 库里直接有预训练好的模型可以拿来即用。第三个方向是理解训练过程。用 TensorBoard 可视化损失曲线和准确率曲线观察模型是在过拟合还是欠拟合。过拟合的表现是训练准确率很高但测试准确率上不去这时候需要加 Dropout 或者数据增强。欠拟合则是两个准确率都低需要增大模型容量或延长训练时间。我个人的体会是入门阶段最重要的不是追求多高的准确率而是把整个流程的每个环节都搞清楚。知道每一步在做什么为什么这么做出了问题知道去哪里找原因。这个能力比会调参重要得多因为调参的技巧会过时但排查问题的思路是通用的。后面我会继续写卷积神经网络的原理拆解、迁移学习的实操、以及如何自己搭建一个完整的图像分类项目。感兴趣的话可以顺着这个系列往下看。