PyTorch深度学习:60分钟入门(Translation)

这是https://zhuanlan.zhihu.com/p/25572330的学习笔记。html

 

  • Tensors

Tensors和numpy中的ndarrays较为类似, 所以Tensor也可以使用GPU来加速运算。python

from __future__ import print_function
import torch
x = torch.Tensor(5, 3)  # 构造一个未初始化的5*3的矩阵
x = torch.rand(5, 3)  # 构造一个随机初始化的矩阵
x # 此处在notebook中输出x的值来查看具体的x内容
x.size()

#NOTE: torch.Size 事实上是一个tuple, 因此其支持相关的操做*
y = torch.rand(5, 3)

#此处 将两个同形矩阵相加有两种语法结构
x + y # 语法一
torch.add(x, y) # 语法二

# 另外输出tensor也有两种写法
result = torch.Tensor(5, 3) # 语法一
torch.add(x, y, out=result) # 语法二
y.add_(x) # 将y与x相加

# 特别注明:任何能够改变tensor内容的操做都会在方法名后加一个下划线'_'
# 例如:x.copy_(y), x.t_(), 这俩都会改变x的值。

#另外python中的切片操做也是资次的。
x[:,1] #这一操做会输出x矩阵的第二列的全部值

http://pytorch.org/docs/master/torch.html git

tensors的100+种用法。github

 

  • CUDA(Compute Unified Device Architecture),是显卡厂商NVIDIA推出的运算平台。 CUDA™是一种由NVIDIA推出的通用并行计算架构,该架构使GPU可以解决复杂的计算问题。

 

  • Numpy桥数组

    将Torch的Tensor和numpy的array相互转换简直就是洒洒水啦。注意Torch的Tensor和numpy的array会共享他们的存储空间,修改一个会致使另外的一个也被修改。网络

# 此处演示tensor和numpy数据结构的相互转换
a = torch.ones(5)
b = a.numpy()

# 此处演示当修改numpy数组以后,与之相关联的tensor也会相应的被修改
a.add_(1)
print(a)
print(b)

# 将numpy的Array转换为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)

# 另外除了CharTensor以外,全部的tensor均可以在CPU运算和GPU预算之间相互转换
# 使用CUDA函数来将Tensor移动到GPU上
# 当CUDA可用时会进行GPU的运算
if torch.cuda.is_available():
    x = x.cuda()
    y = y.cuda()
    x + y

 

PyTorch中的神经网络

接下来介绍pytorch中的神经网络部分。PyTorch中全部的神经网络都来自于autograd包

首先咱们来简要的看一下,以后咱们将训练咱们第一个的神经网络。数据结构

Autograd: 自动求导架构

autograd 包提供Tensor全部操做的自动求导方法。
这是一个运行时定义的框架,这意味着你的反向传播是根据你代码运行的方式来定义的,所以每一轮迭代均可以各不相同。框架

以这些例子来说,让咱们用更简单的术语来看看这些特性。dom

  • autograd.Variable 这是这个包中最核心的类。 它包装了一个Tensor,而且几乎支持全部的定义在其上的操做。一旦完成了你的运算,你能够调用 .backward()来自动计算出全部的梯度。

你能够经过属性 .data 来访问原始的tensor,而关于这一Variable的梯度则集中于 .grad 属性中。

  • 还有一个在自动求导中很是重要的类 Function。

Variable 和 Function 两者相互联系而且构建了一个描述整个运算过程的无环图。每一个Variable拥有一个 .creator 属性,其引用了一个建立Variable的 Function。(除了用户建立的Variable其 creator 部分是 None)。

若是你想要进行求导计算,你能够在Variable上调用.backward()。 若是Variable是一个标量(例如它包含一个单元素数据),你无需对backward()指定任何参数,然而若是它有更多的元素,你须要指定一个和tensor的形状想匹配的grad_output参数。

 

from torch.autograd import Variable
x = Variable(torch.ones(2, 2), requires_grad = True)
y = x + 2
y.creator

# y 是做为一个操做的结果建立的所以y有一个creator 
z = y * y * 3
out = z.mean()

# 如今咱们来使用反向传播
out.backward()

