Deep Learning with PyTorch(60 Minute)

Deep Learning with PyTorch:A 60 Minute Blitz


一、PyTorch 是什么

它是一个基于Python的科学计算包,目标用户有两类:

  • 为了使用GPU来替代numpy。
  • 一个深度学习援救平台:提供最大的灵活性和速度。

开始

张量(Tensors)

张量类似于Numpy的ndarrays,不同之处在于张量可以使用GPU来加快计算。

from __future__ import print_function
import torch  

构建一个未初始化的5*3的矩阵:

x = torch.empty(5, 3)
print(x)  

构建一个随机初始化的矩阵:

x = torch.rand(5, 3)
print(x)  

构建一个以零填充且数据类型为long的矩阵:

x = torch.zeros(5, 3, dtype=torch.long)  
print(x)  

直接从数据构造张量:

x = torch.tensor([5.5, 3])
print(x)

也可以根据现有张量创建张量。这些方法将重用输入张量的属性,例如dtype,除非用户提供了新的值:

x = x.new_ones(5, 3, dtype=torch.double)   #new_* methods 获取大小 
print(x)  

x = torch.randn_like(x, dtype=torch.float)   # 重写dtype
print(x)             # 结果具有相同的大小

获取张量大小:

print(x.size())

注意:torch.Size实际上是一个元组,所以它支持所有的元组操作。

操作(Operations)

张量上的操作有多重语法形式,下面我们一加法为例进行讲解。

加法:语法1

print("x: ", x)
y = torch.rand(5, 3)
print(x + y)  

加法:语法2

print("x: ", x)
print(torch.add(x, y))   

加法:提供输出张量作为参数

result = torch.empty(5, 3)
torch.add(x, y, out=result)
print(result)

加法: in-place

#adds x to y
y.add_(x)
print(y)  

注意:任何使张量在原位发生变异的操作都是用, 例如: x.copy(y), x.t_(),都将会改变x。

可以任意使用标准Numpy-like索引:

print(x)
print(x[:, 1])  
print(x[1, :])
print(x[2, 4])  

调整大小(Resizing):如果您想调整大小/重塑张量,可以使用torch.view

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)
print(x.size(), y.size(), z.size())  

如果有一个单元张量(a one element tensor),请使用.item()将该值作为Python数字来获取

x = torch.rand(1)
print(x)
print(x.item())  

这里描述了100+张量操作,包括转置,索引,切片,数学运算,线性代数,随机数等。

NumPy Bridge

把一个torch张量转换为numpy数组或者反过来都是很简单的。

Torch张量和numpy数组将共享潜在的内存,改变其中一个也将改变另一个。

将Torch张量转换成一个NumPy数组 :

>>> a = torch.ones(5)
>>> print(a)
Out: tensor([ 1.,  1.,  1.,  1.,  1.])

>>> b = a.numpy()
>>> print(b)  
Out: [1. 1. 1. 1. 1.]  

numpy数组的值如何在改变?

a.add_(1)
print(a)
print(b)

把NumPy数组转换成Torch张量:
看看改变numpy数组如何自动改变torch张量。

import numpy as np
a = np.ones(5)
b = torch.from_numpy(a)
np.add(a, 1, out=a)
print(a)
print(b)  

所有在CPU上的张量,除了字符张量,都支持在numpy之间转换。

CUDA 张量

使用 .to 函数可以将张量移动到GPU上。

# 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)
    x = x.to(device)
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))  

Out:

tensor([ 0.5921], device=’cuda:0’)
tensor([ 0.5921], dtype=torch.float64)

Autograd: 自动求导(automatic differentiation)

PyTorch中所有神经网络的核心是autograd包。我们首先简单介绍一下这个包,然后训练我们的第一个神经网络。

autograd包为张量上的所有操作提供了自动求导.它是一个运行时定义的框架,这意味着反向传播是根据你的代码如何运行来定义,并且每次迭代可以不同.

接下来我们用一些简单的示例来看这个包。

Tensor

torch.Tensor是包的核心类,如果将其属性requires_grad设置为true,它开始跟踪它上面的所有操作。 当完成计算时可以调用.backward()并自动计算所有的梯度。该张量的梯度被计算放入搭配到.grad属性中
阻止跟踪历史的张量,可以通过调用.detch()将其从计算历史记录中分离出来,并防止跟踪将来的计算。
为了防止跟踪历史记录(和使用内存),您还可以在 with torch.no_grad()包装代码块。这在评估模型时特别有用,因为该模型可能具有requires_grad = True的可训练参数,但我们不需要梯度。

