PyTorch的简洁设计使得它入门很简单,在深入介绍PyTorch之前,本节将先介绍一些PyTorch的基础知识,使得读者能够对PyTorch有一个大致的了解,并能够用PyTorch搭建一个简单的神经网络。
 
  Tensor 
Tensor是PyTorch中重要的数据结构,可认为是一个高维数组。它可以是一个数(标量)、一维数组(向量)、二维数组(矩阵)以及更高维的数组。Tensor和Numpy的ndarrays类似,但Tensor可以使用GPU进行加速。Tensor的使用和Numpy及Matlab的接口十分相似。(与Tensorflow中的Tensorflow基本相同)
x = t.Tensor(5 , 3 )   [output]: 1.00000e-07  *  0.0000   0.0000   5.3571    0.0000   0.0000   0.0000    0.0000   0.0000   0.0000    0.0000   5.4822   0.0000    5.4823   0.0000   5.4823  [torch.FloatTensor of size 5x3] 
 
x = t.rand(5 , 3 )   [output]: 0.3673   0.2522   0.3553  0.0070   0.7138   0.0463   0.6198   0.6019   0.3752   0.4755   0.3675   0.3032   0.5824   0.5104   0.5759  [torch.FloatTensor of size 5x3] 
 
查看x的形状(torch.Size 是tuple对象的子类,因此它支持tuple的所有操作,如x.size()[0]) 
 
 
x = t.rand(5 , 3 )   y = t.rand(5 , 3 ) x + y t.add(x, y) result = t.Tensor(5 , 3 )  t.add(x, y, out=result)    y.add(x) print(y) y.add_(x)  print(y) 
 
注意:函数名后面带下划线_ 的函数会修改Tensor本身。例如,x.add_(y)和x.t_()会改变 x,但x.add(y)和x.t()返回一个新的Tensor, 而x不变。 
Tensor的选取操作与Numpy类似。Tensor和Numpy的数组之间的互操作非常容易且快速。对于Tensor不支持的操作,可以先转为Numpy数组处理,之后再转回Tensor 
 
x[:, 1 ] a = t.ones(5 )  b = a.numpy()  a = np.ones(5 ) b = t.from_numpy(a)  
 
Tensor和numpy对象共享内存,所以他们之间的转换很快,而且几乎不会消耗什么资源。但这也意味着,如果其中一个变了,另外一个也会随之改变 
 
 
Tensor可通过.cuda 方法转为GPU的Tensor,从而享受GPU带来的加速运算。 
 
if  t.cuda.is_available():    x = x.cuda()     y = y.cuda()     x + y 
 
  Autograd: 自动微分 
深度学习的算法本质上是通过反向传播求导数,而PyTorch的Autograd模块 则实现了此功能。在Tensor上的所有操作,Autograd都能为它们自动提供微分,避免了手动计算导数的复杂过程。 
autograd.Variable 是Autograd中的核心类,它简单封装了Tensor,并支持几乎所有Tensor有的操作。Tensor在被封装为Variable之后,可以调用它的.backward实现反向传播,自动计算所有梯度。Variable的数据结构如图所示。 
 
 
 
from  torch.autograd import  Variablex = Variable(t.ones(2 , 2 ), requires_grad = True ) x output: Variable containing:  1   1   1   1  [torch.FloatTensor of size 2x2] y = x.sum () y output: Variable containing:  4  [torch.FloatTensor of size 1 ] 
 
y.grad_fn <SumBackward0 at 0x7fc14824b860 > y.backward()  x.grad  Variable containing:  1   1   1   1  [torch.FloatTensor of size 2x2] 
 
注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以反向传播之前需把梯度清零。 
 
y.backward() x.grad output: Variable containing:  2   2   2   2  [torch.FloatTensor of size 2x2] y.backward() x.grad output: Variable containing:  3   3   3   3  [torch.FloatTensor of size 2x2] x.grad.data.zero_() output:  0   0   0   0  [torch.FloatTensor of size 2x2] y.backward() x.grad output: Variable containing:  1   1   1   1  [torch.FloatTensor of size 2x2] 
 
Variable和Tensor具有近乎一致的接口,在实际使用中可以无缝切换。 
 
x = Variable(t.ones(4 ,5 )) y = t.cos(x) x_tensor_cos = t.cos(x.data) print(y) x_tensor_cos output: Variable containing:  0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403  [torch.FloatTensor of size 4x5] Out[25 ]:  0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403   0.5403  [torch.FloatTensor of size 4x5] 
 
  神经网络 