# out.backward()和操做out.backward(torch.Tensor([1.0]))是等价的
# 在此处输出 d(out)/dx
x.grad

 

最终得出的结果应该是一个全是4.5的矩阵。设置输出的变量为o。咱们经过这一公式来计算:

o = \frac{1}{4}\sum_i z_iz_i = 3(x_i+2)^2z_i\bigr\rvert_{x_i=1} = 27,所以,\frac{\partial o}{\partial x_i} = \frac{3}{2}(x_i+2),最后有\frac{\partial o}{\partial x_i}\bigr\rvert_{x_i=1} = \frac{9}{2} = 4.5

 

你可使用自动求导来作许多疯狂的事情。

 

x = torch.randn(3)
x = Variable(x, requires_grad = True)
y = x * 2
while y.data.norm() < 1000:
    y = y * 2
gradients = torch.FloatTensor([0.1, 1.0, 0.0001])  //float是类型。
y.backward(gradients)  //
x.grad //返回y关于x的梯度向量

 

神经网络

使用 torch.nn 包能够进行神经网络的构建。

如今你对autograd有了初步的了解,而nn创建在autograd的基础上来进行模型的定义和微分。

nn.Module中包含着神经网络的层,同时forward(input)方法可以将output进行返回。                                                 //看不懂

举个例子,来看一下这个数字图像分类的神经网络。

这是一个简单的前馈神经网络。 从前面获取到输入的结果,从一层传递到另外一层,最后输出最后结果。

一个典型的神经网络的训练过程是这样的:

  • 定义一个有着可学习的参数(或者权重)的神经网络
  • 对着一个输入的数据集进行迭代:
    • 用神经网络对输入进行处理
    • 计算代价值 (对输出值的修正到底有多少)
    • 将梯度传播回神经网络的参数中
    • 更新网络中的权重
      • 一般使用简单的更新规则: weight = weight + learning_rate * gradient

让咱们来定义一个神经网络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(1, 6, 5) # 1 input image channel, 6 output channels, 5x5 square convolution kernel 
        self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16*5*5, 120) # an affine operation: y = Wx + b
         FC(Full-connected)层 
 

  F6层:

输入图片大小:         (1*1)*120

卷积窗大小:            1*1

卷积窗种类:             84

输出特征图数量:    1

输出特征图大小:    84      

神经元数量:             84   

链接数:                     10164        120*84+84

可训练参数:             10164        120*84+84

F6层有84个单元(之因此选这个数字的缘由来自于输出层的设计),与C5层全相连。有10164个可训练参数。如同经典神经网络,F6层计算输入向量和权重向量之间的点积,再加上一个偏置。而后将其传递给sigmoid函数产生单元i的一个状态。

        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)) # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv2(x)), 2) # If the size is a square you can only specify a single number                         //看不懂
        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

