2017 年初,Facebook 在机器学习和科学计算工具 Torch 的基础上,针对 Python 语言发布了一个全新的机器学习工具包 PyTorch。 因其在灵活性、易用性、速度方面的优秀表现,通过2年多的发展,目前 PyTorch 已经成为从业者最重要的研发工具之一。html
内容整理于 PyTorch 深度学习基础课程。
PyTorch 使用一种称之为 imperative / eager 的范式,即每一行代码都要求构建一个图,以定义完整计算图的一个部分。即便完整的计算图尚未构建好,咱们也能够独立地执行这些做为组件的小计算图,这种动态计算图被称为「define-by-run」方法。python
PyTorch 具备两个比较基础的库,全部基础操做都须要提早引入。下面咱们引入基础库。算法
import torch import torchvision
PyTorch 的基本数据单元是张量(Tensor),它其实是一种 N 维数组。数组
建立一个未初始化 5X3 的矩阵:缓存
x = torch.empty(5, 3)
建立一个随机初始化都矩阵:网络
x = torch.rand(5, 3)
建立一个 0 填充的矩阵,指定数据类型为 long:框架
x = torch.zeros(5, 3, dtype=torch.long)
建立一个张量并使用现有数据初始化:机器学习
x = torch.tensor([5.5, 3]) x
根据现有张量建立新张量:函数
x = x.new_ones(5, 3, dtype=torch.double) # new_* 方法来建立对象 x
覆盖 dtype,对象的 size 是相同的,只是值和类型发生了变化:工具
x = torch.randn_like(x, dtype=torch.float) x
获取张量的 size:
x.size()
加法1:
y = torch.rand(5, 3) x + y
加法2:
torch.add(x, y)
加法3:提供一个输出张量做为参数
result = torch.empty(5, 3) torch.add(x, y, out=result) result
加法4: 替换
y.add_(x) # 将 x 加到 y y
关于张量的操做,还有转置,索引,切片,数学运算,线性代数,随机数等。
将 PyTorch 张量转换为 NumPy 数组:
a = torch.ones(5) a
b = a.numpy() b
NumPy 数组转换成 PyTorch 张量时,可使用 from_numpy 完成:
import numpy as np a = np.ones(5) b = torch.from_numpy(a) np.add(a, 1, out=a) a, b
PyTorch 中全部神经网络的核心是 autograd。咱们先简单介绍一下这个包,而后训练一个神经网络。
autograd为张量上的全部操做提供了自动求导。它是一个在运行时定义的框架,这意味着反向传播是根据你的代码来肯定如何运行。torch.Tensor 是这个包的核心类。若是设置 .requires_grad 为 True,那么将会追踪全部对于该张量的操做。当完成计算后经过调用 .backward() 会自动计算全部的梯度,这个张量的全部梯度将会自动积累到 .grad 属性。这也就完成了自动求导的过程。
下面编写代码实际使用自动微分变量。
导入自动梯度的运算包,主要用Variable这个类
from torch.autograd import Variable
建立一个Variable,包裹了一个2*2张量,将须要计算梯度属性置为True
x = Variable(torch.ones(2, 2), requires_grad=True) x
按照张量的方式进行计算
y = x + 2 y.grad_fn #每一个Variable都有一个creator(创造者节点)
也能够进行复合运算,好比求均值mean
z = torch.mean(y * y) z.data #.data属性能够返回z所包裹的tensor
若是须要计算导数,你能够在 Tensor 上调用 .backward()。 若是 Tensor 是一个标量(即它包含一个元素数据)则不须要为 backward() 指定任何参数。可是,若是它有多个元素,你须要指定一个 gradient 参数来匹配张量的形状。
z.backward() #梯度反向传播 print(z.grad) # 无梯度信息 print(y.grad) # 无梯度信息 print(x.grad)
下面的例子中,会让矩阵 x 反复做用在向量 s 上,系统会自动记录中间的依赖关系和长路径。
s = Variable(torch.FloatTensor([[0.01, 0.02]]), requires_grad = True) #建立一个1*2的Variable(1维向量) x = Variable(torch.ones(2, 2), requires_grad = True) #建立一个2*2的矩阵型Variable for i in range(10): s = s.mm(x) #反复用s乘以x(矩阵乘法),注意s始终是1*2的Variable z = torch.mean(s) #对s中的各个元素求均值,获得一个1*1的scalar(标量,即1*1张量)
而后咱们获得了一个复杂的“深度”计算图。
z.backward() #在具备很长的依赖路径的计算图上用反向传播算法计算叶节点的梯度 print(x.grad) #x做为叶节点能够得到这部分梯度信息 print(s.grad) #s不是叶节点,没有梯度信息
PyTorch 中,咱们可使用 torch.nn
来构建神经网络。
前面已经讲过了 autograd
,torch.nn
依赖 autograd
来定义模型并求导。nn.Module
中包含了构建神经网络所需的各个层和 forward(input)
方法,该方法返回神经网络的输出。
下面给出一个示例网络结构,该网络也是经典的 LeNet。
它是一个简单的前馈神经网络,它接受一个输入,而后一层接着一层地传递,最后输出计算的结果。
神经网络的典型训练过程以下:
下面,参照上面的过程完成神经网络训练。
首先,定义上图示例的神经网络结构:
import torch.nn as nn import torch.nn.functional as F class Net(nn.Module): def __init__(self): super(Net, self).__init__() # 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 self.fc1 = nn.Linear(16 * 6 * 6, 120) # 6*6 from image dimension 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 only specify a single number x = F.max_pool2d(F.relu(self.conv2(x)), 2) x = x.view(-1, self.num_flat_features(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): 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() net
模型中必需要定义 forward
函数,backward
函数(用来计算梯度)会被 autograd
自动建立。能够在 forward
函数中使用任何针对 Tensor 的操做。
net.parameters()
返回可被学习的参数(权重)列表和值:
params = list(net.parameters()) print(len(params)) print(params[0].size()) # conv1's .weight
测试随机输入 $32 \times 32$。注意,网络(LeNet)指望的输入大小是 $32 \times 32$,若是使用 MNIST 数据集($28 \times 28$)来训练这个网络,请把图片大小从新调整到 $32 \times 32$。
input = torch.randn(1, 1, 32, 32) out = net(input) out
将全部参数的梯度缓存清零,而后进行随机梯度的的反向传播:
net.zero_grad() out.backward(torch.randn(1, 10))
在继续以前,咱们回顾一下到目前为止用到的类。
torch.Tensor
:自动调用 backward()
实现支持自动梯度计算的多维数组,而且保存关于这个向量的梯度。nn.Module
:神经网络模块。封装参数、移动到 GPU 上运行、导出、加载等。nn.Parameter
:变量,当把它赋值给一个 Module
时,被自动地注册为一个参数。autograd.Function
:实现自动求导操做的前向和反向定义,每一个变量操做至少建立一个函数节点。至此,咱们以及完成:
backword
。接下来还须要:
一个损失函数接受一对 (output, target)
做为输入,计算一个值来估计网络的输出和目标值相差多少。
torch.nn
中有不少不一样的 损失函数。nn.MSELoss
是一个比较简单的损失函数,它能够用来计算输出和目标间的 均方偏差,例如:
output = net(input) target = torch.randn(10) # 随机值做为样例 target = target.view(1, -1) # 使 target 和 output 的 shape 相同 criterion = nn.MSELoss() loss = criterion(output, target) loss
当咱们添加 loss
计算以后,若是使用它 .grad_fn
属性,将获得以下所示的计算图:
<pre style="font-size:14px; line-height:17px;" class="hljs">
input → conv2d → relu → maxpool2d → conv2d → relu → maxpool2d
→ view → linear → relu → linear → relu → linear → MSELoss → loss
</pre>
因此,当咱们调用 loss.backward()
时,会针对整个图执行微分操做。图中全部具备 requires_grad=True
的张量的 .grad
梯度会被累积起来。为了说明该状况,咱们回溯几个步骤:
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
调用 loss.backward()
得到反向传播的偏差。可是在调用前须要清除已存在的梯度,不然梯度将被累加到已存在的梯度。如今,咱们将调用 loss.backward()
,并查看 conv1
层的误差(bias)项在反向传播先后的梯度。下方的代码只能执行一次。
net.zero_grad() # 清除梯度 print('conv1.bias.grad before backward') print(net.conv1.bias.grad) loss.backward() print('conv1.bias.grad after backward') print(net.conv1.bias.grad)
torch.nn
中包含了各类用来构成深度神经网络构建块的模块和损失函数,你能够阅读 官方文档。
至此,剩下的最后一件事,那就是更新网络的权重。
在实践中最简单的权重更新规则是随机梯度降低(SGD):
$$\text{weight}=\text{weight}-\text{learning rate}*\text{gradient}$$
咱们可使用简单的 Python 代码实现这个规则:
learning_rate = 0.01 for f in net.parameters(): f.data.sub_(f.grad.data * learning_rate)
当你想使用其余不一样的优化方法,如 SGD、Nesterov-SGD、Adam、RMSPROP 等来更新神经网络参数时。能够借助于 PyTorch 中的 torch.optim
快速实现。使用它们很是简单:
import torch.optim as optim # 建立优化器 optimizer = optim.SGD(net.parameters(), lr=0.01) # 执行一次训练迭代过程 optimizer.zero_grad() # 梯度置零 output = net(input) loss = criterion(output, target) loss.backward() optimizer.step() # 更新 loss
多执行几回,观察损失值的变化状况。
上面,你已经看到如何去定义一个神经网络,计算损失值和更新网络的权重。接下来,咱们实现一个图像分类神经网络。
通常状况下处理图像、文本、音频和视频数据时,可使用标准的 Python 来加载数据为 NumPy 数组。而后把这个数组转换成torch.*Tensor
。
特别地,对于图像任务,PyTorch 提供了专门的包 torchvision
,它包含了处理一些基本图像数据集的方法。这些数据集包括 Imagenet, CIFAR10, MNIST 等。除了数据加载之外,torchvision
还包含了图像转换器,torchvision.datasets
和 torch.utils.data.DataLoader
数据加载器。
torchvision
不只提供了巨大的便利,也避免了代码的重复。接下来,咱们使用 CIFAR10 数据集完成分类器训练。该数据集有以下 10 个类别:airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck。CIFAR-10 的图像都是 $3 \times 32 \times 32$ ,即 3 个颜色通道,$32 \times 32$ 像素。
训练一个图像分类器,基本流程以下:
torchvision
加载和归一化 CIFAR10 训练集和测试集。使用 torchvision
能够很是容易地加载 CIFAR10。torchvision
的输出是 [0,1]
的 PILImage 图像,咱们把它转换为归一化范围为 [-1, 1]
的张量。
import torchvision import torchvision.transforms as transforms # 图像预处理步骤 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') trainloader, testloader
咱们可视化其中的一些训练图像。
import matplotlib.pyplot as plt %matplotlib inline def imshow(img): # 展现图像的函数 img = img / 2 + 0.5 # 反向归一化 npimg = img.numpy() plt.imshow(np.transpose(npimg, (1, 2, 0))) # 获取随机数据 dataiter = iter(trainloader) images, labels = dataiter.next() # 展现图像 imshow(torchvision.utils.make_grid(images)) # 显示图像标签 print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
从以前的神经网络一节复制神经网络代码,并修改输入为 3 通道图像。
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() net
咱们使用交叉熵做为损失函数,使用带动量的随机梯度降低完成参数优化。
criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) optimizer
有趣的训练过程开始了。只需在数据迭代器上循环,将数据输入给网络,并优化。因为使用了卷积神经网络,该训练时间较长,请耐心等待。
for epoch in range(1): # 迭代一次 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 % 200 == 199: # 每 200 批次打印一次 print('[%d, %5d] loss: %.3f' % (epoch + 1, i + 1, running_loss / 200)) running_loss = 0.0 print('Finished Training.')
咱们在整个训练集上进行了训练,可是须要检查网络是否从数据集中学习到有用的东西。通常状况下,能够经过预测神经网络输出的类别标签与实际状况标签进行对比来进行检测。若是预测正确,咱们把该样本添加到正确预测列表。
第一步,显示测试集中的图片并熟悉图片内容。
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)))
让咱们看看神经网络认为以上图片是什么。
outputs = net(images) outputs
输出是 10 个标签的权重。一个类别的权重越大,神经网络越认为它是这个类别。因此让咱们获得最高权重的标签。
_, predicted = torch.max(outputs, 1) print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] for j in range(4)))
结果看来不错。接下来让看看网络在整个测试集上的结果如何。
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))