Autograd实现了反向传播功能,但是直接用来写深度学习的代码在很多情况下还是稍显复杂,torch.nn是专门为神经网络设计的模块化接口。nn构建于 Autograd之上,可用来定义和运行神经网络。nn.Module是nn中最重要的类,可把它看成是一个网络的封装,包含网络各层定义以及forward方法,调用forward(input)方法,可返回前向传播的结果。下面就以最早的卷积神经网络:LeNet为例,来看看如何用nn.Module实现。LeNet的网络结构如图所示 
  定义网络 
定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数__init__中。如果某一层(如ReLU)不具有可学习的参数,则既可以放在构造函数中,也可以不放,但建议不放在其中,而在forward中使用nn.functional代替。 
 
import  torch.nn as  nnimport  torch.nn.functional as  Fclass  Net (nn.Module ):    def  __init__ (self ):                           super (Net, self).__init__()                           self.conv1 = nn.Conv2d(1 , 6 , 5 )                   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 = F.max_pool2d(F.relu(self.conv1(x)), (2 , 2 ))         x = F.max_pool2d(F.relu(self.conv2(x)), 2 )                   x = x.view(x.size()[0 ], -1 )          x = F.relu(self.fc1(x))         x = F.relu(self.fc2(x))         x = self.fc3(x)                 return  x net = Net() print(net) 
 
output: 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 )   (fc2): Linear(in_features=120 , out_features=84 )   (fc3): Linear(in_features=84 , out_features=10 ) ) 
 
只要在nn.Module的子类中定义了forward函数,backward函数就会自动被实现(利用Autograd)。在forward 函数中可使用任何Variable支持的函数,还可以使用if、for循环、print、log等Python语法,写法和标准的Python写法一致。 
网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。 
 
params = list (net.parameters()) print(len (params)) for  name,parameters in  net.named_parameters():    print(name,':' ,parameters.size()) 
 
output: 10 conv1.weight : torch.Size([6 , 1 , 5 , 5 ]) conv1.bias : torch.Size([6 ]) conv2.weight : torch.Size([16 , 6 , 5 , 5 ]) conv2.bias : torch.Size([16 ]) fc1.weight : torch.Size([120 , 400 ]) fc1.bias : torch.Size([120 ]) fc2.weight : torch.Size([84 , 120 ]) fc2.bias : torch.Size([84 ]) fc3.weight : torch.Size([10 , 84 ]) fc3.bias : torch.Size([10 ]) 
 
forward函数的输入和输出都是Variable,只有Variable才具有自动求导功能,而Tensor是没有的,所以在输入时,需把Tensor封装成Variable。 
 
input  = Variable(t.randn(1 , 1 , 32 , 32 ).float ())out = net(input ) out.size() 
 
output: torch.Size([1 , 10 ]) 
 
net.zero_grad()  out.backward(Variable(t.ones(1 ,10 )))  
 
  损失函数 
nn实现了神经网络中大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失 
 
output = net(input ) target = Variable(t.arange(0 ,10 ).float ())   criterion = nn.MSELoss() loss = criterion(output, target) loss 
 
output: Variable containing:  28.5536  [torch.FloatTensor of size 1 ] 
 
当调用loss.backward()时,该图会动态生成并自动微分,也即会自动计算图中参数(Parameter)的导数。 
 
net.zero_grad()  print('反向传播之前 conv1.bias的梯度' ) print(net.conv1.bias.grad) loss.backward() print('反向传播之后 conv1.bias的梯度' ) print(net.conv1.bias.grad) 
 
  优化器 
在反向传播计算完所有参数的梯度后,还需要使用优化方法来更新网络的权重和参数,例如随机梯度下降法(SGD)的更新策略如下: 
 
weight = weight - learning_rate * gradient 
 
learning_rate = 0.01  for  f in  net.parameters():    f.data.sub_(f.grad.data * learning_rate) 
 
torch.optim中实现了深度学习中绝大多数的优化方法,例如RMSProp、Adam、SGD等,更便于使用,因此大多数时候并不需要手动写上述代码 
 
import  torch.optim as  optimoptimizer = optim.SGD(net.parameters(), lr = 0.01 ) optimizer.zero_grad()  output = net(input ) loss = criterion(output, target) loss.backward()  optimizer.step() 
 
  数据加载与预处理 
在深度学习中数据加载及预处理是非常复杂繁琐的,但PyTorch提供了一些可极大简化和加快数据处理流程的工具。同时,对于常用的数据集,PyTorch也提供了封装好的接口供用户快速调用,这些数据集主要保存在torchvison中。 
torchvision实现了常用的图像数据加载功能,例如Imagenet、CIFAR10、MNIST等,以及常用的数据转换操作,这极大地方便了数据加载,并且代码具有可重用性。
  CIFAR-10分类 