'''神经网络的输出结果是这样的
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会自动地生成。

注意: torch.nn 只接受小批量的数据

整个torch.nn包只接受那种小批量样本的数据,而非单个样本。 例如,nn.Conv2d可以结构一个四维的TensornSamples x nChannels x Height x Width。

若是你拿的是单个样本,使用input.unsqueeze(0)来加一个假维度就能够了。

你能够在forward函数中使用全部的Tensor中的操做。

模型中可学习的参数会由net.parameters()返回。

params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1's .weight

input = Variable(torch.randn(1, 1, 32, 32))
out = net(input)
'''out 的输出结果以下
Variable containing:
-0.0158 -0.0682 -0.1239 -0.0136 -0.0645  0.0107 -0.0230 -0.0085  0.1172 -0.0393
[torch.FloatTensor of size 1x10]
'''

net.zero_grad() # 对全部的参数的梯度缓冲区进行归零                          //不明白
out.backward(torch.randn(1, 10)) # 使用随机的梯度进行反向传播              //不明白

复习一下前面咱们学到的:

  • torch.Tensor - 一个多维数组
  • autograd.Variable - 改变Tensor而且记录下来操做的历史记录。和Tensor拥有相同的API,以及backward()的一些API。同时包含着和张量相关的梯度。
  • nn.Module - 神经网络模块。便捷的数据封装,可以将运算移往GPU,还包括一些输入输出的东西。
  • nn.Parameter - 一种变量,当将任何值赋予Module时自动注册为一个参数。
  • autograd.Function - 实现了使用自动求导方法的前馈和后馈的定义。每一个Variable的操做都会生成至少一个独立的Function节点,与生成了Variable的函数相连以后记录下操做历史。

到如今咱们已经明白的部分:

  • 定义了一个神经网络。
  • 处理了输入以及实现了反馈。

仍然没整的:

  • 计算代价。
  • 更新网络中的权重。

一个代价函数接受(输出,目标)对儿的输入,并计算估计出输出与目标之间的差距。

nn package包中一些不一样的代价函数.

一个简单的代价函数:nn.MSELoss计算输入和目标之间的均方偏差。

举个例子:

output = net(input) target = Variable(torch.range(1, 10)) # a dummy target, for example criterion = nn.MSELoss() loss = criterion(output, target) '''loss的值以下 Variable containing:  38.5849 [torch.FloatTensor of size 1] ''' 

如今,若是你跟随loss从后往前看,使用.creator属性你能够看到这样的一个计算流程图:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d  
      -> view -> linear -> relu -> linear -> relu -> linear 
      -> MSELoss
      -> loss

所以当咱们调用loss.backward()时整个图经过代价来进行区分,图中全部的变量都会以.grad来累积梯度。

# For illustration, let us follow a few steps backward 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 0x7fe8102dd7c8> <torch.nn._functions.linear.Linear object at 0x7fe8102dd708> <torch.nn._functions.thnn.auto.Threshold object at 0x7fe8102dd648> ''' # 如今咱们应当调用loss.backward(), 以后来看看 conv1's在进行反馈以后的偏置梯度如何 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) ''' 这些步骤的输出结果以下 conv1.bias.grad before backward Variable containing:  0  0  0  0  0  0 [torch.FloatTensor of size 6] conv1.bias.grad after backward Variable containing:  0.0346 -0.0141  0.0544 -0.1224 -0.1677  0.0908 [torch.FloatTensor of size 6] ''' 

如今咱们已经了解如何使用代价函数了。(并无)

阅读材料:

神经网络包中包含着诸多用于神经网络的模块和代价函数,带有文档的完整清单在这里: torch.nn - PyTorch 0.1.9 documentation

只剩下一个没学了:

  • 更新网络的权重

最简单的更新的规则是随机梯度降低法(SGD):

weight = weight - learning_rate * gradient

咱们能够用简单的python来表示:

learning_rate = 0.01
for f in net.parameters():
    f.data.sub_(f.grad.data * learning_rate)

然而在你使用神经网络的时候你想要使用不一样种类的方法诸如:SGD, Nesterov-SGD, Adam, RMSProp, etc.

咱们构建了一个小的包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 

就是这样。

但你如今也许会想。

那么数据怎么办呢?

一般来说,当你处理图像,声音,文本,视频时须要使用python中其余独立的包来将他们转换为numpy中的数组,以后再转换为torch.*Tensor。

  • 图像的话,能够用Pillow, OpenCV。
  • 声音处理能够用scipy和librosa。
  • 文本的处理使用原生Python或者Cython以及NLTK和SpaCy均可以。

特别的对于图像,咱们有torchvision这个包可用,其中包含了一些现成的数据集如:Imagenet, CIFAR10, MNIST等等。同时还有一些转换图像用的工具。 这很是的方便而且避免了写样板代码。

本教程使用CIFAR10数据集。 咱们要进行的分类的类别有:'airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'。 这个数据集中的图像都是3通道,32x32像素的图片。

下面是对torch神经网络使用的一个实战练习。

训练一个图片分类器

咱们要按顺序作这几个步骤:

  1. 使用torchvision来读取并预处理CIFAR10数据集
  2. 定义一个卷积神经网络
  3. 定义一个代价函数
  4. 在神经网络中训练训练集数据
  5. 使用测试集数据测试神经网络

1. 读取并预处理CIFAR10

使用torchvision读取CIFAR10至关的方便。

