60分钟闪击速成PyTorch(Deep Learning with PyTorch: A 60 Minute Blitz)学习笔记

简介: 本笔记是我学习 Deep Learning with PyTorch: A 60 Minute Blitz 这一PyTorch官方教程后的学习笔记。该教程在官网上更新过,因此未来还可能继续更新。以后的读者所见的版本可能与我学的不同。

1. Tensor


教程notebook:https://github.com/PolarisRisingWar/Note-of-PyTorch-60-Minutes-Tutorial/blob/master/tensor_tutorial.ipynb


  1. 什么是Tensor?

torch中的Tensor是一种数据结构,其实在使用上与Python的list、numpy的array、ndarray等数据结构比较类似,可以当成一个多维数组来用。

在数学上对张量这一专业名词有特定的定义,但是反正大概理解成一个多维数组就够用了。


  1. 如何生成Tensor?

torch包中提供了一系列直接生成Tensor的函数,如 zeros()、ones()、rand() 等。

此外,可以用 tensor(data) 函数直接将某一表示数组的数据(接受list、numpy.ndarray等格式)转换为Tensor。

也可以通过 from_numpy(data) 函数将numpy.ndarray格式的数据转换为Tensor。

还可以生成一个与其他Tensor具有相同dtype和device等属性的Tensor,使用torch的 ones_like(data) 或 rand_like(data) 等函数,或Tensor的 new_ones() 等函数。


  1. Tensor的属性:shape(返回torch.Size格式)(也可以用size()函数),dtypedevice(可见PyTorch Python API详解大全(持续更新ing…)_诸神缄默不语的博客-CSDN博客第0节的相关介绍)


  1. Tensor可以进行的操作:类似numpy的API;改变原数据的原地操作在函数后面加_就可以(一般不建议这么操作)
  • 索引
  • 切片
  • join:cat(tensors)或stack(tensors)
  • 加法:add()或+
  • 乘法:对元素层面的乘法mul()或*,矩阵乘法matmul()或@
  • resize

(1)reshape()或view()(建议使用reshape(),因为仅使用view()可能会造成Tensor不contiguous的问题,可参考PyTorch Python API详解大全(持续更新ing…)_诸神缄默不语的博客-CSDN博客一文脚注3的介绍)

(2)squeeze()去掉长度为1的维度

(3)unsqueeze()增加一个维度(长度为1)

(4)transpose()转置2个维度


  1. Tensor.numpy()可以将Tensor转换为numpy数据。反向的操作见上面序号2部分。

注意这两方向的转换的数据对象都是占用同一储存空间,修改后变化也会体现在另一对象上。


  1. item()函数返回仅有一个元素的Tensor的该元素值。


2. Autograd


教程notebook:https://github.com/PolarisRisingWar/Note-of-PyTorch-60-Minutes-Tutorial/blob/master/autograd_tutorial.ipynb


  1. torch.autograd是PyTorch提供的自动求导包,非常好用,可以不用自己算神经网络偏导了。


  1. 神经网络构成、常识部分这里就不再详细介绍了,总之大概就是:
  • 神经网络由权重、偏置等参数决定的函数构成,这些参数在PyTorch中都储存在Tensor里
  • 神经网络的训练包括前向传播和反向传播两部分,前向传播就是用函数计算预测值,反向传播就是通过这一预测值产生的error/loss来更新参数(通过梯度下降的方式)

对反向传播算法的介绍,教程中提供了3b1b的视频作为参考。原链接是YouTube视频,不方便的读者可以看B站上面的:【官方双语】深度学习之反向传播算法 上/下 Part 3 ver 0.9 beta  下篇:反向传播的微积分原理

(对上一视频所属的3B1B深度学习视频系列,我也撰写了学习笔记,可参考:3B1B深度学习系列视频学习笔记_诸神缄默不语的博客-CSDN博客 )


  1. 神经网络的一轮训练:
  • 前向传播:prediction = model(data)
  • 反向传播