使用torchvision加载并预处理CIFAR-10数据集 
定义网络 
定义损失函数和优化器 
训练网络并更新网络参数 
测试网络 
 
  CIFAR-10数据加载及预处理 
transform = transforms.Compose([         transforms.ToTensor(),          transforms.Normalize((0.5 , 0.5 , 0.5 ), (0.5 , 0.5 , 0.5 )),                               ]) trainset = tv.datasets.CIFAR10(                     root='/Users/lichao/学习资料/研究生阶段/PYTORCH_LEARNING/dataset' ,                     train=True ,                      download=True ,                     transform=transform) trainloader = t.utils.data.DataLoader(                     trainset,                      batch_size=4 ,                     shuffle=True ,                      num_workers=2 ) testset = tv.datasets.CIFAR10(                     '/Users/lichao/学习资料/研究生阶段/PYTORCH_LEARNING/dataset' ,                     train=False ,                      download=True ,                      transform=transform) testloader = t.utils.data.DataLoader(                     testset,                     batch_size=4 ,                      shuffle=False ,                     num_workers=2 ) classes = ('plane' , 'car' , 'bird' , 'cat' ,            'deer' , 'dog' , 'frog' , 'horse' , 'ship' , 'truck' ) 
 
Dataset对象是一个数据集,可以按下标访问,返回形如(data, label)的数据。 
 
(data, label) = trainset[500 ] print(classes[label]) show((data + 1 ) / 2 ).resize((100 , 100 )) 
 
Dataloader是一个可迭代的对象,它将dataset返回的每一条数据拼接成一个batch,并提供多线程加速优化和数据打乱等操作。当程序对dataset的所有数据遍历完一遍之后,相应的对Dataloader也完成了一次迭代。 
 
dataiter = iter (trainloader) images, labels = dataiter.next ()  print(' ' .join('%11s' %classes[labels[j]] for  j in  range (4 ))) show(tv.utils.make_grid((images+1 )/2 )).resize((400 ,100 )) 
 
  定义网络 
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.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 = F.max_pool2d(F.relu(self.conv1(x)), (2 , 2 ))          x = F.max_pool2d(F.relu(self.conv2(x)), 2 )          x = x.view(x.size()[0 ], -1 )          x = F.relu(self.fc1(x))         x = F.relu(self.fc2(x))         x = self.fc3(x)                 return  x net = Net() print(net) 
 
  定义损失函数和优化器(loss和optimizer) 
from  torch import  optimcriterion = nn.CrossEntropyLoss()  optimizer = optim.SGD(net.parameters(), lr=0.001 , momentum=0.9 ) 
 
  训练网络 
t.set_num_threads(8 ) for  epoch in  range (2 ):      running_loss = 0.0      for  i, data in  enumerate (trainloader, 0 ):                           inputs, labels = data         inputs, labels = Variable(inputs), Variable(labels)                           optimizer.zero_grad()                           outputs = net(inputs)         loss = criterion(outputs, labels)         loss.backward()                              optimizer.step()                           running_loss += loss.item()         if  i % 2000  == 1999 :              print('[%d, %5d] loss: %.3f'  \                   % (epoch+1 , i+1 , running_loss / 2000 ))             running_loss = 0.0  print('Finished Training' ) 
 
此处仅训练了2个epoch(遍历完一遍数据集称为一个epoch),来看看网络有没有效果。将测试图片输入到网络中,计算它的label,然后与实际的label进行比较。 
 
dataiter = iter (testloader) images, labels = dataiter.next ()  print('实际的label: ' , ' ' .join(\             '%08s' %classes[labels[j]] for  j in  range (4 ))) show(tv.utils.make_grid(images / 2  - 0.5 )).resize((400 ,100 )) 
 
outputs = net(Variable(images)) _, predicted = t.max (outputs.data, 1 ) print('预测结果: ' , ' ' .join('%5s' \             % classes[predicted[j]] for  j in  range (4 ))) 
 
correct = 0   total = 0   for  data in  testloader:    images, labels = data     outputs = net(Variable(images))     _, predicted = t.max (outputs.data, 1 )     total += labels.size(0 )     correct += (predicted == labels).sum () print('10000张测试集中的准确率为: %d %%'  % (100  * correct / total)) 
 
  在GPU训练 
就像之前把Tensor从CPU转到GPU一样,模型也可以类似地从CPU转到GPU。 
 
if  t.cuda.is_available():    net.cuda()     images = images.cuda()     labels = labels.cuda()     output = net(Variable(images))     loss= criterion(output,Variable(labels)) 
 
如果发现在GPU上并没有比CPU提速很多,实际上是因为网络比较小,GPU没有完全发挥自己的真正实力。