第一节 基本的操作
什么是PyTorch?
它是基于Python语言的科学计算工具包,其设计目的包括以下两个方面:
- 替代Numpy从而可以使用强大的GPU进行计算
- 一个深读学习的研究平台,致力于提供最大的灵活性与速度
我们开始吧~
Tensors
Tensors和Numpy的ndarrays很像,与之不同之处在于Tensor可以在GPU上进行运算从而获得更快的计算速度。
from __future__ import print_function
import torch
利用pyTorch构造一个未初始化的5×3的矩阵:
x = torch.Tensor(5,3)
print(x)
-9.3921e+17 4.5628e-41 -9.3921e+17
4.5628e-41 0.0000e+00 0.0000e+00
0.0000e+00 0.0000e+00 0.0000e+00
0.0000e+00 0.0000e+00 0.0000e+00
0.0000e+00 0.0000e+00 0.0000e+00
[torch.FloatTensor of size 5x3]
类似于上一步,构造一个随机(值域为[0,1])初始化的矩阵:
x = torch.rand(5,3)
print(x)
0.9144 0.5597 0.7737
0.4661 0.0467 0.1902
0.7286 0.9863 0.7427
0.5697 0.6273 0.0305
0.5571 0.3872 0.0925
[torch.FloatTensor of size 5x3]
获得其尺寸:
print(x.size())
torch.Size([5, 3])
* Note: *
Torch.Size
实际上是一个tuple,因此支持相同的操作。
操作
用于数据操作的语法有很多,下面来看加法的操作:
Addition:语法一
y = torch.rand(5,3)
print(x+y)
1.5705 0.7976 1.5798
0.8943 0.2229 0.4182
0.8277 1.7598 1.7178
0.8377 0.9944 0.0811
0.9289 1.3111 0.7678
[torch.FloatTensor of size 5x3]
Addition:语法二
print(torch.add(x,y))
1.5705 0.7976 1.5798
0.8943 0.2229 0.4182
0.8277 1.7598 1.7178
0.8377 0.9944 0.0811
0.9289 1.3111 0.7678
[torch.FloatTensor of size 5x3]
Addition:给一个输出的Tensor
result = torch.Tensor(5,3)
torch.add(x,y,out=result)
print(result)
1.5705 0.7976 1.5798
0.8943 0.2229 0.4182
0.8277 1.7598 1.7178
0.8377 0.9944 0.0811
0.9289 1.3111 0.7678
[torch.FloatTensor of size 5x3]
Addition:替换
#把x的值加到y上
y.add_(x)
print(y)
1.5705 0.7976 1.5798
0.8943 0.2229 0.4182
0.8277 1.7598 1.7178
0.8377 0.9944 0.0811
0.9289 1.3111 0.7678
[torch.FloatTensor of size 5x3]
注意,每个用于替换的操作其后都会接一个
_
。例如x.copy_(y),x.t_()
,都会改变x
的值。
你可以使用标准的numpy的操作来使用数据:
print(x[:,1])#打印矩阵的所有行及第二列
0.5597
0.0467
0.9863
0.6273
0.3872
[torch.FloatTensor of size 5]
Numpy Bridge:Tensor与Numpy的相互转换
Tensor和Numpy在转换后会共享数据所占的内存区域,改变两者的任何一个的值,两者的值都会同时发生改变。
将Torch的Tensor转换为Numpy的Array
a = torch.ones(5)
print(a)
1
1
1
1
1
[torch.FloatTensor of size 5]
b = a.numpy()
print(b)
[ 1. 1. 1. 1. 1.]
验证:共享内存的机制
a.add_(1)
print('a is :\n',a)
print('b is :\n',b)
a is :
2
2
2
2
2
[torch.FloatTensor of size 5]
b is :
[ 2. 2. 2. 2. 2.]
将numpy数组转换为torch的Tensor
import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a,1,out=a)
print(a)
print(b)
[ 2. 2. 2. 2. 2.]
2
2
2
2
2
[torch.DoubleTensor of size 5]
除了CharTensor外,运算在CPU模式的所有Tensor都支持与Numpy的相互转换。
Autograd: 自动求导数
PyTorch的所有神经网络的核心功能都在于autograd
类。该类提供所有对Tensor操作的求导。它是一个define-by-run的框架,这意味着你的反向传播是有你的代码在运行时决定的,因而每个单一的迭代可以不同。
Variable
autograd.Variable
是该类的一个核心类,它包含着Tensor并且支持几乎所有相关的操作。一旦你完成了前向传播的计算任务,你可以调用backward()
使得所有的梯度自动的进行计算。
你可以使用Tensor.data访问原始Tensor的数据,与此同时,梯度的值贝类加到属性Tensor.grad中。
图示为Variable的内部组成
import torch
from torch.autograd import Variable
创建一个Variable
x = Variable(torch.ones(2,2),requires_grad=True)
print(x)
Variable containing:
1 1
1 1
[torch.FloatTensor of size 2x2]
对变量进行操作:
y = x + 2
print(y)
Variable containing:
3 3
3 3
[torch.FloatTensor of size 2x2]
y是由一个操作的结果所生成的,因此它拥有一个creator。
print(y.creator)
<torch.autograd._functions.basic_ops.AddConstant object at 0x7f31d7e47828>
对y进行更多的操作:
z = y * y * 3
out = z.mean()
print(z,out)
Variable containing:
27 27
27 27
[torch.FloatTensor of size 2x2]
Variable containing:
27
[torch.FloatTensor of size 1]
梯度
让反向传播 out.backward() 等价于操作out.backward(torch.Tensor([1.0]))
out.backward()
打印求导结果 d(out)dx
print(x.grad)
Variable containing:
4.5000 4.5000
4.5000 4.5000
[torch.FloatTensor of size 2x2]
得出上面结果的原因:
你可以利用autograd做很多疯狂的事情。
x = torch.rand(3)
x = Variable(x,requires_grad=True)
y = x*2
count = 0
while y.data.norm() < 1000:
y = y * 2#运行好多次,导致x有好多次的累加
count = count + 1
print(y)
print(count)
Variable containing:
279.9745
1013.4999
346.2041
[torch.FloatTensor of size 3]
10
gradients = torch.FloatTensor([0.1,1.0,0.0001])
y.backward(gradients)
print(x.grad)
Variable containing:
204.8000
2048.0000
0.2048
[torch.FloatTensor of size 3]
神经网络
神经网络可以通过torch.nn包来实现。
现在我们对于autograd有了一个大概的印象,nn依赖于autograd来定义模型以及对它们求导数。一个nn.Module包含layers和方法forward(input),返回的结果为output
例如,观看下面分类数字手写识别的卷积神经网络:
它是一个简单的feed-forward网络。他将输入一层一层的传递下去,最终给出一个输出结果。
一个典型的神经网络的训练流程如下所下:
- 定义一个拥有可学习参数的神经网络
- 遍历数据集,将其内容作为输入
- 处理输入数据
- 计算代价(神经网络的输出与正确值的距离)
- 反向传播
- 更新神经网络中的参数,一般形式如下:
weight=weight+learningrate∗gradient
定义神经网络
让我们来定义一个神经网络:
import torch
from torch.autograd import Variable
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net,self).__init__()
#输入图像通道为1,6个输出通道,5×5的卷积核
self.conv1 = nn.Conv2d(1,6,5)
# 输入图像通道为6,16个输出通道,5×5的卷积核
self.conv2 = nn.Conv2d(6,16,5)
#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):
# 2×2的最大池化窗口
x = F.max_pool2d(F.relu(self.conv1(x)),(2,2))
# 若是一个正方形,可以用一个数来表示
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:]# 取得出batch外的所有维度
num_features = 1
for s in size:
num_features *= s
return num_features
net = 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 (400 -> 120)
(fc2): Linear (120 -> 84)
(fc3): Linear (84 -> 10)
)
我们必须自定义forward
函数,backward
函数(用于计算梯度)使用autograd
自动定义,你可以在forward
函数中使用任何Tensor的操作
用于学习的网络参数可以通过net.parameters()
获取。
params = list(net.parameters())
print(len(params))
print(params[0].size())# 卷积层1的参数
print(params[0][1,:])
10
torch.Size([6, 1, 5, 5])
Variable containing:
(0 ,.,.) =
-0.1029 0.0501 -0.1202 0.0168 -0.0679
-0.1803 0.1550 -0.1125 -0.0424 -0.0865
0.0836 0.0452 -0.0619 0.1352 0.0567
0.1265 -0.1011 -0.0643 -0.1561 -0.1950
-0.0530 0.1887 0.1961 -0.0866 0.0307
[torch.FloatTensor of size 1x5x5]
forward函数的输入是一个autograd.Variable
,输出同样也是。
input = Variable(torch.randn(1,1,32,32))
out = net(input)
print(out)
Variable containing:
-0.1454 0.0262 0.0150 -0.0715 -0.1060 0.0319 -0.0391 -0.0759 -0.0447 -0.1261
[torch.FloatTensor of size 1x10]
清空所有参数的梯度的缓存,并且使用随机梯度进行反步操作:
net.zero_grad()
out.backward(torch.randn(1,10))
注意:
torch.nn
只支持mini-batches
,完整的torch.nn
包只支持样本的mini-batch
作为输入,而不是单个的样本。
例如,nn.Conv2d
会接收一个4维的Tensor
作为输入,包括nSamples x nChannels x Height x Width
。
如果只有一个样本,可以通过使用input.unsqueeze(0)
来添加一个伪造的batch
维度。
在进行更深入的研究之前,让我们概括一下目前所见到的一些类。
torch.Tensor
:一个多维数组
autograd.Variable
:包含一个Tensor
并接受应用在其上的历史操作。拥有和Tensor
一样的API
,额外的有像是backward()
。同样包含着Tensor
中的梯度。
nn.Mosule:
神经网络模型,便于封装参数,易于转到GPU进行计算,导出,加载等。
nn.Parameter:
一种类型的Variable
,当作为Module
的一个属性时,自动的注册为一个参数。
autograd.Function:
实现用于自动求导操作的前向传播和反向传播。每一个Variable操作,创造至少一个单一的Function
节点,用于创建一个Variable
并编码其历史。
目前为止我们学习了:
- 定义一个神经网络
- 处理输入,调用反向传播
接下来我们还要学习:
- 计算代价函数
- 更新神经网络权重
代价函数(Loss Function)
代价函数通过计算来自于input的(output,target),计算一个值然后估计输出值偏离于目标值的程度。
nn包中有很多种loss function,一个简单的loss是:nn.MSELoss,该loss用于计算输出和目标之间的均方误差。
例如:
output = net(input)
target = Variable(torch.arange(1,11))#假设的target :1,2,3,4,5,6,7,8,9,10
criterion = nn.MSELoss()
loss = criterion(output,target)
print(loss)
Variable containing:
39.1498
[torch.FloatTensor of size 1]
现在,如果你沿着反向传播的方向,使用其creator属性,你会看到一个计算图:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
因此,当我们调用loss.backward()的时候。完整的图就是对loss求导后的结果,每一个图中的Variable将会拥有使用gradient累加的它们自己的.grad Variable。
为了更好的说明,进行一些反向传播的操作:
print(loss.creator) # MSELoss
print(loss.creator.previous_functions[0][0])# Linear
print(loss.creator.previous_functions[0][0].previous_functions[0][0])# Relu
<torch.nn._functions.thnn.auto.MSELoss object at 0x7f319130cf28>
<torch.nn._functions.linear.Linear object at 0x7f319130cd68>
<torch.nn._functions.thnn.auto.Threshold object at 0x7f319130cc88>
Backprop(反向传播)
为了将误差进行反向传播,我们只需要运行loss.backward()。你需要清空已经存在的梯度信息,否则现存的梯度值会被累加到下一步要计算的梯度当中去。
现在,我们调用一下loss.backward(),然后看一下conv1的偏置梯度在反向传播前后的变化。
net.zero_grad() # 清空梯度的缓存
print("反向传播前的conv1.bias.grad:")
print(net.conv1.bias.grad)
loss.backward()
print("反向传播后的conv1.bias.grad:")
print(net.conv1.bias.grad)
反向传播前的conv1.bias.grad:
Variable containing:
0
0
0
0
0
0
[torch.FloatTensor of size 6]
反向传播后的conv1.bias.grad:
Variable containing:
1.00000e-02 *
1.6163
-1.4633
-1.4045
-0.7913
4.5722
1.9762
[torch.FloatTensor of size 6]
至此,我们明白了如何使用loss functions
Update the weights
最简单的权重更新规则被称为Stochastic Gradient Descent (SGD):
使用简单的python代码实现上述功能:
learning_rate = 0.01
for f in net.parameters():
f.data.sub_(f.grad.data * learning_rate)
当然,PyTorch同样提供了很多的类似函数包括SGD,Nesterov-SGD, Adam, RMSProp等等。所有的这些方法都被封装到包torch.optim中。
import torch.optim as optim
# 创建自己的optimizer
optimizer = optim.SGD(net.parameters(),lr=0.01)
# 在训练的循环中
optimizer.zero_grad() # 清空梯度缓存
output = net(input)
loss = criterion(output,target)
loss.backward()
optimizer.step() # 更新操作
训练一个图像分类器
我们将从以下几个步骤来进行:
- 使用torchvision加载并且正则化数据集CIFAR10
- 定义一个卷积神经网络
- 定义一个损失函数
- 在training data 上训练网络
- 在test data上测试神经网络
加载并正则化CIFAR10
使用torchvision,它用于加载CIFAR10时十分方便
import torch
import torchvision
import torchvision.transforms as transforms
输出的torchvision数据集是PILImages的格式,范围为[0,1],我们需要将它转化为正则化的Tensor,范围是[-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('./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')
Files already downloaded and verified
Files already downloaded and verified
选几张图片做展示:
import matplotlib.pyplot as plt
import numpy as np
%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('%10s'%classes[labels[j]] for j in range(4)))
frog dog plane car
定义一个卷积神经网络
复制之前神经网络的定义,修改网络的1个通道输入为3个通道
from torch.autograd import Variable
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()
定义Loss函数和优化器(optimizer)
这里我们使用交叉熵损失函数和SGD做为优化器
import torch.optim as optim
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
训练神经网络
事情变得有趣起来。我们简单的迭代取得数据集中的数据然后喂给神将网络并进行优化。
for epoch in range(2):# 多次循环取出数据
running_loss = 0.0
for i, data in enumerate(trainloader,0):
# 获得输入
inputs,labels = data
# 使用Variable包装这些数据
inputs,labels = Variable(inputs),Variable(labels)
# 清空缓存的梯度信息
optimizer.zero_grad()
#forward + backward + optimize
outputs = net(inputs)
loss = criterion(outputs,labels)
loss.backward()
optimizer.step()
#打印统计信息
running_loss += loss.data[0]
if i%2000 == 1999:# 打印没2000 mini-batches
print('[%d,%5d] loss :%.3f'%
(epoch+1,i+1,running_loss / 2000))
running_loss = 0.0
print("结束训练")
[1, 2000] loss :2.184
[1, 4000] loss :1.821
[1, 6000] loss :1.644
[1, 8000] loss :1.548
[1,10000] loss :1.498
[1,12000] loss :1.456
[2, 2000] loss :1.380
[2, 4000] loss :1.345
[2, 6000] loss :1.323
[2, 8000] loss :1.329
[2,10000] loss :1.297
[2,12000] loss :1.266
结束训练
在测试集上测试神经网络
我们已经把神经网络在训练集上训练了2遍,现在我们需要检验一下神经网络是否学到了什么。
我们根据神经网络输出的类标签与实际的类标签进行比较,将神经网络预测准确的样本归类到correct predictions中去。
首先,我们先是一张测试及中的图片来熟悉一下数据:
dataiter = iter(testloader)
images,labels = dataiter.next()
# 显示图片
imshow(torchvision.utils.make_grid(images))
print("GroundTruth:",''.join('%10s'%classes[labels[j]] for j in range(4)))
GroundTruth: cat ship ship plane
好了,让我们看一下神经网络是如何识别上面的图片的:
outputs = net(Variable(images))
输出是10个类的能量。高的能量集中到一个类标签中,则神经网络则会将数据归类为该类。因此,让我们找出最高的能量的索引。
_,predicted = torch.max(outputs.data,1)
print('Predicted:',' '.join('%5s'%classes[predicted[j][0]]
for j in range(4)))
Predicted: ship car car ship
接下来看看该神经网络在整个数据集上的表现如何。
correct = 0
total = 0
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum()
print('Accuracy of the network on the 10000 test images: %d %%' % (
100 * correct / total))
Accuracy of the network on the 10000 test images: 55 %
看起来不错哦,因为它比随机的结果好很多(随机的准确率为10%)
那么,那些类别可以被很好的区分,那些类别却又不能被很好的区分呢?
class_correct = list(0. for i in range(10))
class_total = list(0. for i in range(10))
for data in testloader:
images, labels = data
outputs = net(Variable(images))
_, predicted = torch.max(outputs.data, 1)
c = (predicted == labels).squeeze()
for i in range(4):
label = labels[i]
class_correct[label] += c[i]
class_total[label] += 1
for i in range(10):
print('Accuracy of %5s : %2d %%' % (
classes[i], 100 * class_correct[i] / class_total[i]))
Accuracy of plane : 66 %
Accuracy of car : 81 %
Accuracy of bird : 33 %
Accuracy of cat : 44 %
Accuracy of deer : 36 %
Accuracy of dog : 54 %
Accuracy of frog : 59 %
Accuracy of horse : 55 %
Accuracy of ship : 73 %
Accuracy of truck : 44 %
目标达成
- Understanding PyTorch’s Tensor library and neural networks at a high level.
- Train a small neural network to classify images