本文最后更新于:2020年10月4日 晚上

1.简介

1.1 Pytorch 简介

说到Pytorch首先要了解torch,Torch是一个与Numpy类似的张量(tensor)操作库,与Numpy不同的是Torch对GPU的支持很好,Lua是Torch的上层包装。

PyTorch和Torch使用包含所有相同性能的C库:TH, THC, THNN, THCUNN,并且它们将继续共享这些库。PyTorch和Torch都使用的是相同的底层,只是使用了不同的上层包装语言。

PyTorch是一个基于Torch的Python开源机器学习库,用于自然语言处理等应用程序。 它主要由Facebook的人工智能研究小组开发。

总结:Pytorch是一个Python包,提供两个高级功能:

  • 具有强大的GPU加速的张量计算(如NumPy)
  • 包含自动求导系统的深度神经网络

此外,具有以下特点:

  • 类似于Numpy,但可以使用GPU。
  • 可以用它定义深度学习模型,可以灵活地进行深度学习模型的训练和使用

此外具有如下优点:

  • 简洁、优雅、高效、快速
  • 设计追求最少的封装,避免重复造轮子
  • 符合人们的思维,让用户专注实现自己的想法
  • 良好的FAIR团队支持,确保Pytorch获得持续的开发更新
  • 文档完善,Pytorch的作者亲自维护论坛
  • 入门简单

1.2 环境搭建

笔者使用的操作系统为MAC OS Catelina 10.15.5,conda 4.8.3。可在官网选择环境,复制生成的命令安装即可。由于MAC不支持CUDA,我安装的是torch-1.5.1不含CUDA。

使用以下命令安装:

pip install torch torchvision

由于在安装过程中下载包的速度较慢,故将pip换成清华源:

pip install pip -U
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple

验证是否安装成功:

import torch
torch.__version__
#得到结果:1.5.1

安装Jupyter:

pip3 install --user jupyer

在当前目录打开Jupyter:

python -m IPython notebook

1.3 Pytorch深度学习:60分钟快速入门(官方)

1.3.1张量

from __future__ import print_function
import torch

创建未初始化的矩阵:x = torch.empty(5,3)

创建随机初始化的矩阵:x = torch.rand(5,3)

创建0填充的矩阵,数据类型为long: x = torch.zeros(5, 3, dtype=torch.long)

创建tensor并初始化: x = torch.tensor([5.5, 3])

根据现有张量创建张量。将重用原张量的属性,除非设置新的值进行覆盖:

x = x.new_ones(5, 3, dtype=torch.double)
x = torch.randn_like(x, dtype=torch.float) #随机产生一个与x形状相同的tensor

获取尺寸:print(x.size())

加法运算:

x+y, torch.add(x, y, out=result), y.add_(x) (任何以_结尾的操作都会替换原变量)

改变张量的维度和大小:torch.view()

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  #  size为-1 从其他维度推断
print(x.size(), y.size(), z.size())
#torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])

只有一个元素的张量使用x.item()得到Python数据类型的值

Numpy与Torch Tensor相互转换:

Torch Tensor与Numpy数组共享低层内存地址,修改一个会导致另一个的变化。

Torch Tensor转换为Numpy数组:

a = torch.ones(5)  #tensor([1., 1., 1., 1., 1.])
b = a.numpy() #[1. 1. 1. 1. 1.]

Numpy数组转换为Torch Tensor:

a = np.ones(5)
b = torch.from_numpy(a)

1.3.2 Autograd:自动求导

PyTorch 中所有神经网络的核心是 autograd 包。

autograd包为张量上的所有操作提供了自动求导。 它是一个在运行时定义的框架,这意味着反向传播是根据你的代码来确定如何运行,并且每次迭代可以是不同的。

张量(Tensor)

torch.Tensor是这个包的核心类。如果设置 .requires_gradTrue,那么将会追踪所有对于该张量的操作。 当完成计算后通过调用 .backward(),自动计算所有的梯度, 这个张量的所有梯度将会自动积累到 .grad 属性。

要阻止张量跟踪历史记录,可以调用.detach()方法将其与计算历史记录分离,并禁止跟踪它将来的计算记录。

为了防止跟踪历史记录(和使用内存),可以将代码块包装在with torch.no_grad():中。 在评估模型时特别有用,因为模型可能具有requires_grad = True的可训练参数,但是我们不需要梯度计算。.requires_grad_( ... ) 可以改变现有张量的 requires_grad属性。 如果没有指定的话,默认输入的flag是 False

在自动梯度计算中还有另外一个重要的类Function.

TensorFunction互相连接并生成一个非循环图,它表示和存储了完整的计算历史。 每个张量都有一个.grad_fn属性,这个属性引用了一个创建了TensorFunction(除非这个张量是用户手动创建的,即,这个张量的 grad_fnNone)。

如果需要计算导数,你可以在Tensor上调用.backward()。 如果Tensor是一个标量(即它包含一个元素数据)则不需要为backward()指定任何参数, 但是如果它有更多的元素,你需要指定一个gradient 参数来匹配张量的形状。

1.3.3 神经网络

使用torch.nn包来构建神经网络。

上一讲已经讲过了autogradnn包依赖autograd包来定义模型并求导。 一个nn.Module包含各个层和一个forward(input)方法,该方法返回output

它是一个简单的前馈神经网络,它接受一个输入,然后一层接着一层地传递,最后输出计算的结果。

神经网络的典型训练过程如下:

  1. 定义包含一些可学习的参数(或者叫权重)神经网络模型;
  2. 在数据集上迭代;
  3. 通过神经网络处理输入;
  4. 计算损失(输出结果和正确值的差值大小);
  5. 将梯度反向传播回网络的参数;
  6. 更新网络的参数,主要使用如下简单的更新原则: weight = weight - learning_rate * gradient