(1)计算loss

(2)loss.backward()(autograd会在这一步计算参数的梯度,存在相应参数Tensor的grad属性中)

(3)更新参数

      1)加载optimizer(通过torch.optim)

      2)optimizer.step()对参数使用梯度下降的方法进行更新(梯度来源自参数的grad属性)

本节以下内容都属于原理部分,可以直接跳过


  1. autograd实现细节:一个示例

image.png


  1. 教程中提供了aotugrad矢量分析方面的解释,我没看懂,以后学了矢量分析看懂了再说。


  1. autograd的计算图

image.png


  1. 将Tensor的requires_grad属性设置为False,可以将其排除在DAG之外,autograd就不会计算它的梯度。

在神经网络中,这种不需要计算梯度的参数叫frozen parameters。可以冻结不需要知道梯度的参数(节省计算资源),也可以在微调预训练模型时使用(此时往往冻结绝大多数参数,仅调整classifier layer参数,以在新标签上做预测)。

类似功能也可以用上下文管理器torch.no_grad()实现。


3. Neural Networks


教程notebook:https://github.com/PolarisRisingWar/Note-of-PyTorch-60-Minutes-Tutorial/blob/master/neural_networks_tutorial.ipynb


  1. 神经网络可以通过torch.nn包搭建(torch.nn包里预定义的层调用了torch.nn.functional包的函数)
  2. nn.Module包含了网络层
  3. forward(input)方法返回输出结果
  4. 示例:简单前馈神经网络convnet

image.png


  1. 典型的神经网络训练流程:(从下一序号开始对每一部分进行详细介绍)
  • 定义具有可训练参数(或权重)的神经网络
  • 用数据集进行多次迭代

(1)前向传播

(2)计算loss

(3)计算梯度

(4)使用梯度下降法更新参数


  1. 定义网络

只需要定义forward()方法,backward()方法会自动定义(因为用了autograd)。在forward()方法中可以进行任何Tensor操作。

本部分代码定义了一个卷积→池化→卷积→池化→仿射变换→仿射变换→仿射变换的叠叠乐网络。

(这个网络我有一点没搞懂,就是仿射变换前一步,既然已知数据维度是16*6*6,为什么还要用num_flat_features()这个方法算一遍啊……?)

import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        #Conv2d是在输入信号(由几个平面图像构成)上应用2维卷积
        # 1 input image channel, 6 output channels, 3x3 square convolution kernel
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.conv2 = nn.Conv2d(6, 16, 3)
        # an affine operation: y = Wx + b
        #affine仿射的
        self.fc1 = nn.Linear(16 * 6 * 6, 120)
        #16是conv2的输出通道数,6*6是图像维度
        #(32*32的原图,经conv1卷后是6*30*30,经池化后是6*15*15,经conv2卷后是16*13*13,经池化后是16*6*6)
        #经过网络层后的维度数计算方式都可以看网络的类的文档来查到
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square, you can specify with a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        #将x转化为元素不变,尺寸为[-1,self.num_flat_features(x)]的Tensor
        #-1的维度具体是多少,是根据另一维度计算出来的
        #由于另一维度是x全部特征的长度,所以这一步就是把x从三维张量拉成一维向量
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
    def num_flat_features(self, x):
    #计算得到x的特征总数(就是把各维度乘起来)
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features
net = Net()
print(net)


输出:

