PyTorch进行深度学习入门
一、PyTorch是什么?
这是一个基于Python的科学计算软件包,针对两组受众:
①、NumPy的替代品,可以使用GPU的强大功能
②、深入学习研究平台,提供最大的灵活性和速度
二、入门
①、张量(tensor):
张量与NumPy的ndarray类似,另外还有Tensors也可用于GPU以加速计算:
1 from __future__ import print_function 2 import torch
构造一个未初始化的5x3矩阵:
1 x = torch.empty(5, 3) 2 print(x)
构造一个随机初始化的矩阵:
1 x = torch.rand(5, 3) 2 print(x)
构造一个矩阵填充的零和dtype long:
1 x = torch.zeros(5, 3, dtype=torch.long) 2 print(x)
直接从数据构造张量:
1 x = torch.tensor([5.5, 3]) 2 print(x)
根据现有的张量创建张量。除非用户提供新值,否则这些方法将重用输入张量的属性,例如dtype:
1 x = x.new_ones(5, 3, dtype=torch.double) # new_* methods take in sizes 2 print(x) 3 4 x = torch.randn_like(x, dtype=torch.float) # override dtype! 5 print(x) # result has the same size
6 print(x.size()) #x的尺寸,torch.Size
实际上是一个元组,因此它支持所有元组操作。
②、操作
增加:语法1
1 y = torch.rand(5, 3) 2 print(x + y)
增加:语法2
1 print(torch.add(x, y))
增加:提供输出张量作为参数
1 result = torch.empty(5, 3) 2 torch.add(x, y, out=result) 3 print(result)
增加:就地。注意:任何使原位张量变形的操作都是用_
。后固定的。例如:x.copy_(y)
,x.t_()
,将改变x
。
1 # adds x to y 2 y.add_(x) 3 print(y)
使用标准的NumPy索引
1 print(x[:, 1])
调整大小:如果要调整张量/重塑张量,可以使用torch.view
:
1 x = torch.randn(4, 4) 2 y = x.view(16) 3 z = x.view(-1, 8) # the size -1 is inferred from other dimensions 4 print(x.size(), y.size(), z.size())
如果你有一个元素张量,用于.item()
获取值作为Python数字:
1 x = torch.randn(1) 2 print(x) 3 print(x.item())
③、NumPy Bridge:将Torch Tensor转换为NumPy阵列(反之亦然)是一件轻而易举的事。Torch Tensor和NumPy阵列将共享其底层内存位置,更改一个将改变另一个。
将Torch Tensor转换为NumPy数组:
1 a = torch.ones(5) 2 print(a) 3 b = a.numpy() 4 print(b)
了解numpy数组的值如何变化。
1 a.add_(1) 2 print(a) 3 print(b)
将NumPy数组转换为Torch Tensor:了解更改np阵列如何自动更改Torch Tensor
1 import numpy as np 2 a = np.ones(5) 3 b = torch.from_numpy(a) 4 np.add(a, 1, out=a) 5 print(a) 6 print(b)
除了CharTensor之外,CPU上的所有Tensors都支持转换为NumPy并返回。
④、CUDA Tensors
可以使用该.to方法将张量移动到任何设备上。
1 # let us run this cell only if CUDA is available 2 # We will use ``torch.device`` objects to move tensors in and out of GPU 3 if torch.cuda.is_available(): 4 device = torch.device("cuda") # a CUDA device object 5 y = torch.ones_like(x, device=device) # directly create a tensor on GPU 6 x = x.to(device) # or just use strings ``.to("cuda")`` 7 z = x + y 8 print(z) 9 print(z.to("cpu", torch.double)) # ``.to`` can also change dtype together!
⑤、AUTOGRAD:自动分化
autograd
包中是PyTorch中所有神经网络的核心。首先简要地访问它,然后将去训练第一个神经网络。该autograd
软件包为Tensors上的所有操作提供自动区分。它是一个逐个运行的框架,backprop由自己的代码运行方式定义,并且每个迭代都可以不同。
张量:torch.Tensor
是包的核心类。如果将其属性设置 .requires_grad
为True
,则会开始跟踪其上的所有操作。完成计算后,可以调用.backward()
并自动计算所有渐变。该张量的梯度将累积到.grad
属性中。要阻止张量跟踪历史记录,可以调用.detach()
它将其从计算历史记录中分离出来,并防止将来的计算被跟踪。
要防止跟踪历史记录(和使用内存),还可以将代码块包装在其中。这在评估模型时尤其有用,因为模型可能具有可训练的参数 ,但不需要梯度。with torch.no_grad():
requires_grad=True,还有一个类对于autograd实现非常重要 - a
Function,Tensor 和 function是否相互连接并建立一个非循环图,编码了完整的计算历史,每个张量都有一个.grad_fn属性,该属性引用一个创建了张量的函数(用户创建的张量除外——它们的grad_fn是None。如果你想去计算导数,可以在一个张量上调用.backward(),如果张量是标量(即它包含一个元素数据),不需要为.backward()指定任何参数,然而,如果它有更多的元素,需要指定一个梯度参数,这是一个匹配形状的张量。
1 import torch
创建一个张量,并设置requires_grad=True来跟踪计算:
1 x = torch.ones(2, 2, requires_grad=True) 2 print(x)
张量运算:
1 y = x + 2 2 print(y)
y是一个操作的结果,所以它有一个grad_fn。
1 print(y.grad_fn)
更多关于y的运算:
1 z = y * y * 3 2 out = z.mean() 3 print(z, out)
.requires_grad_ (…)更改现有张量的requires_grad标志。如果没有给出输入标志,则默认为False。
1 a = torch.randn(2, 2) 2 a = ((a * 3) / (a - 1)) 3 print(a.requires_grad) 4 a.requires_grad_(True) 5 print(a.requires_grad) 6 b = (a * a).sum() 7 print(b.grad_fn)
⑥、梯度下降
对于反向传播,因为out包含一个标量,out. backwards()等价于out. backwards(torch.tensor(1.))。
1 out.backward()
打印梯度d(out)/dx
1 print(x.grad)
在数学上,有一个向量值函数,y关于x的导数矩阵如下:
一般来说,torch.autograd用来计算雅可比行列式的工具,对于任意给定的矢量,计算
。如果v恰好是标量函数梯度
,那么
,通过链规则,矢量雅可比产品将是梯度l关于
注意:给出一个行向量,可以转换为列向量
向量-雅可比矩阵乘积的这种特性使得将外部梯度输入具有非标量输出的模型非常方便。
一个向量雅可比矩阵乘积的例子:
1 x = torch.randn(3, requires_grad=True) 2 3 y = x * 2 4 while y.data.norm() < 1000: 5 y = y * 2 6 7 print(y)
在这种情况下,y不再是标量。torch.autograd不能直接计算出整个雅可比矩阵,只需将向量作为参数传递给backward。
1 v = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float) 2 y.backward(v) 3 4 print(x.grad)
可以使用.requires_grad=True来阻止autograd跟踪张量的历史,方法是使用torch.no_grad()将代码块封装起来:
1 print(x.requires_grad) 2 print((x ** 2).requires_grad) 3 4 with torch.no_grad(): 5 print((x ** 2).requires_grad)
三、搭建神经网络
①、可以使用torch.nn包来构造神经网络,nn取决于autograd,定义模块一个nn.Module包含层和一种方法forward(input),返回output。
②、卷积神经网络模型如下:
这是一个简单的前馈网络。它接受输入,一个接一个地通过几个层输入,然后最终给出输出。
③、神经网络的典型训练程序如下:
-
-
- 定义具有一些可学习参数(或权重)的神经网络
- 迭代输入数据集
- 通过网络处理输入
- 计算损失(输出距离正确多远)
- 将渐变传播回网络参数
- 通常使用简单的更新规则更新网络权重:weight = weight -learning_rate * gradient
-
④、定义网络
1 import torch 2 import torch.nn as nn 3 import torch.nn.functional as F 4 5 6 class Net(nn.Module): 7 8 def __init__(self): 9 super(Net, self).__init__() 10 # 1 input image channel, 6 output channels, 5x5 square convolution 11 # kernel 12 self.conv1 = nn.Conv2d(1, 6, 5) 13 self.conv2 = nn.Conv2d(6, 16, 5) 14 # an affine operation: y = Wx + b 15 self.fc1 = nn.Linear(16 * 5 * 5, 120) 16 self.fc2 = nn.Linear(120, 84) 17 self.fc3 = nn.Linear(84, 10) 18 19 def forward(self, x): 20 # Max pooling over a (2, 2) window 21 x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) 22 # If the size is a square you can only specify a single number 23 x = F.max_pool2d(F.relu(self.conv2(x)), 2) 24 x = x.view(-1, self.num_flat_features(x)) 25 x = F.relu(self.fc1(x)) 26 x = F.relu(self.fc2(x)) 27 x = self.fc3(x) 28 return x 29 30 def num_flat_features(self, x): 31 size = x.size()[1:] # all dimensions except the batch dimension 32 num_features = 1 33 for s in size: 34 num_features *= s 35 return num_features 36 37 38 net = Net() 39 print(net)
只需定义正向函数,然后使用autograd自动定义反向函数(其中计算梯度)。可以在正函数中使用任何张量运算。
模型的可学习参数由net.parameters()返回:
1 params = list(net.parameters()) 2 print(len(params)) 3 print(params[0].size()) # conv1's .weight
⑤、处理输入并向后调用
尝试一个随机的32x32输入。注意:此网络(LeNet)的预期输入大小为32x32。要在MNIST数据集上使用此网络,请将数据集中的图像大小调整为32x32。
1 input = torch.randn(1, 1, 32, 32) 2 out = net(input) 3 print(out)
使用随机梯度将所有参数和反向的梯度缓冲区归零:
1 net.zero_grad() 2 out.backward(torch.randn(1, 10))
注意: torch.nn仅支持小批量,整个torch.nn包仅支持输入是一小批样本,而不是单个样本:
例如:nn.Conv2d接收一个四维向量nSamples x nChannels x Height x Width,如果是单个样本,仅使用input.unsqueeze(0)添加伪批处理维度。
回顾一下到目前为止所见的所有类:
-
-
torch.Tensor
- 支持诸如backward()之类的autograd操作的多维数组,因此,包含梯度w.r.t这些向量。nn.Module
-神经网络模型,使用帮助程序将它们移动到GPU,导出,加载等。nn.Parameter
- 一种Tensor,在被指定为a的属性时自动注册为参数Module
。autograd.Function
- 实现自动编程操作的前向和后向定义。每个Tensor
操作至少创建一个Function
节点,该节点连接到创建Tensor
和编码其历史记录的函数。
-
⑥、损失函数
损失函数接受(输出,目标)输入对,并计算估计输出距目标的距离的值。nn包下有几种不同的 损失函数。一个简单的损失是:nn.MSELoss
它计算输入和目标之间的均方误差。
例如:
1 output = net(input) 2 target = torch.randn(10) # a dummy target, for example 3 target = target.view(1, -1) # make it the same shape as output 4 criterion = nn.MSELoss() 5 6 loss = criterion(output, target) 7 print(loss)
如果按照loss后向方向,使用它的.grad_fn属性,可以得到类似下面的计算步骤:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
因此,当要调用loss.backward()时,整个图像将被微分成w.r.t。这些损失,以及图中所有有requires_grad=True的张量都将随着梯度有.grad张量累积。
为了说明这一点,后退几步:
1 print(loss.grad_fn) # MSELoss 2 print(loss.grad_fn.next_functions[0][0]) # Linear 3 print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # ReLU
⑦、反向传播
对于反向传播误差,需要求loss.backward()。不过,需要清除现有的梯度,否则梯度将累积为现有梯度。现在将调用loss. backwards (),看一下前后的conv1的偏置梯度。
1 net.zero_grad() # zeroes the gradient buffers of all parameters 2 3 print('conv1.bias.grad before backward') 4 print(net.conv1.bias.grad) 5 6 loss.backward() 7 8 print('conv1.bias.grad after backward') 9 print(net.conv1.bias.grad)
现在,我们已经学习了如何使用损失函数,神经网络包包含各种模块和损失函数,构成了深度神经网络的构建模块。唯一需要学习的是:更新网络的权重。
⑧、更新权重
实践中最简单的更新规则是随机梯度下降(SGD):weight = weight - learning_rate *gradient
可以用简单的python代码实现:
1 learning_rate = 0.01 2 for f in net.parameters(): 3 f.data.sub_(f.grad.data * learning_rate)
然而,当使用神经网络时,希望使用各种不同的更新规则,如SGD、Nesterov-SGD、Adam、RMSProp等。为此,制作了一个小包装:torch.optim实现了所有这些方法。使用它很简单:
1 import torch.optim as optim 2 3 # create your optimizer 4 optimizer = optim.SGD(net.parameters(), lr=0.01) 5 6 # in your training loop: 7 optimizer.zero_grad() # zero the gradient buffers 8 output = net(input) 9 loss = criterion(output, target) 10 loss.backward() 11 optimizer.step() # Does the update
注意:观察如何使用optimizer.zero_grad(),手动将梯度缓冲区设置为零。这是因为梯度是累积的。
⑨、训练分类器
通常,当需要处理图像,文本,音频或视频数据时,可以使用标准的python包将数据加载到numpy数组中。然后你可以将这个数组转换成一个torch.*Tensor
。
-
-
- 对于图像,Pillow,OpenCV等软件包很有用
- 对于音频,包括scipy和librosa
- 对于文本,无论是原始Python还是基于Cython的加载,还是NLTK和SpaCy都很有用
-
特别是对于视觉,我们创建了一个名为的包 torchvision
,它包含用于常见数据集的数据加载器,如Imagenet,CIFAR10,MNIST等,以及用于图像的数据转换器,即torchvision.datasets
和torch.utils.data.DataLoader
。
在本教程中,我们将使用CIFAR10数据集。它有类:'飞机','汽车','鸟','猫','鹿','狗','青蛙','马','船','卡车'。CIFAR-10中的图像尺寸为3x32x32,即尺寸为32x32像素的3通道彩色图像。
训练分类器执行以下步骤:
1、使用加载和标准化CIFAR10训练和测试数据集 torchvision
2、定义卷积神经网络
3、定义损失函数
4、在训练数据上训练网络
5、在测试数据上测试网络
加载和标准化CIFAR10
使用torchvision
,加载CIFAR10非常容易。
1 import torch 2 import torchvision 3 import torchvision.transforms as transforms
torchvision数据集的输出是范围[0,1]的PILImage图像。将它们转换为归一化范围的张量[-1,1]。
1 transform = transforms.Compose( 2 [transforms.ToTensor(), 3 transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) 4 5 trainset = torchvision.datasets.CIFAR10(root='./data', train=True, 6 download=True, transform=transform) 7 trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, 8 shuffle=True, num_workers=2) 9 10 testset = torchvision.datasets.CIFAR10(root='./data', train=False, 11 download=True, transform=transform) 12 testloader = torch.utils.data.DataLoader(testset, batch_size=4, 13 shuffle=False, num_workers=2) 14 15 classes = ('plane', 'car', 'bird', 'cat', 16 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
展示一些训练图像,如下:
1 import matplotlib.pyplot as plt 2 import numpy as np 3 4 # functions to show an image 5 6 7 def imshow(img): 8 img = img / 2 + 0.5 # unnormalize 9 npimg = img.numpy() 10 plt.imshow(np.transpose(npimg, (1, 2, 0))) 11 plt.show() 12 13 14 # get some random training images 15 dataiter = iter(trainloader) 16 images, labels = dataiter.next() 17 18 # show images 19 imshow(torchvision.utils.make_grid(images)) 20 # print labels 21 print(' '.join('%5s' % classes[labels[j]] for j in range(4)))
定义卷积神经网络:
从神经网络部分复制神经网络并修改它以获取3通道图像(而不是定义的1通道图像)
1 import torch.nn as nn 2 import torch.nn.functional as F 3 4 5 class Net(nn.Module): 6 def __init__(self): 7 super(Net, self).__init__() 8 self.conv1 = nn.Conv2d(3, 6, 5) 9 self.pool = nn.MaxPool2d(2, 2) 10 self.conv2 = nn.Conv2d(6, 16, 5) 11 self.fc1 = nn.Linear(16 * 5 * 5, 120) 12 self.fc2 = nn.Linear(120, 84) 13 self.fc3 = nn.Linear(84, 10) 14 15 def forward(self, x): 16 x = self.pool(F.relu(self.conv1(x))) 17 x = self.pool(F.relu(self.conv2(x))) 18 x = x.view(-1, 16 * 5 * 5) 19 x = F.relu(self.fc1(x)) 20 x = F.relu(self.fc2(x)) 21 x = self.fc3(x) 22 return x 23 24 25 net = Net()
定义Loss函数和优化器:
使用分类交叉熵损失和SGD动量:
1 import torch.optim as optim 2 3 criterion = nn.CrossEntropyLoss() 4 optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
训练网络:
需循环遍历数据迭代器,并将输入提供给网络并进行优化:
1 for epoch in range(2): # loop over the dataset multiple times 2 3 running_loss = 0.0 4 for i, data in enumerate(trainloader, 0): 5 # get the inputs 6 inputs, labels = data 7 8 # zero the parameter gradients 9 optimizer.zero_grad() 10 11 # forward + backward + optimize 12 outputs = net(inputs) 13 loss = criterion(outputs, labels) 14 loss.backward() 15 optimizer.step() 16 17 # print statistics 18 running_loss += loss.item() 19 if i % 2000 == 1999: # print every 2000 mini-batches 20 print('[%d, %5d] loss: %.3f' % 21 (epoch + 1, i + 1, running_loss / 2000)) 22 running_loss = 0.0 23 24 print('Finished Training')
在测试数据上测试网络:
通过预测神经网络输出的类标签来检查网络是否已经学到了什么,并根据地面实况进行检查。如果预测正确,我们将样本添加到正确预测列表中。
第一步:从测试集中显示一个图像以熟悉:
1 dataiter = iter(testloader) 2 images, labels = dataiter.next() 3 4 # print images 5 imshow(torchvision.utils.make_grid(images)) 6 print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))
看看神经网络认为上面这些例子是什么:
1 outputs = net(images)
输出是10类的能量,一个类的能量越高,网络认为图像是特定类的越多。那么,让我们得到最高能量的指数:
1 _, predicted = torch.max(outputs, 1) 2 3 print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] 4 for j in range(4)))
⑩、如何在GPU上运行这些神经网络
如果有可用的CUDA,首先将设备定义为第一个可见的cuda设备:
1 device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 2 3 # Assuming that we are on a CUDA machine, this should print a CUDA device: 4 5 print(device)
然后这些方法将递归遍历所有模块并将其参数和缓冲区转换为CUDA张量:
1 net.to(device)
还必须将每一步的输入和目标发送到GPU:
1 inputs, labels = inputs.to(device), labels.to(device)