「免费学习 60+ 节公开课:投票页面,点击讲师头像」
配置环境:PyTorch 1.3,python 3.7,
主机:显卡:一块1080ti;内存:32g(注:绝大部分代码不需要GPU)
目录
一、Pytorch是什么?
二、AUTOGRAD
三、神经网络
四、训练一个分类器
五、数据并行
一、PyTorch 是什么
他是一个基于Python的科学计算包,目标用户有两类
为了使用GPU来替代numpy
一个深度学习研究平台:提供最大的灵活性和速度
开始
张量(Tensors)
张量类似于numpy的ndarrays,不同之处在于张量可以使用GPU来加快计算。
from __future__ import print_functionimport torch
x = torch.Tensor(5 , 3 ) print(x)
tensor([[ 0.0000e+00, 0.0000e+00, 1.3004e-42], [ 0.0000e+00, 7.0065e-45, 0.0000e+00], [-3.8593e+35, 7.8753e-43, 0.0000e+00], [ 0.0000e+00, 1.8368e-40, 0.0000e+00], [-3.8197e+35, 7.8753e-43, 0.0000e+00]])
x = torch.zeros(5 , 3 , dtype=torch.long) print(x)
tensor([[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]])
x = torch.tensor([5.5 , 3 ]) print(x)
或者在已有的张量(tensor)中构建一个张量(tensor). 这些方法将重用输入张量(tensor)的属性,例如, dtype,除非用户提供新值
x = x.new_ones(5 , 3 , dtype=torch.double) # new_* methods take in sizes print(x) x = torch.randn_like(x, dtype=torch.float) # 覆盖类型!
print(x) # result 的size相同
tensor([[1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.], [1., 1., 1.]], dtype=torch.float64) tensor([[ 1.1701, -0.8342, -0.6769], [-1.3060, 0.3636, 0.6758], [ 1.9133, 0.3494, 1.1412], [ 0.9735, -0.9492, -0.3082], [ 0.9469, -0.6815, -1.3808]])
torch.Size
实际上是一个元组,所以它支持元组的所有操作。
操作
张量上的操作有多重语法形式,下面我们以加法为例进行讲解。
语法1
y = torch.rand(5 , 3 ) print(x + y)
tensor([[ 1.7199, -0.1819, -0.1543], [-0.5413, 1.1591, 1.4098], [ 2.0421, 0.5578, 2.0645], [ 1.7301, -0.3236, 0.4616], [ 1.2805, -0.4026, -0.6916]])
语法二
tensor([[ 1.7199, -0.1819, -0.1543], [-0.5413, 1.1591, 1.4098], [ 2.0421, 0.5578, 2.0645], [ 1.7301, -0.3236, 0.4616], [ 1.2805, -0.4026, -0.6916]])
语法三:
result = torch.empty(5 , 3 ) torch.add(x, y, out=result) print(result)
tensor([[ 1.7199, -0.1819, -0.1543], [-0.5413, 1.1591, 1.4098], [ 2.0421, 0.5578, 2.0645], [ 1.7301, -0.3236, 0.4616], [ 1.2805, -0.4026, -0.6916]])
语法四:
# 把x加到y上 y.add_(x) print(y)
tensor([[ 1.7199, -0.1819, -0.1543], [-0.5413, 1.1591, 1.4098], [ 2.0421, 0.5578, 2.0645], [ 1.7301, -0.3236, 0.4616], [ 1.2805, -0.4026, -0.6916]])
任何在原地(in-place)改变张量的操作都有一个
_
后缀。例如
x.copy_(y)
,
x.t_()
操作将改变x.
tensor([-0.8342, 0.3636, 0.3494, -0.9492, -0.6815])
调整大小:如果要调整张量/重塑张量,可以使用
torch.view
:
x = torch.randn(4 , 4 ) y = x.view(16 ) z = x.view(-1 , 8 ) # -1的意思是没有指定维度 print(x.size(), y.size(), z.size())
torch.Size([4, 4]) torch.Size([16]) torch.Size([2, 8])
如果你有一个单元素张量,使用
.item()
将值作为Python数字
x = torch.randn(1 ) print(x) print(x.item())
tensor([0.3441]) 0.34412217140197754
稍后阅读
这里:(https://pytorch.org/docs/stable/torch.html)
描述了一百多种张量操作,包括转置,索引,数学运算,线性代数,随机数等。
Numpy桥
把一个torch张量转换为numpy数组或者反过来都是很简单的。
Torch张量和numpy数组将共享潜在的内存,改变其中一个也将改变另一个。
把Torch张量转换为numpy数组
a = torch.ones(5 ) print(a)
tensor([1., 1., 1., 1., 1.])
b = a.numpy() print(b) print(type(b))
通过如下操作,我们看一下numpy数组的值如何在改变。
a.add_(1 ) print(a) print(b)
tensor([2., 2., 2., 2., 2.]) [ 2. 2. 2. 2. 2.]
把numpy数组转换为torch张量
看看改变numpy数组如何自动改变torch张量。
import numpy as npa = np.ones(5 ) b = torch.from_numpy(a) np.add(a, 1
, out=a) print(a) print(b)
[ 2. 2. 2. 2. 2.] tensor([2., 2., 2., 2., 2.], dtype=torch.float64)
所有在CPU上的张量,除了字符张量,都支持在numpy之间转换。
CUDA张量
# let us run this cell only if CUDA is available # We will use ``torch.device`` objects to move tensors in and out of GPU if torch.cuda.is_available(): device = torch.device("cuda" ) # a CUDA device object y = torch.ones_like(x, device=device) # directly create a tensor on GPU x = x.to(device) # or just use strings ``.to("cuda")`` z = x + y print(z) print(z.to("cpu" , torch.double)) # ``.to`` can also change dtype together!
tensor([1.3441], device='cuda:0') tensor([1.3441], dtype=torch.float64)
https://pytorch.org/tutorials/_downloads/3c2b25b8a9f72db7780a6bf9b5fc9f62/tensor_tutorial.ipynb
二、Autograd: 自动求导(automatic differentiation)
PyTorch 中所有神经网络的核心是
autograd
包.我们首先简单介绍一下这个包,然后训练我们的第一个神经网络.
autograd
包为张量上的所有操作提供了自动求导.它是一个运行时定义的框架,这意味着反向传播是根据你的代码如何运行来定义,并且每次迭代可以不同.
张量(Tensor)
torch.Tensor
是包的核心类。如果将其属性
.requires_grad
设置为True,则会开始跟踪其上的所有操作。完成计算后,您可以调用
.backward()
并自动计算所有梯度。此张量的梯度将累积到
.grad
属性中。
要阻止张量跟踪历史记录,可以调用
.detach()
将其从计算历史记录中分离出来,并防止将来的计算被跟踪。
要防止跟踪历史记录(和使用内存),您还可以使用torch.no_grad()包装代码块:在评估模型时,这可能特别有用,因为模型可能具有
requires_grad = True
的可训练参数,但我们不需要梯度。
还有一个类对于autograd实现非常重要 - Function。
Tensor和Function互相连接并构建一个非循环图构建一个完整的计算过程。每个张量都有一个
.grad_fn
属性,该属性引用已创建Tensor的Function(除了用户创建的Tensors - 它们的
grad_fn
为
None
)。
如果要计算导数,可以在Tensor上调用
.backward()
。如果Tensor是标量(即它包含一个元素数据),则不需要为
backward()
指定任何参数,但是如果它有更多元素,则需要指定一个梯度参数,该参数是匹配形状的张量。
创建一个张量并设置
requires_grad = True
以跟踪它的计算
x = torch.ones(2 , 2 , requires_grad=True ) print(x)
tensor([[1., 1.], [1., 1.]], requires_grad=True)
tensor([[3., 3.], [3., 3.]], grad_fn=)
因为y是通过一个操作创建的,所以它有grad_fn,而x是由用户创建,所以它的grad_fn为None.
print(y.grad_fn) print(x.grad_fn)
z = y * y * 3 out = z.mean() print(z, out)
tensor([[27., 27.], [27., 27.]], grad_fn=) tensor(27., grad_fn=)
.requires\_grad_(...)
就地更改现有的Tensor的
requires_grad
标志。如果没有给出,输入标志默认为False。
a = torch.randn(2 , 2 ) a = ((a * 3 ) / (a - 1 )) print(a.requires_grad) a.requires_grad_(True ) print(a.requires_grad) b = (a * a).sum() print(b.grad_fn)
梯度(Gradients)
现在我们来执行反向传播,
out.backward()
相当于执行
out.backward(torch.tensor(1.))
tensor([[4.5000, 4.5000], [4.5000, 4.5000]])
你应该得到一个值全为4.5的矩阵,我们把张量out称为"
". 则:
,
,并且
,所以,
, 因此
在数学上,如果你有一个向量值函数
,则
相对于
的梯度是雅可比矩阵:
一般来说,torch.autograd是一个计算雅可比向量积的引擎。也就是说,给定任何向量
,计算乘积
。如果
恰好是标量函数的梯度
,即
然后根据链式法则,雅可比向量乘积将是
相对于
的梯度
雅可比向量积的这种特性使得将外部梯度馈送到具有非标量输出的模型中非常方便。
x = torch.randn(3 , requires_grad=True ) y = x * 2 while y.data.norm() < 1000 : y = y * 2 print(y)
tensor([ 384.5854, -13.6405, -1049.2870], grad_fn=)
现在在这种情况下,y不再是标量。
torch.autograd
无法直接计算完整雅可比行列式,但如果我们只想要雅可比向量积,只需将向量作为参数向后传递:
v = torch.tensor([0.1 , 1.0 , 0.0001 ], dtype=torch.float) y.backward(v) print(x.grad)
tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])
您还可以通过torch.no_grad()代码,在张量上使用.requires_grad = True来停止使用跟踪历史记录。
print(x.requires_grad) print((x ** 2 ).requires_grad) with torch.no_grad(): print((x ** 2 ).requires_grad)
关于
autograd
和
Function
的文档在http://pytorch.org/docs/autograd
https://pytorch.org/tutorials/_downloads/009cea8b0f40dfcb55e3280f73b06cc2/autograd_tutorial.ipynb
三、神经网络
你已知道
autograd
包,
nn
包依赖
autograd
包来定义模型并求导.一个
nn.Module
包含各个层和一个
forward(input)
方法,该方法返回
output
.
它是一个简单的前馈神经网络,它接受一个输入,然后一层接着一层的输入,直到最后得到结果。
定义神经网络模型,它有一些可学习的参数(或者权重);
更新网络的参数,主要使用如下简单的更新原则:
weight = weight - learning_rate * gradient
定义网络
import torchimport torch.nn as nnimport torch.nn.functional as Fclass Net(nn.Module) : def __init__(self): super(Net, self).__init__() # 1 input image channel, 6 output channels, 5x5 square convolution # kernel self.conv1 = nn.Conv2d(1 , 6 , 5 ) self.conv2 = nn.Conv2d(6 , 16 , 5 ) # an affine operation: y = Wx + b self.fc1 = nn.Linear(16 * 5 * 5 , 120 ) 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_featuresnet = Net() print(net)
Net( (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1)) (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1)) (fc1): Linear(in_features=400, 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) )
你只需定义
forward
函数,
backward
函数(计算梯度)在使用
autograd
时自动为你创建.你可以在
forward
函数中使用
Tensor
的任何操作。
net.parameters()
返回模型需要学习的参数。
params = list(net.parameters()) print(len(params)) print(params[0 ].size())
10 torch.Size([6, 1, 5, 5])
forward
的输入和输出都是
autograd.Variable
.注意:这个网络(LeNet)期望的输入大小是32*32.如果使用MNIST数据集来训练这个网络,请把图片大小重新调整到32*32.
input = torch.randn(1 , 1 , 32 , 32 ) out = net(input) print(out)
tensor([[-0.1217, 0.0449, -0.0392, -0.1103, -0.0534, -0.1108, -0.0565, 0.0116, 0.0867, 0.0102]], grad_fn=)
将所有参数的梯度缓存清零,然后进行随机梯度的的反向传播.
net.zero_grad() out.backward(torch.randn(1 , 10 ))
torch.nn
只支持小批量输入,整个
torch.nn
包都只支持小批量样本,而不支持单个样本
例如,
nn.Conv2d
将接受一个4维的张量,每一维分别是(样本数*通道数*高*宽).
如果你有单个样本,只需使用
input.unsqueeze(0)
来添加其它的维数.
回顾
torch.Tensor
-支持自动编程操作(如
backward()
)的多维数组。同时保持梯度的张量。
nn.Module
-神经网络模块.封装参数,移动到GPU上运行,导出,加载等
nn.Parameter
-一种张量,当把它赋值给一个
Module
时,被自动的注册为参数.
autograd.Function
-实现一个自动求导操作的前向和反向定义, 每个张量操作都会创建至少一个
Function
节点,该节点连接到创建张量并对其历史进行编码的函数。
现在,我们包含了如下内容:
剩下的内容:
损失函数
一个损失函数接受一对(output, target)作为输入(output为网络的输出,target为实际值),计算一个值来估计网络的输出和目标值相差多少。
在nn包中有几种不同的损失函数.一个简单的损失函数是:
nn.MSELoss
,它计算输入和目标之间的均方误差。
output = net(input) target = torch.randn(10 ) # a dummy target, for example target = target.view(1 , -1 ) # make it the same shape as output criterion = nn.MSELoss() loss = criterion(output, target) print(loss)
现在,你反向跟踪
loss
,使用它的
.grad_fn
属性,你会看到向下面这样的一个计算图:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
所以, 当你调用
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 ])
反向传播
为了反向传播误差,我们所需做的是调用
loss.backward()
.你需要清除已存在的梯度,否则梯度将被累加到已存在的梯度。
现在,我们将调用
loss.backward()
,并查看conv1层的偏置项在反向传播前后的梯度。
net.zero_grad() # zeroes the gradient buffers of all parameters print('conv1.bias.grad before backward' ) print(net.conv1.bias.grad) loss.backward() print('conv1.bias.grad after backward' ) print(net.conv1.bias.grad)
conv1.bias.grad before backward tensor([0., 0., 0., 0., 0., 0.]) conv1.bias.grad after backward tensor([ 0.0006, -0.0164, 0.0122, -0.0060, -0.0056, -0.0052])
稍后阅读:
神经网络包包含了各种用来构成深度神经网络构建块的模块和损失函数,一份完整的文档查看[这里]:(
https://pytorch.org/docs/nn
)
唯一剩下的内容:
更新权重
weight=weight−learning_rate∗gradient
learning_rate = 0.01 for f in net.parameters(): f.data.sub_(f.grad.data * learning_rate)
然而,当你使用神经网络是,你想要使用各种不同的更新规则,比如
SGD,Nesterov-SGD
,
Adam
,
RMSPROP
等.为了能做到这一点,我们构建了一个包
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
观察如何使用
optimizer.zero_grad()
手动将梯度缓冲区设置为零。这是因为梯度是反向传播部分中的说明那样是累积的。
https://pytorch.org/tutorials/_downloads/97abb4c06a586d45ef3fc4b4b9634406/neural_networks_tutorial.ipynb
四、训练一个分类器
你已经学会如何去定义一个神经网络,计算损失值和更新网络的权重。
关于数据
通常,当你处理图像,文本,音频和视频数据时,你可以使用标准的Python包来加载数据到一个numpy数组中.然后把这个数组转换成
torch.*Tensor
。
对于图像,有诸如Pillow,OpenCV包等非常实用
对于文本,可以用原始Python和Cython来加载,或者使用NLTK和SpaCy 对于视觉,我们创建了一个
torchvision
包,包含常见数据集的数据加载,比如Imagenet,CIFAR10,MNIST等,和图像转换器,也就是
torchvision.datasets
和
torch.utils.data.DataLoader
。
在这个教程中,我们使用CIFAR10数据集,它有如下10个类别:’airplane’,’automobile’,’bird’,’cat’,’deer’,’dog’,’frog’,’horse’,’ship’,’truck’。这个数据集中的图像大小为3*32*32,即,3通道,32*32像素。
训练一个图像分类器
使用
torchvision
加载和归一化CIFAR10训练集和测试集.
1. 加载和归一化CIFAR0
使用
torchvision
加载CIFAR10是非常容易的。
import torchimport torchvisionimport 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' )#这个过程有点慢,会下载大约340mb图片数据。
Files already downloaded and verified Files already downloaded and verified
import matplotlib.pyplot as pltimport
numpy as np# functions to show an image def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1 , 2 , 0 ))) plt.show() # get some random training images dataiter = iter(trainloader) images, labels = dataiter.next() # show images imshow(torchvision.utils.make_grid(images)) # print labels print(' ' .join('%5s' % classes[labels[j]] for j in range(4 )))
2. 定义一个卷积神经网络
从之前的神经网络一节复制神经网络代码,并修改为接受3通道图像取代之前的接受单通道图像。
import torch.nn as nnimport torch.nn.functional as Fclass 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 xnet = Net()
3. 定义损失函数和优化器
我们使用交叉熵作为损失函数,使用带动量的随机梯度下降。
import torch.optim as optimcriterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.001 , momentum=0.9 )
4. 训练网络
这是开始有趣的时刻,我们只需在数据迭代器上循环,把数据输入给网络,并优化。
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 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() 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' )
[1, 2000] loss: 2.286 [1, 4000] loss: 1.921 [1, 6000] loss: 1.709 [1, 8000] loss: 1.618 [1, 10000] loss: 1.548 [1, 12000] loss: 1.496