import torchvision import torchvision.transforms as transforms # torchvision数据集的输出是在[0, 1]范围内的PILImage图片。 # 咱们此处使用归一化的方法将其转化为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(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') '''注:这一部分须要下载部分数据集 所以速度可能会有一些慢 同时你会看到这样的输出 Downloading http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./data/cifar-10-python.tar.gz Extracting tar file Done! Files already downloaded and verified ''' 

咱们来从中找几张图片看看。

# functions to show an image import matplotlib.pyplot as plt import numpy as np %matplotlib inline def imshow(img): img = img / 2 + 0.5 # unnormalize npimg = img.numpy() plt.imshow(np.transpose(npimg, (1,2,0))) # show some random training images dataiter = iter(trainloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) # print labels print(' '.join('%5s'%classes[labels[j]] for j in range(4))) 

结果是这样的:

2. 定义一个卷积神经网络

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() 

3. 定义代价函数和优化器

criterion = nn.CrossEntropyLoss() # use a Classification Cross-Entropy loss 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 # wrap them in Variable inputs, labels = Variable(inputs), Variable(labels) # 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.data[0] 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.212 [1, 4000] loss: 1.892 [1, 6000] loss: 1.681 [1, 8000] loss: 1.590 [1, 10000] loss: 1.515 [1, 12000] loss: 1.475 [2, 2000] loss: 1.409 [2, 4000] loss: 1.394 [2, 6000] loss: 1.376 [2, 8000] loss: 1.334 [2, 10000] loss: 1.313 [2, 12000] loss: 1.264 Finished Training ''' 

咱们已经训练了两遍了。 此时须要测试一下到底结果如何。

经过对比神经网络给出的分类和已知的类别结果,能够得出正确与否,若是预测的正确,咱们能够将样本加入正确预测的结果的列表中。

好的第一步,让咱们展现几张照片来熟悉一下。

dataiter = iter(testloader) images, labels = dataiter.next() # print images imshow(torchvision.utils.make_grid(images)) print('GroundTruth: ', ' '.join('%5s'%classes[labels[j]] for j in range(4))) 

结果是这样的:

好的,接下来看看神经网络如何看待这几个照片。

outputs = net(Variable(images)) # the outputs are energies for the 10 classes. # Higher the energy for a class, the more the network # thinks that the image is of the particular class # So, let's get the index of the highest energy _, predicted = torch.max(outputs.data, 1) print('Predicted: ', ' '.join('%5s'% classes[predicted[j][0]] for j in range(4))) '''输出结果为 Predicted: cat plane car plane ''' 

结果看起来挺好。

看看神经网络在整个数据集上的表现结果如何。

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: 54 % ''' 

看上去这玩意输出的结果比随机整的要好,随机选择的话从十个中选择一个出来,准确率大概只有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 : 73 % Accuracy of car : 70 % Accuracy of bird : 52 % Accuracy of cat : 27 % Accuracy of deer : 34 % Accuracy of dog : 37 % Accuracy of frog : 62 % Accuracy of horse : 72 % Accuracy of ship : 64 % Accuracy of truck : 53 % ''' 

好吧,接下来该怎么搞了?

咱们该如何将神经网络运行在GPU上呢?

在GPU上进行训练

就像你把Tensor传递给GPU进行运算同样,你也能够将神经网络传递给GPU。

这一过程将逐级进行操做,直到全部组件所有都传递到GPU上。

net.cuda()

'''输出结果为
Net (
  (conv1): Conv2d(3, 6, kernel_size=(5, 5), stride=(1, 1))
  (pool): MaxPool2d (size=(2, 2), stride=(2, 2), dilation=(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)
)
'''

记住,每一步都须要把输入和目标传给GPU。

inputs, labels = Variable(inputs.cuda()), Variable(labels.cuda())

我为何没有进行CPU运算和GPU运算的对比呢?由于神经网络实在过小了,其中的差距并不明显。

目标达成:

  • 在更高层级上理解PyTorch的Tensor库和神经网络。
  • 训练一个小的神经网络。

接下来我该去哪?

相关文章
相关标签/搜索