在模型中必须要定义 forward 函数,backward 函数(用来计算梯度)会被autograd自动创建。 可以在 forward 函数中使用任何针对 Tensor 的操作。

net.parameters()返回可被学习的参数(权重)列表和值

回顾:

  • torch.Tensor:一个用过自动调用 backward()实现支持自动梯度计算的 多维数组 , 并且保存关于这个向量的梯度 w.r.t.
  • nn.Module:神经网络模块。封装参数、移动到GPU上运行、导出、加载等。
  • nn.Parameter:一种变量,当把它赋值给一个Module时,被 自动 地注册为一个参数。
  • autograd.Function:实现一个自动求导操作的前向和反向定义,每个变量操作至少创建一个函数节点,每一个Tensor的操作都回创建一个接到创建Tensor编码其历史 的函数的Function节点。

重点如下:

  • 定义一个网络
  • 处理输入,调用backword

还剩:

  • 计算损失
  • 更新网络权重

一个损失函数接受一对 (output, target) 作为输入,计算一个值来估计网络的输出和目标值相差多少。

反向传播

调用loss.backward()获得反向传播的误差。

但是在调用前需要清除已存在的梯度,否则梯度将被累加到已存在的梯度。

更新权重

在实践中最简单的权重更新规则是随机梯度下降(SGD):

weight = weight - learning_rate * gradient

当使用神经网络是想要使用各种不同的更新规则时,比如SGD、Nesterov-SGD、Adam、RMSPROP等,PyTorch中构建了一个包torch.optim实现了所有的这些规则。 使用它们非常简单:

import torch.optim as optim
#create your optimizer
optimizer = optim.SGD(net.parameters(), lr=0.01)
#in your training loop:
optimizer.zero_grad()   # zero the gradient buffers
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()    # Does the update

1.3.4 训练一个分类器

关于数据?

一般情况下处理图像、文本、音频和视频数据时,可以使用标准的Python包来加载数据到一个numpy数组中。 然后把这个数组转换成 torch.*Tensor

  • 图像可以使用 Pillow, OpenCV
  • 音频可以使用 scipy, librosa
  • 文本可以使用原始Python和Cython来加载,或者使用 NLTK或 SpaCy 处理

特别的,对于图像任务,我们创建了一个包 torchvision,它包含了处理一些基本图像数据集的方法。这些数据集包括 Imagenet, CIFAR10, MNIST 等。除了数据加载以外,torchvision 还包含了图像转换器, torchvision.datasetstorch.utils.data.DataLoader

torchvision包不仅提供了巨大的便利,也避免了代码的重复。

在这个教程中,我们使用CIFAR10数据集,它有如下10个类别 :‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10的图像都是 3x32x32大小的,即,3颜色通道,32x32像素。

训练一个图像分类器

依次按照下列顺序进行:

  1. 使用torchvision加载和归一化CIFAR10训练集和测试集
  2. 定义一个卷积神经网络
  3. 定义损失函数
  4. 在训练集上训练网络
  5. 在测试集上测试网络
  1. 读取和归一化 CIFAR10

使用torchvision可以非常容易地加载CIFAR10。

import torch
import torchvision
import torchvision.transforms as transforms

torchvision的输出是[0,1]的PILImage图像,我们把它转换为归一化范围为[-1, 1]的张量。

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

2.定义神经网络

import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__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()

3.定义损失函数和优化器

我们使用交叉熵作为损失函数,使用带动量的随机梯度下降。

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

4.训练网络

只需在数据迭代器上循环,将数据输入给网络,并优化。

for epoch in range(2):  # 多批次循环

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 获取输入
        inputs, labels = data

        # 梯度置0
        optimizer.zero_grad()

        # 正向传播,反向传播,优化
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # 打印状态信息
        running_loss += loss.item()
        if i % 2000 == 1999:    # 每2000批次打印一次
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

5.在测试集上测试网络

我们在整个训练集上进行了2次训练,但是我们需要检查网络是否从数据集中学习到有用的东西。 通过预测神经网络输出的类别标签与实际情况标签进行对比来进行检测。 如果预测正确,我们把该样本添加到正确预测列表。 第一步,显示测试集中的图片并熟悉图片内容。

dataiter = iter(testloader)
images, labels = dataiter.next()

# 显示图片
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

在GPU上训练

把一个神经网络移动到GPU上训练就像把一个Tensor转换GPU上一样简单。并且这个操作会递归遍历有所模块,并将其参数和缓冲区转换为CUDA张量。

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 确认我们的电脑支持CUDA,然后显示CUDA信息:
print(device)

1.3.5 数据并行

在这个教程里,我们将学习如何使用 DataParallel 来使用多GPU。

PyTorch非常容易就可以使用多GPU,用如下方式把一个模型放到GPU上:

device = torch.device("cuda:0")
model.to(device)

GPU: 然后复制所有的张量到GPU上:

mytensor = my_tensor.to(device)

请注意,只调用my_tensor.to(device)并没有复制张量到GPU上,而是返回了一个copy。所以你需要把它赋值给一个新的张量并在GPU上使用这个张量。

在多GPU上执行前向和反向传播是自然而然的事。 但是PyTorch默认将只使用一个GPU。

使用DataParallel可以轻易的让模型并行运行在多个GPU上。

model = nn.DataParallel(model)

DataParallel会自动的划分数据,并将作业发送到多个GPU上的多个模型。 并在每个模型完成作业后,收集合并结果并返回。