Net(
  (conv1): Conv2d(1, 6, kernel_size=(3, 3), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
  (fc1): Linear(in_features=576, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)


模型的可学习参数存储在net.parameters()中。这个方法的返回值是一个迭代器,包含了模型及其所有子模型的参数


  1. 前向传播:out = net(input)


  1. 反向传播:先将参数梯度缓冲池清零(否则梯度会累加),再反向传播(此处使用一个随机矩阵)

net.zero_grad()1

out.backward(torch.randn(1, 10))

如果有计算出损失函数,上一行代码应为:loss.backward()


  1. 注意:torch.nn只支持mini-batch,所以如果只有一个输入数据的话,可以用input.unsqueeze(0)方法创造一个伪batch维度


  1. 损失函数

torch.nn包中定义的损失函数文档:https://pytorch.org/docs/nn.html#loss-functions

以MSELoss为例:

criterion = nn.MSELoss()

loss = criterion(output, target)

对如此得到的loss,其grad_fn组成的DAG为:

image.png

所以,调用loss.backward()后,所有张量的梯度都会得到更新

直观举例:

print(loss.grad_fn)  # MSELoss
print(loss.grad_fn.next_functions[0][0])  # Linear
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])  # ReLU


输出:

<MseLossBackward object at 0x0000015F84F7E388>
<AddmmBackward object at 0x0000015FFFF98E48>
<AccumulateGrad object at 0x0000015F84F7E388>
更新网络中的权重:step()
使用torch.optim中的优化器(lr入参是学习率,这个学习率也可以通过torch.optim.lr_scheduler包实现learning rate scheduling操作2)
import torch.optim as optim
# create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)
# in your training loop:
optimizer.zero_grad()   #原因见前
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update



4. CIFAR10 (Example: Image Classification)


教程notebook:https://github.com/PolarisRisingWar/Note-of-PyTorch-60-Minutes-Tutorial/blob/master/cifar10_tutorial.ipynb


  1. 各种形式的数据都可以通过Python标准库转换为numpy数组格式,然后再转换为Tensor格式
  • 图像:Pillow, OpenCV
  • 音频:scipy and librosa
  • 文本:raw Python or Cython based loading, or NLTK and SpaCy
  1. 对计算机视觉任务,PyTorch有专门的包torchvision,可以直接通过torchvision.datasets和torch.utils.data.DataLoader下载Imagenet, CIFAR10, MNIST等常用数据集并对其进行数据转换
  2. 在本教程中使用的是CIFAR10。图片是3通道,大小为32*32。标签为图像类别(共10类)


Step1:下载并规范化数据集

通过torch.utils.data.DataLoader加载torchvision.datasets中的数据集,返回迭代器

使用torchvision.transforms包进行规范化



Step2:定义一个卷积神经网络

image.png

import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        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 = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
net = Net()


Step3:定义损失函数和优化器

import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)


Step4:训练神经网络

for epoch in range(2):  # loop over the dataset multiple times
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the inputs; data is a list of [inputs, labels]
        inputs, labels = data
        # zero the parameter gradients
        optimizer.zero_grad()
        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        # print statistics
        running_loss += loss.item()  
        #loss.item()文档:https://pytorch.org/docs/stable/tensors.html?highlight=item#torch.Tensor.item
        #Returns the value of this tensor as a standard Python number.
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0
print('Finished Training')


将模型保存到本地:

PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)


对模型存取的更多细节详见:SERIALIZATION SEMANTICS


Step5:测试神经网络

加载模型文件:

net = Net()
net.load_state_dict(torch.load(PATH))


用测试集输出向量中最大的元素代表的类作为输出

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))


在GPU上训练

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net.to(device)
inputs, labels = data[0].to(device), data[1].to(device)  #把每一步的输入数据和目标数据也转到GPU上


注意,直接调用 my_tensor.to(device) 将返回一个在GPU上的 my_tensor 的副本而不是直接重写 my_tensor,因此在后续训练的过程中需要将其赋予一个新Tensor,然后用新Tensor来训练。



5. 多GPU数据并行训练


原教程:DATA PARALLELISM


代码:Note-of-PyTorch-60-Minutes-Tutorial/dp.py at master · PolarisRisingWar/Note-of-PyTorch-60-Minutes-Tutorial


这个教程主要讲如何使用 DataParallel 这个类(简称DP)。文档:DataParallel — PyTorch 1.10 documentation

