《深度学习框架PyTorch入门与实践》系列:01-PyTorch入门与环境搭建之从安装到第一个神经网络 PyTorch入门与环境搭建从安装到第一个神经网络本文你将学到pip/conda两种方式安装PyTorch的正确姿势含GPU版本注意事项、IPython与Jupyter Notebook的高效用法自动补全、内省、魔术方法、调试、Tensor的基本操作、autograd自动求导的第一印象以及如何用nn.Module从零搭建LeNet卷积网络并在CIFAR-10数据集上完整跑通数据加载→训练→测试的全流程。读完本文你就拥有了一个能真正跑起来的深度学习环境和第一个自己训练出来的模型。文章目录PyTorch入门与环境搭建从安装到第一个神经网络一、为什么选PyTorch二、安装PyTorchpip与conda2.1 用pip安装推荐新手2.2 用conda安装三、搭好你的学习环境IPython与Jupyter3.1 IPython增强版Python Shell3.2 Jupyter Notebook交互式笔记本3.3 推荐的开发模式四、五分钟上手TensorTensor与NumPy互转从Tensor里取出Python数值一行代码上GPU五、autograd自动求导初体验六、用nn.Module搭建LeNet七、实战CIFAR-10图像分类7.1 数据加载与预处理7.2 定义网络、损失函数和优化器7.3 训练网络7.4 测试网络7.5 迁移到GPU训练总结一、为什么选PyTorchPyTorch是一款底层以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中依然完全适用。二、安装PyTorchpip与conda2.1 用pip安装推荐新手pip安装二进制包是最简单、最不容易出错的方式。到PyTorch官网pytorch.org首页依次选择操作系统、包管理器、语言和CUDA版本官网会直接生成对应的安装命令。以Linux pip CUDA 10.2为例pipinstalltorch1.8.0torchvision0.9.0torchaudio0.8.0如果不指定版本号pip会直接安装最新的稳定版对新手来说通常也是可以的。安装完成后验证一下importtorchastprint(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安装condainstallpytorch1.8.0torchvision0.9.0torchaudio0.8.0cudatoolkit10.2-cpytorch-c pytorch表示从PyTorch官方源下载国内速度可能比较慢。解决办法是把conda源换成清华镜像源配置方法搜索conda 清华镜像即可换源之后去掉-c pytorch参数下载速度会快很多。Windows下的安装流程完全一致同样是在官网选择对应选项拿到命令执行即可。三、搭好你的学习环境IPython与Jupyter学PyTorch最好的方式是边敲边看交互式环境远比直接写.py脚本高效。这里介绍三件套VS Code、IPython、Jupyter Notebook。3.1 IPython增强版Python Shellpipinstallipython命令行输入ipython启动。它比原生Python Shell强在这几个功能自动补全输入变量或函数前几个字母按Tab即可补全。内省对象后面加?查看文档加??直接看Python源码In[1]:importtorchast In[2]:t.abs?# 查看abs函数的帮助文档In[3]:t.nn.L1Loss??# 查看源码只能看到Python层看不到C实现魔术方法以%开头的特殊命令常用的有In[4]:at.Tensor(2,3)In[5]:%timeit a.sum()# 测量语句执行耗时7.34µs ±18.3ns per loop In[6]:%hist# 查看输入历史In[7]:%run-i a.py# 在当前命名空间中执行脚本文件In[8]:%debug# 程序报错后输入它直接跳到出错现场调试其中%debug值得单独强调当你%run main.py跑挂了之后输入%debug可以直接进入报错处的调试模式用u/d在调用栈里上下移动l查看上下文代码q退出。不需要重跑程序就能查错这在训练了半小时才崩的场景里能救命。脚本里也可以主动埋断点importipdb ipdb.set_trace()# 程序运行到这里会自动进入调试模式需先pip install ipdb3.2 Jupyter Notebook交互式笔记本pipinstalljupyter jupyter notebook# 启动后浏览器自动打开 http://127.0.0.1:8888Jupyter在IPython的能力之上提供了图形化界面支持代码、Markdown、图表混排特别适合做实验记录。IPython的自动补全、内省、魔术方法在Jupyter里全部可用。很多人的算力在远程服务器上Jupyter的远程访问配置流程如下# 1. 生成配置文件在~/.jupyter/下生成jupyter_notebook_config.pyjupyter notebook --generate-config# 2. 在Python中生成加密密码fromnotebook.authimportpasswd ppasswd()# 交互式输入两次密码print(p)# 得到形如 argon2:... 的加密串# 3. 修改配置文件中的三项c.NotebookApp.passworduargon2:...# 上一步的加密串c.NotebookApp.ip*# 允许任意ip访问c.NotebookApp.port9999# 绑定端口之后在服务器上启动jupyter notebook本地浏览器访问http://[服务器ip]:9999输入密码即可。如果打不开检查服务器防火墙是否放行了对应端口。3.3 推荐的开发模式对于本地写代码 远程服务器跑训练这一最常见的场景笔者最推荐的方案是VS Code Remote-SSH插件直接打开远程服务器上的文件夹用远程的Python解释器运行和调试。相比SSHFS挂载读写慢、用不了远程GPU和Git来回同步繁琐、难调试这种方式启动快、读数据方便配合VS Code的Python和Jupyter插件.py和.ipynb都能流畅编辑。四、五分钟上手TensorTensor张量是PyTorch最核心的数据结构可以是标量、向量、矩阵或任意高维数组用法与NumPy的ndarray类似但支持GPU加速。先建立一个直观印象importtorchast xt.Tensor(2,3)# 按形状分配一个2×3矩阵未初始化数值是内存中的随机值xt.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两个元素的一维张量。这是新手极易混淆的点。加法有三种等价写法还有一种会就地修改的写法yt.rand(2,3)z1xy# 写法一z2t.add(x,y)# 写法二resultt.Tensor(2,3)t.add(x,y,outresult)# 写法三结果写入预分配的resulty.add(x)# 普通加法返回新Tensory本身不变y.add_(x)# inplace加法y被修改函数名以下划线_结尾的都是inplace操作会直接修改调用者本身如add_、t_。这是PyTorch全局统一的命名约定记住这一条能避免很多莫名其妙的数值错误。Tensor与NumPy互转importnumpyasnp at.ones(5)ba.numpy()# Tensor → ndarraycnp.ones(5)dt.from_numpy(c)# ndarray → Tensord.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数值scalard[0]print(scalar.shape)# torch.Size([])0维print(scalar.item())# 2.0Python float一行代码上GPUdevicet.device(cuda:0ift.cuda.is_available()elsecpu)xx.to(device)# 没有GPU时这行代码照样能跑还在CPU上yy.to(x.device)zxy这种写法能让同一份代码在有无GPU的机器上都正常运行是工程上的标准写法。顺带一提小规模张量在GPU上未必更快因为数据从内存搬到显存本身有开销GPU的优势要在大规模数据和复杂运算下才能体现。五、autograd自动求导初体验深度学习的训练核心是反向传播算梯度、梯度下降更新参数。PyTorch的autograd模块把算梯度这件事完全自动化了——只要给Tensor打上requires_gradTrue标记xt.ones(2,2,requires_gradTrue)yx.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.]])每个元素的偏导都是1grad_fn属性记录了生成该Tensor的运算autograd靠它串起整张计算图。有一个关键行为必须现在就记住梯度是累加的。再调用一次y.backward()x.grad会变成全2而不是保持全1y.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维输出importtorch.nnasnnimporttorch.nn.functionalasFclassNet(nn.Module):def__init__(self):super().__init__()# 必须先调用父类构造函数# Conv2d(输入通道数, 输出通道数, 卷积核大小)self.conv1nn.Conv2d(1,6,5)# 输入单通道图像输出6通道5×5卷积核self.conv2nn.Conv2d(6,16,5)# 全连接层 y Wx bself.fc1nn.Linear(16*5*5,120)self.fc2nn.Linear(120,84)self.fc3nn.Linear(84,10)# 10类输出defforward(self,x):# 卷积 → ReLU激活 → 最大池化xF.max_pool2d(F.relu(self.conv1(x)),(2,2))xF.max_pool2d(F.relu(self.conv2(x)),2)xx.view(x.size()[0],-1)# 展平成一维-1表示自动计算xF.relu(self.fc1(x))xF.relu(self.fc2(x))xself.fc3(x)returnx netNet()print(net)# 打印出网络结构这段代码有几个要点有参数的层放__init__无参数的操作放forward。ReLU、池化没有可学习参数所以用nn.functional里的函数形式直接写在forward里。只要定义了forwardbackward就自动有了——这就是nn构建在autograd之上的含义。forward里可以自由使用if、for、print等Python语法。通过net.parameters()可以拿到所有可学习参数优化器就吃这个named_parameters()还能带上名字forname,pinnet.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组参数前向传播就是把网络当函数调用inputt.randn(1,1,32,32)# batch×通道×高×宽outnet(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和数据预处理工具transformsimporttorchastimporttorchvisionastvimporttorchvision.transformsastransforms# 预处理转Tensor 归一化到[-1, 1]transformtransforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5)),])# 首次运行会自动下载数据集约100MBtrainsettv.datasets.CIFAR10(root./data/,trainTrue,downloadTrue,transformtransform)trainloadert.utils.data.DataLoader(trainset,batch_size4,shuffleTrue,num_workers2)testsettv.datasets.CIFAR10(root./data/,trainFalse,downloadTrue,transformtransform)testloadert.utils.data.DataLoader(testset,batch_size4,shuffleFalse,num_workers2)classes(plane,car,bird,cat,deer,dog,frog,horse,ship,truck)这里出现了两个重要抽象Dataset是可按下标访问的数据集对象返回(data, label)DataLoader是可迭代对象负责把样本拼成batch、打乱顺序、多进程加速。遍历完一遍DataLoader就是一个epoch。7.2 定义网络、损失函数和优化器网络直接复用第六节的LeNet只需把第一个卷积层的输入通道从1改成3CIFAR-10是彩色图fromtorchimportoptimclassNet(nn.Module):def__init__(self):super().__init__()self.conv1nn.Conv2d(3,6,5)# 输入通道改为3self.conv2nn.Conv2d(6,16,5)self.fc1nn.Linear(16*5*5,120)self.fc2nn.Linear(120,84)self.fc3nn.Linear(84,10)defforward(self,x):xF.max_pool2d(F.relu(self.conv1(x)),(2,2))xF.max_pool2d(F.relu(self.conv2(x)),2)xx.view(x.size()[0],-1)xF.relu(self.fc1(x))xF.relu(self.fc2(x))returnself.fc3(x)netNet()criterionnn.CrossEntropyLoss()# 分类任务标配交叉熵损失optimizeroptim.SGD(net.parameters(),lr0.001,momentum0.9)优化器的原理其实很朴素SGD的更新规则就是weight weight - learning_rate * gradient完全可以手写。但torch.optim已经实现了SGD、Adam、RMSProp等主流优化算法实际开发中直接用即可。7.3 训练网络所有神经网络的训练循环都是同一个模板输入数据 → 梯度清零 → 前向传播算loss → 反向传播 → 更新参数。forepochinrange(2):running_loss0.0fori,datainenumerate(trainloader,0):inputs,labelsdata optimizer.zero_grad()# 梯度清零防止累加第五节讲过原因outputsnet(inputs)# forwardlosscriterion(outputs,labels)# 计算损失loss.backward()# backward自动算梯度optimizer.step()# 用梯度更新所有参数running_lossloss.item()ifi%20001999:# 每2000个batch打印一次平均lossprint([%d, %5d] loss: %.3f%(epoch1,i1,running_loss/2000))running_loss0.0print(Finished Training)训练2个epoch后loss大致会从2.2左右降到1.3以下说明网络在收敛。7.4 测试网络在整个测试集上统计准确率。测试阶段不需要求梯度用t.no_grad()包裹可以显著提速并节省内存correct0total0witht.no_grad():# 测试时关闭autogradfordataintestloader:images,labelsdata outputsnet(images)_,predictedt.max(outputs,1)# 取每行得分最高的类别totallabels.size(0)correct(predictedlabels).sum()print(10000张测试集中的准确率为: %f %%%(100*correct//total))只训练2个epoch的LeNet能达到52%左右的准确率。这个数字不高但已经远超随机猜测的10%——证明网络确实学到了东西。后续通过更深的网络、更多的训练轮次和数据增强准确率还能大幅提升。7.5 迁移到GPU训练模型和数据一起搬过去即可训练代码一行不用改devicet.device(cuda:0ift.cuda.is_available()elsecpu)net.to(device)# 模型上GPUimagesimages.to(device)# 数据上GPUlabelslabels.to(device)outputnet(images)losscriterion(output,labels)总结本文完整搭建了PyTorch的开发环境并跑通了第一个神经网络要点回顾安装pip最省事conda记得换清华源包名是torchPyTorch自带CUDA运行时无需与系统CUDA版本一致。工具链IPython的?内省、%timeit、%debug是学习利器远程开发首推VS Code Remote-SSH。Tensor接口类NumPy支持GPUTensor(2,3)按形状创建、tensor([2,3])按数据创建下划线结尾的函数是inplace操作from_numpy共享内存。autogradrequires_gradTruebackward()自动算梯度梯度会累加每次反向传播前必须清零。nn.Module__init__放有参数的层forward写前向逻辑backward自动实现输入必须带batch维度。训练五步曲数据加载Dataset/DataLoader→ 定义网络 → 损失函数优化器 → 训练循环zero_grad→forward→backward→step→ 测试用no_grad。本文对Tensor和autograd都只是初体验级别的介绍这两块正是PyTorch的地基。**下一篇《Tensor全解析深度学习的数据基石》**将系统拆解Tensor的创建、索引、变形、广播、内存共享机制乃至storage/stride内部结构让你真正吃透这个最核心的数据结构。