对自动求导的实现还有一个非常重要的类,即函数(Function)

张量(Tensor)和函数(Function)是相互联系的,并形成一个非循环图来构建一个完整的计算过程.每个变量有一个.grad_fn属性,它指向创建该变量的一个Function(用户自己创建的变量除外,它的grad_fn属性为None)。

如果你想计算导数,可以在一个张量上调用.backward().如果一个Tensor是一个标量(它只有一个元素值),你不必给backward()指定任何的参数,但是该Variable有多个值,你需要指定一个和该张量相同形状的的gradient参数(查看API发现实际为gradients参数)。

import torch  

# 创建一个张量,饼设置reuqires_grad=True来跟踪计算过程
x = torch.ones(2, 2, reuqires_grad=True)
print(x)

Out:
 tensor([[ 1.,  1.],
        [ 1.,  1.]])  

在张量上执行操作:

y = x + 2  
print(y)

Out:   
 tensor([[ 3.,  3.],
         [ 3.,  3.]])

因为y是通过一个操作创建的,所以它有grad_fn,而x是由用户创建,所以它的grad_fn为None.

print(y.grad_fn)

Out:  <AddBackward0 object at 0x0000020D2A5CC048>  

在张量y上执行更多操作:

z = y * y * 3  
out = z.mean()
print("z : ", z, ", out: ",  out) 

Out: z :  tensor([[ 27.,  27.],
    [ 27.,  27.]]) , out:  tensor(27.)  

.requires_grad_(…)按位(in-place)更改现有张量的requires_grad标志。如果没有给出,输入标志默认为True。

a = torch.randn(2, 2)
a = ((a * 3) / (a - 0))
print(a.requires_grad)

a.requires_grad_(True)
print(a.requires_grad)  
b = (a * a).sum()
print(b.grad_fn)  

梯度

现在我们来执行反向传播,因为out包含一个标量,out.backward()相当于执行out.backward(torch.tensor(1)):

out.backward()
# 输出out对x的梯度d(out)/d(x):
print("x.grad: ", x.grad)  

输出:

你应该得到一个值全为4.5的矩阵,我们把out称为张量O。则

我们还可以用自动求导做更多有趣的事:

x = torch.randn(3, requires_grad=True)
y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)

输出:

gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(gradients)

print(x.grad)

还可以通过使用torch.no_grad()包装代码块来停止autograd跟踪在张量上的历史记录,其中require_grad = True:

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
    print((x ** 2 ).requires_grad)  

输出:

Documentation of autograd and Function is at http://pytorch.org/docs/autograd

神经网络(Neural Networks)

可以使用torch.nn包来构建神经网络。
我们已知道autograd包,nn包依赖autograd包来定义模型并求导.一个nn.Module包含各个层和一个faward(input)方法,该方法返回output。

例如,我们来看一下下面这个分类数字图像的网络。

convnet

这是一个简单的前溃神经网络,它接受一个输入,然后一层接着一层的输入,直到最后得到结果。

神经网络的典型训练过程如下:

  • 定义神经网络模型。它有一些可学习的参数(或者权重);
  • 在输入数据集上迭代
  • 通过神经网络处理输入,主要体现在网络的前向传播;
  • 计算损失(输出结果和正确值的差距大小)
  • 将梯度反向传播回网络的参数,反向传播求梯度。
  • 根据梯度值更新网络的参数,主要使用如下简单的更新原则: weight = weight - learning_rate * gradient

定义网络

Let’s define this network:

import torch 
import torch.nn as nn
import torch.nn.functional as F

Class Net(nn.Module):

    def __init__(self):
        # super就是在子类中调用父类方法时用的。
        super(Net, self).__init__()   # 对继承自父类的属性进行初始化。而且是用父类的初始化方法来初始化继承的属性。也就是说,子类继承了父类的所有属性和方法,父类属性自然会用父类方法来进行初始化。当然,如果初始化的逻辑与父类的不同,不使用父类的方法,自己重新初始化也是可以的。
        # 1 input image channel, 6 output channels, 5*5 square convution
        # kernel
        self.comv1 = nn.Conv2d(1, 6, 5)
        self.comv2 = 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 = 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:]
        num_features = 1
        for s in size:
        num_features *= s
        return num_features

net = Net()
print(net)        

输出:

learn more about the network:
在pytorch中只需要定义forward函数即可,backward函数会在使用autograd时自动创建(其中梯度是计算过的)。可以在forward函数中使用Tensor的任何操作。

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

params = list(net.parameters())
print('可学习参数的个数:', len(params))
#print("可学习的参数:", params)
print(params[0].size())  # conv1's的权值
for param in params:
    print(param.size())