PyTorch常用的另一个多卡训练的类是 DistributedDataParallel(简称DDP。文档:DistributedDataParallel — PyTorch 1.10.0 documentation)。那个类怎么用我还没搞懂,我就先把这个 DataParallel 搞懂了来写一写……


核心代码:

model = nn.DataParallel(model)


在单卡上写好的model直接调用这个类,然后别的都跟单卡形式下的一样就可以了。程序会自动把数据拆分放到所有已知的GPU上来运行。

看我在GitHub上写的代码,数据是直接从第一维拆开平均放到各个GPU上,相当于每个GPU放 batch_size/卡数 个样本。


设置已知的GPU,可以在运行代码的 python 加上 CUDA_VISIBLE_DEVICES 参数,举例:

CUDA_VISIBLE_DEVICES=0,1,2,3 python example.py


注意如果要使用nohup的话,这个参数要加在nohup的还前面,举例:

CUDA_VISIBLE_DEVICES=0,1,2,3 nohup python -u example.py >> nohup_output.log 2>&1 &


如果不设置则默认为所有GPU。


对GPU数量的计数可以使用 torch.cuda.device_count() 代码。


原理我还没怎么搞懂,但是据说直接用 DataParallel 不太好,有各卡空间不均衡之类的问题,建议使用 DistributedDataParallel。我学会那个类的使用方法以后大约也会写篇笔记博文的。


其他多卡运行PyTorch模型的资料可参考:


PyTorch分布式训练简介

Distributed communication package - torch.distributed — PyTorch 1.10.0 documentation


6. 衍生学习资料


微调torchvision模型教程


autograd具体机制


逆向自动求导法应用实例 colab版 由于众所周知的有些读者可能无法登入colab,因此我也下载了原notebook文件放在了GitHub公开项目上供便捷下载,网址:https://github.com/PolarisRisingWar/Note-of-PyTorch-60-Minutes-Tutorial/blob/master/Simple_Grad.ipynb


训练神经网络玩视频游戏 REINFORCEMENT LEARNING (DQN) TUTORIAL


在ImageNet数据集上训练ResNet ImageNet training in PyTorch


用GAN生成人脸 Deep Convolution Generative Adversarial Networks


用Recurrent LSTM networks训练一个词级别的语言模型 Word-level language modeling RNN


更多PyTorch应用示例


更多PyTorch教程


在论坛上讨论PyTorch


在Slack上与其他PyTorch学习者交流


7. 其他正文及脚注未提及的参考资料


pytorch tutorial : A 60 Minute Blitz_Gitabytes的博客-CSDN博客:这一篇在原教程的基础上还有所衍生,比如讲了个用torch.optim.lr_scheduler.ExponentialLR的代码例子。


torch.nn.Module.zero_grad()的使用_敲代码的小风的博客-CSDN博客_torch zero_grad:这一篇用代码演示了一下PyTorch的梯度积累和清零的过程。


optimizer.zero_grad()和net.zero_grad()_前进ing_嘟嘟的博客-CSDN博客


注意这个zero_grad()方法在此处是用在了net(一个网络(nn.Module子类)实例)上,后文的zero_grad()方法则是用在了optimizer(优化器)上。

前者的文档见https://pytorch.org/docs/stable/generated/torch.nn.Module.html#torch.nn.Module.zero_grad,是将其所有参数梯度清零。

后者的文档见https://pytorch.org/docs/stable/optim.html#torch.optim.Optimizer.zero_grad,是将优化器上所有参数梯度清零。

注意到我们往优化器中传的就是这个网络的所有参数:optimizer = optim.SGD(net.parameters(), lr=0.01),所以我觉得这两种写法应该是一样的(因为model.parameters()返回一个Tensor的迭代器,Tensor作为一个可变object应该是直接传入引用,所以应该一样)。但是我还没有试验过,如果有闲情逸致的话可以试试。

