PyTorch入门与环境搭建:从安装到第一个神经网络
本文你将学到 :pip/conda两种方式安装PyTorch的正确姿势(含GPU版本注意事项)、IPython与Jupyter Notebook的高效用法(自动补全、内省、魔术方法、调试)、Tensor的基本操作、autograd自动求导的第一印象,以及如何用
nn.Module从零搭建LeNet卷积网络,并在CIFAR-10数据集上完整跑通"数据加载→训练→测试"的全流程。读完本文,你就拥有了一个能真正跑起来的深度学习环境和第一个自己训练出来的模型。
文章目录
- PyTorch入门与环境搭建:从安装到第一个神经网络
-
- 一、为什么选PyTorch
- 二、安装PyTorch:pip与conda
-
- [2.1 用pip安装(推荐新手)](#2.1 用pip安装(推荐新手))
- [2.2 用conda安装](#2.2 用conda安装)
- 三、搭好你的学习环境:IPython与Jupyter
-
- [3.1 IPython:增强版Python Shell](#3.1 IPython:增强版Python Shell)
- [3.2 Jupyter Notebook:交互式笔记本](#3.2 Jupyter Notebook:交互式笔记本)
- [3.3 推荐的开发模式](#3.3 推荐的开发模式)
- 四、五分钟上手Tensor
- 五、autograd:自动求导初体验
- 六、用nn.Module搭建LeNet
- 七、实战:CIFAR-10图像分类
-
- [7.1 数据加载与预处理](#7.1 数据加载与预处理)
- [7.2 定义网络、损失函数和优化器](#7.2 定义网络、损失函数和优化器)
- [7.3 训练网络](#7.3 训练网络)
- [7.4 测试网络](#7.4 测试网络)
- [7.5 迁移到GPU训练](#7.5 迁移到GPU训练)
- 总结

一、为什么选PyTorch
PyTorch是一款底层以C/C++为主导开发、对外提供完整Python接口的深度学习框架。它有几个非常适合入门者的特点:
- 接口设计和NumPy高度相似 。会用NumPy的人几乎可以无缝迁移,
view、sum、索引切片等操作的手感基本一致,而且Tensor可以和ndarray低成本互转。 - 动态计算图 。网络的计算图在每次前向传播时动态构建,你可以在
forward里随意使用if、for、print这些原生Python语法,调试体验和写普通Python脚本没有区别。 - GPU加速只需一行代码 。
tensor.to(device)即可把数据搬到显卡上,模型同理。 - 自动求导。autograd模块会自动记录运算过程并完成反向传播,你不需要手动推导任何梯度公式。
本系列所有代码基于 PyTorch 1.8 + Python 3 环境验证(这也是陈云《深度学习框架PyTorch:入门与实践(第2版)》采用的版本),核心概念在更新版本的PyTorch中依然完全适用。
二、安装PyTorch:pip与conda
2.1 用pip安装(推荐新手)
pip安装二进制包是最简单、最不容易出错的方式。到PyTorch官网(pytorch.org)首页,依次选择操作系统、包管理器、语言和CUDA版本,官网会直接生成对应的安装命令。以Linux + pip + CUDA 10.2为例:
bash
pip install torch==1.8.0 torchvision==0.9.0 torchaudio==0.8.0
如果不指定版本号,pip会直接安装最新的稳定版,对新手来说通常也是可以的。
安装完成后验证一下:
python
import torch as t
print(t.__version__) # 输出版本号即安装成功
print(t.cuda.is_available()) # True表示GPU版可用
有两个新手最常踩的坑必须先说清楚:
- 包名是
torch,不是pytorch。import时写的也是torch。 - 要用GPU版本,必须先装好NVIDIA显卡驱动 ,再安装PyTorch。不过PyTorch的安装包里已经集成了CUDA运行时的二进制文件,所以你选择的CUDA版本不需要和系统里装的CUDA版本一致,这一点省去了很多配置麻烦。没有N卡的读者可以选择CPU-only版本,学习本系列内容完全够用。
2.2 用conda安装
如果你用Anaconda管理Python环境(笔者也推荐用它建独立的虚拟环境),可以用conda安装:
bash
conda install pytorch==1.8.0 torchvision==0.9.0 torchaudio==0.8.0 cudatoolkit=10.2 -c pytorch
-c pytorch表示从PyTorch官方源下载,国内速度可能比较慢。解决办法是把conda源换成清华镜像源(配置方法搜索"conda 清华镜像"即可),换源之后去掉-c pytorch参数,下载速度会快很多。
Windows下的安装流程完全一致,同样是在官网选择对应选项拿到命令执行即可。
三、搭好你的学习环境:IPython与Jupyter
学PyTorch最好的方式是"边敲边看",交互式环境远比直接写.py脚本高效。这里介绍三件套:VS Code、IPython、Jupyter Notebook。
3.1 IPython:增强版Python Shell
bash
pip install ipython
命令行输入ipython启动。它比原生Python Shell强在这几个功能:
自动补全 :输入变量或函数前几个字母按Tab即可补全。
内省 :对象后面加?查看文档,加??直接看Python源码:
python
In [1]: import torch as t
In [2]: t.abs? # 查看abs函数的帮助文档
In [3]: t.nn.L1Loss?? # 查看源码(只能看到Python层,看不到C++实现)
魔术方法 :以%开头的特殊命令,常用的有:
python
In [4]: a = t.Tensor(2, 3)
In [5]: %timeit a.sum() # 测量语句执行耗时
7.34 µs ± 18.3 ns per loop
In [6]: %hist # 查看输入历史
In [7]: %run -i a.py # 在当前命名空间中执行脚本文件
In [8]: %debug # 程序报错后输入它,直接跳到出错现场调试
其中%debug值得单独强调:当你%run main.py跑挂了之后,输入%debug可以直接进入报错处的调试模式,用u/d在调用栈里上下移动,l查看上下文代码,q退出。不需要重跑程序就能查错,这在训练了半小时才崩的场景里能救命。脚本里也可以主动埋断点:
python
import ipdb
ipdb.set_trace() # 程序运行到这里会自动进入调试模式(需先pip install ipdb)
3.2 Jupyter Notebook:交互式笔记本
bash
pip install jupyter
jupyter notebook # 启动后浏览器自动打开 http://127.0.0.1:8888
Jupyter在IPython的能力之上提供了图形化界面,支持代码、Markdown、图表混排,特别适合做实验记录。IPython的自动补全、内省、魔术方法在Jupyter里全部可用。
很多人的算力在远程服务器上,Jupyter的远程访问配置流程如下:
bash
# 1. 生成配置文件(在~/.jupyter/下生成jupyter_notebook_config.py)
jupyter notebook --generate-config
python
# 2. 在Python中生成加密密码
from notebook.auth import passwd
p = passwd() # 交互式输入两次密码
print(p) # 得到形如 argon2:... 的加密串
python
# 3. 修改配置文件中的三项
c.NotebookApp.password = u'argon2:...' # 上一步的加密串
c.NotebookApp.ip = '*' # 允许任意ip访问
c.NotebookApp.port = 9999 # 绑定端口
之后在服务器上启动jupyter notebook,本地浏览器访问http://[服务器ip]:9999输入密码即可。如果打不开,检查服务器防火墙是否放行了对应端口。
3.3 推荐的开发模式
对于"本地写代码 + 远程服务器跑训练"这一最常见的场景,笔者最推荐的方案是:VS Code + Remote-SSH插件 ,直接打开远程服务器上的文件夹,用远程的Python解释器运行和调试。相比SSHFS挂载(读写慢、用不了远程GPU)和Git来回同步(繁琐、难调试),这种方式启动快、读数据方便,配合VS Code的Python和Jupyter插件,.py和.ipynb都能流畅编辑。
四、五分钟上手Tensor
Tensor(张量)是PyTorch最核心的数据结构,可以是标量、向量、矩阵或任意高维数组,用法与NumPy的ndarray类似,但支持GPU加速。先建立一个直观印象:
python
import torch as t
x = t.Tensor(2, 3) # 按形状分配一个2×3矩阵,未初始化,数值是内存中的随机值
x = t.rand(2, 3) # 用[0,1)均匀分布随机初始化
print(x.shape) # torch.Size([2, 3])
print(x.size(1)) # 3,查看第1维(列)的大小,与x.size()[1]等价
注意t.Tensor(2, 3)和t.tensor([2, 3])是两回事:前者传入的是形状 ,后者传入的是数据。前者得到2×3的未初始化矩阵,后者得到只有2和3两个元素的一维张量。这是新手极易混淆的点。
加法有三种等价写法,还有一种会"就地修改"的写法:
python
y = t.rand(2, 3)
z1 = x + y # 写法一
z2 = t.add(x, y) # 写法二
result = t.Tensor(2, 3)
t.add(x, y, out=result) # 写法三:结果写入预分配的result
y.add(x) # 普通加法,返回新Tensor,y本身不变
y.add_(x) # inplace加法,y被修改
函数名以下划线_结尾的都是inplace操作,会直接修改调用者本身 ,如add_、t_。这是PyTorch全局统一的命名约定,记住这一条能避免很多莫名其妙的数值错误。
Tensor与NumPy互转
python
import numpy as np
a = t.ones(5)
b = a.numpy() # Tensor → ndarray
c = np.ones(5)
d = t.from_numpy(c) # ndarray → Tensor
d.add_(1)
print(c) # [2. 2. 2. 2. 2.],c也变了!
from_numpy得到的Tensor和原ndarray共享内存 ,转换本身几乎零开销,但一个变另一个也跟着变。想要独立副本,用t.tensor(c)或tensor.clone(),它们总是做数据拷贝。
从Tensor里取出Python数值
索引单个元素得到的是0维Tensor(一般称为scalar),用.item()取出真正的Python数值:
python
scalar = d[0]
print(scalar.shape) # torch.Size([]),0维
print(scalar.item()) # 2.0,Python float
一行代码上GPU
python
device = t.device("cuda:0" if t.cuda.is_available() else "cpu")
x = x.to(device) # 没有GPU时这行代码照样能跑,还在CPU上
y = y.to(x.device)
z = x + y
这种写法能让同一份代码在有无GPU的机器上都正常运行,是工程上的标准写法。顺带一提:小规模张量在GPU上未必更快,因为数据从内存搬到显存本身有开销,GPU的优势要在大规模数据和复杂运算下才能体现。
五、autograd:自动求导初体验
深度学习的训练核心是反向传播算梯度、梯度下降更新参数。PyTorch的autograd模块把"算梯度"这件事完全自动化了------只要给Tensor打上requires_grad=True标记:
python
x = t.ones(2, 2, requires_grad=True)
y = x.sum() # y = x[0][0]+x[0][1]+x[1][0]+x[1][1]
print(y.grad_fn) # <SumBackward0 ...>,记录了y是由什么运算得来的
y.backward() # 反向传播
print(x.grad) # tensor([[1., 1.], [1., 1.]]),每个元素的偏导都是1
grad_fn属性记录了生成该Tensor的运算,autograd靠它串起整张计算图。有一个关键行为必须现在就记住:梯度是累加的 。再调用一次y.backward(),x.grad会变成全2而不是保持全1:
python
y.backward()
print(x.grad) # tensor([[2., 2.], [2., 2.]]),累加了!
x.grad.data.zero_() # 清零(inplace操作)
y.backward()
print(x.grad) # tensor([[1., 1.], [1., 1.]]),清零后才正确
所以后面所有训练代码里,你都会看到每个迭代先optimizer.zero_grad()再backward()。关于autograd的完整机制(计算图、叶子节点、no_grad等),本系列第3篇会深入展开。
六、用nn.Module搭建LeNet
直接用autograd写深度网络还是太底层了。torch.nn模块构建在autograd之上,提供了神经网络专用的模块化接口,其中nn.Module是最重要的类:一个网络就是一个继承nn.Module的类,在__init__里声明含可学习参数的层,在forward里定义前向传播逻辑,反向传播由autograd自动搞定。
以经典的LeNet卷积网络为例(1998年LeCun提出,用于手写数字识别,输入32×32图像,经2个卷积层、2次下采样和3个全连接层得到10维输出):
python
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super().__init__() # 必须先调用父类构造函数
# Conv2d(输入通道数, 输出通道数, 卷积核大小)
self.conv1 = nn.Conv2d(1, 6, 5) # 输入单通道图像,输出6通道,5×5卷积核
self.conv2 = nn.Conv2d(6, 16, 5)
# 全连接层 y = Wx + b
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10) # 10类输出
def forward(self, x):
# 卷积 → ReLU激活 → 最大池化
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(x.size()[0], -1) # 展平成一维,-1表示自动计算
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
net = Net()
print(net) # 打印出网络结构
这段代码有几个要点:
- 有参数的层放
__init__,无参数的操作放forward。ReLU、池化没有可学习参数,所以用nn.functional里的函数形式直接写在forward里。 - 只要定义了
forward,backward就自动有了------这就是nn构建在autograd之上的含义。 forward里可以自由使用if、for、print等Python语法。
通过net.parameters()可以拿到所有可学习参数(优化器就吃这个),named_parameters()还能带上名字:
python
for name, p in net.named_parameters():
print(name, ':', p.size())
# conv1.weight : torch.Size([6, 1, 5, 5])
# conv1.bias : torch.Size([6])
# fc1.weight : torch.Size([120, 400]) ...共10组参数
前向传播就是把网络当函数调用:
python
input = t.randn(1, 1, 32, 32) # batch×通道×高×宽
out = net(input)
print(out.size()) # torch.Size([1, 10])
注意:torch.nn只接受mini-batch输入 ,即输入永远比单个样本多一个batch维度。nn.Conv2d要的是4维输入(nSamples × nChannels × H × W),只有一张图时用input.unsqueeze(0)把batch_size补成1。
七、实战:CIFAR-10图像分类
现在把所有零件组装起来,完整走一遍训练流程。CIFAR-10是含10个类别(飞机、汽车、鸟、猫等)的彩色图片数据集,每张图3通道、32×32分辨率。流程分五步:加载数据 → 定义网络 → 定义损失函数和优化器 → 训练 → 测试。
7.1 数据加载与预处理
torchvision是PyTorch官方的视觉工具包,内置常用数据集(datasets)、经典预训练模型(models)和数据预处理工具(transforms):
python
import torch as t
import torchvision as tv
import torchvision.transforms as transforms
# 预处理:转Tensor + 归一化到[-1, 1]
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])
# 首次运行会自动下载数据集(约100MB)
trainset = tv.datasets.CIFAR10(root='./data/', train=True,
download=True, transform=transform)
trainloader = t.utils.data.DataLoader(trainset, batch_size=4,
shuffle=True, num_workers=2)
testset = tv.datasets.CIFAR10(root='./data/', train=False,
download=True, transform=transform)
testloader = t.utils.data.DataLoader(testset, batch_size=4,
shuffle=False, num_workers=2)
classes = ('plane', 'car', 'bird', 'cat', 'deer',
'dog', 'frog', 'horse', 'ship', 'truck')
这里出现了两个重要抽象:Dataset是可按下标访问的数据集对象,返回(data, label);DataLoader是可迭代对象,负责把样本拼成batch、打乱顺序、多进程加速。遍历完一遍DataLoader就是一个epoch。
7.2 定义网络、损失函数和优化器
网络直接复用第六节的LeNet,只需把第一个卷积层的输入通道从1改成3(CIFAR-10是彩色图):
python
from torch import optim
class Net(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 6, 5) # 输入通道改为3
self.conv2 = nn.Conv2d(6, 16, 5)
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
x = x.view(x.size()[0], -1)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x)
net = Net()
criterion = nn.CrossEntropyLoss() # 分类任务标配:交叉熵损失
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
优化器的原理其实很朴素,SGD的更新规则就是weight = weight - learning_rate * gradient,完全可以手写。但torch.optim已经实现了SGD、Adam、RMSProp等主流优化算法,实际开发中直接用即可。
7.3 训练网络
所有神经网络的训练循环都是同一个模板:输入数据 → 梯度清零 → 前向传播算loss → 反向传播 → 更新参数。
python
for epoch in range(2):
running_loss = 0.0
for i, data in enumerate(trainloader, 0):
inputs, labels = data
optimizer.zero_grad() # 梯度清零,防止累加(第五节讲过原因)
outputs = net(inputs) # forward
loss = criterion(outputs, labels) # 计算损失
loss.backward() # backward,自动算梯度
optimizer.step() # 用梯度更新所有参数
running_loss += loss.item()
if i % 2000 == 1999: # 每2000个batch打印一次平均loss
print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 2000))
running_loss = 0.0
print('Finished Training')
训练2个epoch后,loss大致会从2.2左右降到1.3以下,说明网络在收敛。
7.4 测试网络
在整个测试集上统计准确率。测试阶段不需要求梯度,用t.no_grad()包裹可以显著提速并节省内存:
python
correct = 0
total = 0
with t.no_grad(): # 测试时关闭autograd
for data in testloader:
images, labels = data
outputs = net(images)
_, predicted = t.max(outputs, 1) # 取每行得分最高的类别
total += labels.size(0)
correct += (predicted == labels).sum()
print('10000张测试集中的准确率为: %f %%' % (100 * correct // total))
只训练2个epoch的LeNet能达到52%左右的准确率。这个数字不高,但已经远超随机猜测的10%------证明网络确实学到了东西。后续通过更深的网络、更多的训练轮次和数据增强,准确率还能大幅提升。
7.5 迁移到GPU训练
模型和数据一起搬过去即可,训练代码一行不用改:
python
device = t.device("cuda:0" if t.cuda.is_available() else "cpu")
net.to(device) # 模型上GPU
images = images.to(device) # 数据上GPU
labels = labels.to(device)
output = net(images)
loss = criterion(output, labels)
总结
本文完整搭建了PyTorch的开发环境,并跑通了第一个神经网络,要点回顾:
- 安装 :pip最省事,conda记得换清华源;包名是
torch;PyTorch自带CUDA运行时,无需与系统CUDA版本一致。 - 工具链 :IPython的
?内省、%timeit、%debug是学习利器;远程开发首推VS Code + Remote-SSH。 - Tensor :接口类NumPy,支持GPU;
Tensor(2,3)按形状创建、tensor([2,3])按数据创建;下划线结尾的函数是inplace操作;from_numpy共享内存。 - autograd :
requires_grad=True+backward()自动算梯度;梯度会累加,每次反向传播前必须清零。 - nn.Module :
__init__放有参数的层,forward写前向逻辑,backward自动实现;输入必须带batch维度。 - 训练五步曲 :数据加载(Dataset/DataLoader)→ 定义网络 → 损失函数+优化器 → 训练循环(zero_grad→forward→backward→step)→ 测试(用
no_grad)。
本文对Tensor和autograd都只是"初体验"级别的介绍,这两块正是PyTorch的地基。**下一篇《Tensor全解析:深度学习的数据基石》**将系统拆解Tensor的创建、索引、变形、广播、内存共享机制乃至storage/stride内部结构,让你真正吃透这个最核心的数据结构。