以上代码段实现将该神经网络的可学习参数都放到params中,并且输出了第一层conv的参数大小.
输出:

注意:我们来尝试一个3232的随机输入,这个网络(LeNet)期望的输入大小是3232。如果使用的是MINIST数据集来训练这个网络,请把数据集中图片大小调整到32*32。

input = torch.randn(1,1,32,32)
print("input: ", input)
out = net(input)
print("out: ", out)  

输出:

把所有参数的梯度缓存区清零,然后进行随机梯度的的反向传播。

net.zero_grad()
out.backward(torch.randn(1, 10))  

Note:

  • torch.nn只支持小批量输入(mini-batches)。整个torch.nn包仅支持作为最小样本量的输入,而不支持单个样本。
  • 例如,nn.Conv2d只接受一个四维张量(nSamples nChannels Height Width),即样本数通道数高度宽度。
  • 如果你有单个样本,只需使用input.unsqueeze(0)来添加一个虚假的批量维度.

在继续之前,我们回顾一下到目前为止见过的所有类。

回顾:

  • torch.Tensor - 一个支持autograd等操作(比如banckward())的多维数组,也支持梯度w、r、t等张量。
  • nn.Module - 神经网络模块。封装参数的便捷方式,移动到GPU运行,导出,加载等。
  • nn.Parameters - 一种张量,当作为属性赋值给一个模块(Module)时,能被自动注册为一个参数。
  • autograd.Function - 实现一个自动求导操作的前向和反向定义,每个张量操作至少创建一个函数节点,该节点连接到创建Tensor并对其历史进行编码的函数。

以上内容:

  • 定义一个神经网络
  • 处理输入和调用backward

剩下的内容:

  • 计算损失值
  • 更新神经网络的权值

损失函数(Loss Function)

一个损失函数接受一对(output, target)作为输入(output为网络的输出,target为实际值),,计算一个值来评估网络的输出和目标值(实际值)相差多少。

在nn包中有几种不同的损失函数。一个简单的损失函数是:nn.MSELoss,它计算的是网络的输出和目标值之间的均方误差。

例如:

output = net(input)
target = torch.arrange(1, 11)  # 例如,虚拟目标
target = target.view(1, -1)# 使其与输出(output)形状相同
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

输出:

现在,如果反向跟踪loss,用它的属性.grad_fn,你会的到下面这样的一个计算图:

所以, 当调用loss.backward(),整个图与w、r、t的损失不同,图中所有变量(其requres_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])  # ReLU

输出:

反向传播(Backprop)

为了反向传播误差,我们所需做的是调用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)

输出:

现在我们也知道了如何使用损失函数。

神经网络包包含各种深度神经网络的构建模块和损失函数。完整的文档列表在这里

接下来是更新权重。

更新权重

最简单的更新权重的方法是:随机梯度下降(SGD)。

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等。为了能够更好地使用这些方法,Pytorch提供了一个小工具包: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  

训练分类器(Training a classifier)

What about Data?

一般来说,在处理图像、文本、音频或者视频数据时可以使用标准的python包把数据加载成一个numpy数组, 然后再把这个数组转换成一个 torch.*Tensor。

  • 对于图像来说,可以使用Pillow、OpenCV等包。
  • 对于音频来说,可以使用scipy和librosa包。
  • 对于文本来说,无论是原始的Python还是基于Cython的加载,或者NLTK和SpaCy都是有用的

特别是对于视觉,我们已经创建了一个名为torchvision的软件包,它具有常用数据集的数据加载器,如Imagenet,CIFAR10,MNIST等,以及图像数据转换器,即torchvision.datasets 和 torch.utils.data.DataLoader。

这提供了巨大的便利并避免了编写样板代码。

本例中我们将使用CIFAR-10数据集。它有以下几类: ‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。在CIFAR-10数据集中的数据大小是33232,例如尺寸为32x32像素的3通道彩色图像。

训练一个图像分类器

  • 使用torchvision加载并归一化(normalizing)CIFAR10训练和测试数据集。
  • 定义一个卷积神经网络
  • 定义一个损失函数
  • 在训练集上训练网络
  • 在测试集上测试数据
  1. 加载和归一化CIFAR10

torchvison能够很简单的加载CIFAR10。

import torch  
import torchvision  
import torchvision.transforms as transforms  

torchvision数集的输出是范围为[0, 1]的PILImage图像。我们将其转换为归一化范围[-1, 1]的张量。

transform = transform.Compose([transform])  
-------------本文结束感谢您的阅读-------------
0%