另,如果有frozen parameters,或者优化器只优化一部分模型中的参数,可能不等价。这一部分我也还没有试验过。

此外参考为什么要使用 zero_grad()?_马鹏森的博客-CSDN博客_net.zero_grad()一文,还可以实现对特定Variable梯度清零:Variable.grad.data.zero_() ↩︎


这个东西我会在写李宏毅机器学习笔记的时候细讲的。 ↩︎


相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
数据采集 PyTorch 数据处理
Pytorch学习笔记(3):图像的预处理(transforms)
Pytorch学习笔记(3):图像的预处理(transforms)
1489 1
Pytorch学习笔记(3):图像的预处理(transforms)
|
机器学习/深度学习 PyTorch 算法框架/工具
Pytorch学习笔记(1):基本概念、安装、张量操作、逻辑回归
Pytorch学习笔记(1):基本概念、安装、张量操作、逻辑回归
396 0
Pytorch学习笔记(1):基本概念、安装、张量操作、逻辑回归
|
PyTorch 算法框架/工具 索引
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
741 0
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
|
机器学习/深度学习 缓存 监控
Pytorch学习笔记(7):优化器、学习率及调整策略、动量
Pytorch学习笔记(7):优化器、学习率及调整策略、动量
1072 0
Pytorch学习笔记(7):优化器、学习率及调整策略、动量
|
2月前
|
PyTorch 算法框架/工具
Pytorch学习笔记(五):nn.AdaptiveAvgPool2d()函数详解
PyTorch中的`nn.AdaptiveAvgPool2d()`函数用于实现自适应平均池化,能够将输入特征图调整到指定的输出尺寸,而不需要手动计算池化核大小和步长。
202 1
Pytorch学习笔记(五):nn.AdaptiveAvgPool2d()函数详解
|
2月前
|
算法 PyTorch 算法框架/工具
Pytorch学习笔记(九):Pytorch模型的FLOPs、模型参数量等信息输出(torchstat、thop、ptflops、torchsummary)
本文介绍了如何使用torchstat、thop、ptflops和torchsummary等工具来计算Pytorch模型的FLOPs、模型参数量等信息。
392 2
|
2月前
|
PyTorch 算法框架/工具
Pytorch学习笔记(六):view()和nn.Linear()函数详解
这篇博客文章详细介绍了PyTorch中的`view()`和`nn.Linear()`函数,包括它们的语法格式、参数解释和具体代码示例。`view()`函数用于调整张量的形状,而`nn.Linear()`则作为全连接层,用于固定输出通道数。
126 0
Pytorch学习笔记(六):view()和nn.Linear()函数详解
|
2月前
|
PyTorch 算法框架/工具
Pytorch学习笔记(四):nn.MaxPool2d()函数详解
这篇博客文章详细介绍了PyTorch中的nn.MaxPool2d()函数,包括其语法格式、参数解释和具体代码示例,旨在指导读者理解和使用这个二维最大池化函数。
191 0
Pytorch学习笔记(四):nn.MaxPool2d()函数详解
|
2月前
|
PyTorch 算法框架/工具
Pytorch学习笔记(三):nn.BatchNorm2d()函数详解
本文介绍了PyTorch中的BatchNorm2d模块,它用于卷积层后的数据归一化处理,以稳定网络性能,并讨论了其参数如num_features、eps和momentum,以及affine参数对权重和偏置的影响。
275 0
Pytorch学习笔记(三):nn.BatchNorm2d()函数详解
|
2月前
|
机器学习/深度学习 PyTorch TensorFlow
Pytorch学习笔记(二):nn.Conv2d()函数详解
这篇文章是关于PyTorch中nn.Conv2d函数的详解,包括其函数语法、参数解释、具体代码示例以及与其他维度卷积函数的区别。
303 0
Pytorch学习笔记(二):nn.Conv2d()函数详解