目录

1 张量

1.1 PyTorch是什么?

1.2 Tensors(张量)

1.3 张量操作

1.4 NumPy 转换

1.5 CUDA 张量

2 自动求导(Autograd)

2.1 张量(Tensor)

2.2 梯度

3 神经网络

3.1 定义网络

3.2 损失函数

3.3 反向传播

3.4 更新权重

4 训练一个分类器

4.1 关于数据?

4.2 训练一个图像分类器

1) 读取和归一化 CIFAR10

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

3) 定义损失函数和优化器

4) 训练网络

5) 在测试集上测试网络

6) 在GPU上训练

5 数据并行处理(多GPU)

5.1 导入和参数

5.2 虚拟数据集

5.3 简单模型

5.4 创建一个模型和数据并行

5.5 运行模型

5.6 结果

总结


1 张量

1.1 PyTorch是什么?

pytorch是基于Python的科学计算包,服务于以下两种场景:

  • 作为NumPy的替代品,可以使用GPU的强大计算能力
  • 提供最大的灵活性和高速的深度学习研究平台

1.2 Tensors(张量)

Tensors与Numpy中的 ndarrays类似,但是在PyTorch中 Tensors 可以使用GPU进行计算.

from __future__ import print_function
import torch

创建一个 5x3 矩阵, 但是未初始化:

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

 输出结果如下,由于没有初始化,因此矩阵中的数值全是0。

tensor([[0.0000, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000],
        [0.0000, 0.0000, 0.0000]])

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

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

 输出结果如下,矩阵中的每个数值都是随机产生的。

tensor([[0.6972, 0.0231, 0.3087],
        [0.2083, 0.6141, 0.6896],
        [0.7228, 0.9715, 0.5304],
        [0.7727, 0.1621, 0.9777],
        [0.6526, 0.6170, 0.2605]])

创建一个全是0的矩阵,数据类型为long: 

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

创建tensor并使用现有数据初始化:

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

 输出结果为:

tensor([5.5000, 3.0000])

根据现有的张量创建张量。 这些方法将重用输入张量的属性,例如, dtype,除非设置新的值进行覆盖

x = x.new_ones(5, 3, dtype=torch.double) # new_* 方法来创建对象
print(x)

x = torch.randn_like(x, dtype=torch.float) # 覆盖 dtype!
print(x) #  对象的size 是相同的,只是值和类型发生了变化

输出结果为: 

tensor([[1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.],
        [1., 1., 1.]], dtype=torch.float64)
tensor([[ 0.5691, -2.0126, -0.4064],
        [-0.0863,  0.4692, -1.1209],
        [-1.1177, -0.5764, -0.5363],
        [-0.4390,  0.6688,  0.0889],
        [ 1.3334, -1.1600,  1.8457]])

 获取上述创建的x张量的size:

print(x.size())

输出结果为:

torch.Size([5, 3])

 注意:"torch.Size" 返回值是 tuple类型, 所以它支持tuple类型的所有操作.

1.3 张量操作

操作有多种语法,们将看一下加法运算。

加法1:

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

输出结果为:

tensor([[ 0.7808, -1.4388,  0.3151],
        [-0.0076,  1.0716, -0.8465],
        [-0.8175,  0.3625, -0.2005],
        [ 0.2435,  0.8512,  0.7142],
        [ 1.4737, -0.8545,  2.4833]])

 加法2:

print(torch.add(x, y))

输出结果为:

tensor([[ 0.7808, -1.4388,  0.3151],
        [-0.0076,  1.0716, -0.8465],
        [-0.8175,  0.3625, -0.2005],
        [ 0.2435,  0.8512,  0.7142],
        [ 1.4737, -0.8545,  2.4833]])

 提供输出tensor作为参数

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

输出结果为:

tensor([[ 0.7808, -1.4388,  0.3151],
        [-0.0076,  1.0716, -0.8465],
        [-0.8175,  0.3625, -0.2005],
        [ 0.2435,  0.8512,  0.7142],
        [ 1.4737, -0.8545,  2.4833]])

替换:

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

输出结果为:

tensor([[ 0.7808, -1.4388,  0.3151],
        [-0.0076,  1.0716, -0.8465],
        [-0.8175,  0.3625, -0.2005],
        [ 0.2435,  0.8512,  0.7142],
        [ 1.4737, -0.8545,  2.4833]])

 注意:任何 以"_" 结尾的操作都会用结果替换原变量. 例如: "x.copy_(y)", "x.t_()", 都会改变 "x".

你可以使用与NumPy索引方式相同的操作来进行对张量的操作:

print(x[:, 1])

输出结果为:

tensor([-2.0126,  0.4692, -0.5764,  0.6688, -1.1600])

torch.view: 可以改变张量的维度和大小

注意:torch.view 与Numpy的reshape类似

x = torch.randn(4, 4)
y = x.view(16)
z = x.view(-1, 8)  #  size -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.2368])
-0.23680149018764496

1.4 NumPy 转换

将一个Torch Tensor转换为NumPy数组是一件轻松的事,反之亦然。Torch Tensor与NumPy数组共享底层内存地址,修改一个会导致另一个的变化。

将一个Torch Tensor转换为NumPy数组:

a = torch.ones(5)
print(a)
b = a.numpy()
print(b)

 输出结果为:

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

观察numpy数组的值是如何改变的。

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

 输出结果为:

tensor([2., 2., 2., 2., 2.])
[2. 2. 2. 2. 2.]

 NumPy Array 转化成 Torch Tensor

使用from_numpy自动转化:

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.]
tensor([2., 2., 2., 2., 2.], dtype=torch.float64)

所有的 Tensor 类型默认都是基于CPU, CharTensor 类型不支持到 NumPy 的转换. 

1.5 CUDA 张量

使用.to 方法可以将Tensor移动到任何设备中。

# is_available 函数判断是否有cuda可以使用
# "torch.device"将张量移动到指定的设备中
if torch.cuda.is_available():
    device = torch.device("cuda")          # a CUDA 设备对象
    y = torch.ones_like(x, device=device)  # 直接从GPU创建张量
    x = x.to(device)                       # 或者直接使用".to("cuda")"将张量移动到cuda中
    z = x + y
    print(z)
    print(z.to("cpu", torch.double))       # ".to" 也会对变量的类型做更改

输出结果为:

tensor([0.7632], device='cuda:0')
tensor([0.7632], dtype=torch.float64)

2 自动求导(Autograd)

PyTorch 中所有神经网络的核心是autograde包。 我们先简单介绍一下这个包,然后训练第一个简单的神经网络。autograde包为张量上的所有操作提供了自动求导。 它是一个在运行时定义的框架,这意味着反向传播是根据你的代码来确定如何运行,并且每次迭代可以是不同的。

2.1 张量(Tensor)

torch.Tensor是这个包的核心类。如果设置 .requires_grad 为 True,那么将会追踪所有对于该张量的操作。当完成计算后通过调用 .backward(),自动计算所有的梯度, 这个张量的所有梯度将会自动积累到 .grad 属性。要阻止张量跟踪历史记录,可以调用.detach()方法将其与计算历史记录分离,并禁止跟踪它将来的计算记录。

为了防止跟踪历史记录(和使用内存),可以将代码块包

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

装在with torch.no_grad():中。 在评估模型时特别有用,因为模型可能具有requires_grad = True的可训练参数,但是我们不需要梯度计算。在自动梯度计算中还有另外一个重要的类Function.

Tensor 和 Function互相连接并生成一个非循环图,它表示和存储了完整的计算历史。每个张量都有一个.grad_fn属性,这个属性引用了一个创建了TensorFunction(除非这个张量是用户手动创建的,即,这个张量的 grad_fn 是 None)。

如果需要计算导数,你可以在Tensor上调用.backward()。 如果Tensor是一个标量(即它包含一个元素数据)则不需要为backward()指定任何参数, 但是如果它有更多的元素,你需要指定一个gradient 参数来匹配张量的形状。

创建一个张量并设置 requires_grad=True 用来追踪他的计算历史:

import torch
x = torch.ones(2, 2, requires_grad=True)
print(x)

 输出结果为:

tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

对张量进行操作:

y = x + 2
print(y)

输出结果为:

tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)

 结果y已经被计算出来了,所以,grad_fn已经被自动生成了。

print(y.grad_fn)

 输出结果为:

<AddBackward0 object at 0x000002004F7CC248>

对y进行一个操作:

z = y * y * 3
out = z.mean()

print(z, out)

 输出结果为:

tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>

.requires_grad_( ... ) 可以改变现有张量的 requires_grad属性。 如果没有指定的话,默认输入的flag是 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)

 输出结果为:

False
True
<SumBackward0 object at 0x000002004F7D5608>

2.2 梯度

反向传播 因为 out是一个纯量(scalar),out.backward() 等out.backward(torch.tensor(1))

out.backward()
print(x.grad)

输出结果为:

tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

 现在让我们来看一个vector-Jacobian product的例子:

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)

 输出结果为:

tensor([ 293.4463,   50.6356, 1031.2501], grad_fn=<MulBackward0>)

在这个情形中,y不再是个标量。torch.autograd无法直接计算出完整的雅可比行列,但是如果我们只想要vector-Jacobian product,只需将向量作为参数传入backward

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

print(x.grad)

 输出结果为:

tensor([5.1200e+01, 5.1200e+02, 5.1200e-02])

如果.requires_grad=True但是你又不希望进行autograd的计算, 那么可以将变量包裹在 with torch.no_grad()中:

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

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

输出结果为:

True
True
False

3 神经网络

上一节已经讲过了autogradnn包依赖autograd包来定义模型并求导。 一个nn.Module包含各个层和一个forward(input)方法,该方法返回output。本节将使用torch.nn包来构建神经网络

例如:

它是一个简单的前馈神经网络,它接受一个输入,然后一层接着一层地传递,最后输出计算的结果。

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

  1. 定义包含一些可学习的参数(或者叫权重)神经网络模型;
  2. 在数据集上迭代;
  3. 通过神经网络处理输入;
  4. 计算损失(输出结果和正确值的差值大小);
  5. 将梯度反向传播回网络的参数;
  6. 更新网络的参数,主要使用如下简单的更新原则: weight = weight - learning_rate * gradient

3.1 定义网络

开始定义一个网络

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


class 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_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(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())  # conv1's .weight

输出结果为:

10
torch.Size([6, 1, 5, 5])

测试随机输入32×32。 注:这个网络(LeNet)期望的输入大小是32×32,如果使用MNIST数据集来训练这个网络,请把图片大小重新调整到32×32。

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

 可以看到输出结果为:

tensor([[ 0.1120,  0.0713,  0.1014, -0.0696, -0.1210,  0.0084, -0.0206,  0.1366,
         -0.0455, -0.0036]], grad_fn=<AddmmBackward>)

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

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

 注意:"torch.nn"只支持小批量输入。整个 "torch.nn"包都只支持小批量样本,而不支持单个样本。 例如,"nn.Conv2d"接受一个4维的张量,"每一维分别是sSamples * nChannels * Height * Width(样本数*通道数*高*宽)"。 如果你有单个样本,只需使用"input.unsqueeze(0)"来添加其它的维数.

在继续之前,我们回顾一下到目前为止用到的类。

回顾:

  • torch.Tensor:一个用过自动调用 backward()实现支持自动梯度计算的 多维数组 , 并且保存关于这个向量的梯度 w.r.t.
  • nn.Module:神经网络模块。封装参数、移动到GPU上运行、导出、加载等。
  • nn.Parameter:一种变量,当把它赋值给一个Module时,被 自动 地注册为一个参数。
  • autograd.Function:实现一个自动求导操作的前向和反向定义,每个变量操作至少创建一个函数节点,每一个Tensor的操作都回创建一个接到创建Tensor和 编码其历史 的函数的Function节点。

重点如下:

  • 定义一个网络
  • 处理输入,调用backword

还剩:

3.2 损失函数

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

注:output为网络的输出,target为实际值

nn包中有很多不同的损失函数。 nn.MSELoss是一个比较简单的损失函数,它计算输出和目标间的均方误差, 例如:

output = net(input)
target = torch.randn(10)  # 随机值作为样例
target = target.view(1, -1)  # 使target和output的shape相同
criterion = nn.MSELoss()

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

输出loss函数的值为:

tensor(0.8109, grad_fn=<MseLossBackward>)

现在,如果在反向过程中跟随loss , 使用它的 .grad_fn 属性,将看到如下所示的计算图。

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

所以,当我们调用 loss.backward()时,整张计算图都会 根据loss进行微分,而且图中所有设置为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])  # ReLU

输出结果为:

<MseLossBackward object at 0x7f3b49fe2470>
<AddmmBackward object at 0x7f3bb05f17f0>
<AccumulateGrad object at 0x7f3b4a3c34e0>

3.3 反向传播

调用loss.backward()获得反向传播的误差。但是在调用前需要清除已存在的梯度,否则梯度将被累加到已存在的梯度。

现在,我们将调用loss.backward(),并查看conv1层的偏差(bias)项在反向传播前后的梯度。

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
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([ 0.0051,  0.0042,  0.0026,  0.0152, -0.0040, -0.0036])

3.4 更新权重

在实践中最简单的权重更新规则是随机梯度下降(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等,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

4 训练一个分类器

上一讲中已经看到如何去定义一个神经网络,计算损失值和更新网络的权重。 你现在可能在想下一步。

4.1 关于数据?

一般情况下处理图像、文本、音频和视频数据时,可以使用标准的Python包来加载数据到一个numpy数组中。 然后把这个数组转换成 torch.*Tensor

  • 图像可以使用 Pillow, OpenCV
  • 音频可以使用 scipy, librosa
  • 文本可以使用原始Python和Cython来加载,或者使用 NLTK或 SpaCy 处理

特别的,对于图像任务,我们创建了一个包 torchvision,它包含了处理一些基本图像数据集的方法。这些数据集包括 Imagenet, CIFAR10, MNIST 等。除了数据加载以外,torchvision 还包含了图像转换器, torchvision.datasets 和 torch.utils.data.DataLoader

torchvision包不仅提供了巨大的便利,也避免了代码的重复。

在这个教程中,我们使用CIFAR10数据集,它有如下10个类别 :‘airplane’, ‘automobile’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’。CIFAR-10的图像都是 3x32x32大小的,即,3颜色通道,32x32像素。

4.2 训练一个图像分类器

依次按照下列顺序进行:

  1. 使用torchvision加载和归一化CIFAR10训练集和测试集
  2. 定义一个卷积神经网络
  3. 定义损失函数
  4. 在训练集上训练网络
  5. 在测试集上测试网络

1) 读取和归一化 CIFAR10

使用torchvision可以非常容易地加载CIFAR10。

import torch
import torchvision
import torchvision.transforms as transforms

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

我们展示一些训练图像。

import matplotlib.pyplot as plt
import numpy as np

# 展示图像的函数


def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    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('%5s' % classes[labels[j]] for j in range(4)))

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

从之前的神经网络一节复制神经网络代码,并修改为输入3通道图像。

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

3) 定义损失函数和优化器

我们使用交叉熵作为损失函数,使用带动量的随机梯度下降。

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

4) 训练网络

有趣的时刻开始了。 我们只需在数据迭代器上循环,将数据输入给网络,并优化。

for epoch in range(2):  # 多批次循环

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # 获取输入
        inputs, labels = data

        # 梯度置0
        optimizer.zero_grad()

        # 正向传播,反向传播,优化
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # 打印状态信息
        running_loss += loss.item()
        if i % 2000 == 1999:    # 每2000批次打印一次
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 2000))
            running_loss = 0.0

print('Finished Training')

5) 在测试集上测试网络

我们在整个训练集上进行了2次训练,但是我们需要检查网络是否从数据集中学习到有用的东西。 通过预测神经网络输出的类别标签与实际情况标签进行对比来进行检测。 如果预测正确,我们把该样本添加到正确预测列表。 第一步,显示测试集中的图片并熟悉图片内容。

dataiter = iter(testloader)
images, labels = dataiter.next()

# 显示图片
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join('%5s' % classes[labels[j]] for j in range(4)))

让我们看看神经网络认为以上图片是什么。

outputs = net(images)

 输出是10个标签的能量。 一个类别的能量越大,神经网络越认为它是这个类别。所以让我们得到最高能量的标签。

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join('%5s' % classes[predicted[j]]
                              for j in range(4)))

 输出结果如下,可以看出预测的结果还是比较准确地。

Predicted:  plane plane plane plane

接下来让看看网络在整个测试集上的结果如何。

correct = 0
total = 0
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print('Accuracy of the network on the 10000 test images: %d %%' % (
    100 * correct / total))

 输出结果为,结果看起来不错,至少比随机选择要好,随机选择的正确率为10%。 似乎网络学习到了一些东西。

Accuracy of the network on the 10000 test images: 9 %

在识别哪一个类的时候好,哪一个不好呢? 

class_total = list(0. for i in range(10))
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predicted = torch.max(outputs, 1)
        c = (predicted == labels).squeeze()
        for i in range(4):
            label = labels[i]
            class_correct[label] += c[i].item()
            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 : 99 %
Accuracy of   car :  0 %
Accuracy of  bird :  0 %
Accuracy of   cat :  0 %
Accuracy of  deer :  0 %
Accuracy of   dog :  0 %
Accuracy of  frog :  0 %
Accuracy of horse :  0 %
Accuracy of  ship :  0 %
Accuracy of truck :  0 %

下一步,我们如何在GPU上运行神经网络呢?

6) 在GPU上训练

把一个神经网络移动到GPU上训练就像把一个Tensor转换GPU上一样简单。并且这个操作会递归遍历有所模块,并将其参数和缓冲区转换为CUDA张量。

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

# 确认我们的电脑支持CUDA,然后显示CUDA信息:

print(device)

本节的其余部分假定device是CUDA设备。然后这些方法将递归遍历所有模块并将模块的参数和缓冲区 转换成CUDA张量:

net.to(device)

记住:inputs, targets 和 images 也要转换。

inputs, labels = inputs.to(device), labels.to(device)

为什么我们没注意到GPU的速度提升很多?那是因为网络非常的小。

实践: 尝试增加你的网络的宽度(第一个nn.Conv2d的第2个参数,第二个nn.Conv2d的第一个参数,它们需要是相同的数字),看看你得到了什么样的加速。

实现的目标:

  • 深入了解了PyTorch的张量库和神经网络
  • 训练了一个小网络来分类图片

注:后面会训练一个真正的网络,使识别率达到90%以上

5 数据并行处理(多GPU)

在这个教程里,我们将学习如何使用 DataParallel 来使用多GPU。PyTorch非常容易就可以使用多GPU,用如下方式把一个模型放到GPU上:

device = torch.device("cuda:0") 
model.to(device)

GPU: 然后复制所有的张量到GPU上:

mytensor = my_tensor.to(device)

请注意,只调用my_tensor.to(device)并没有复制张量到GPU上,而是返回了一个copy。所以你需要把它赋值给一个新的张量并在GPU上使用这个张量。

在多GPU上执行前向和反向传播是自然而然的事。 但是PyTorch默认将只使用一个GPU。

使用DataParallel可以轻易的让模型并行运行在多个GPU上。这才是这篇教程的核心,接下来我们将更详细的介绍它。

model = nn.DataParallel(model)

5.1 导入和参数

导入PyTorch模块和定义参数。

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader

# Parameters and DataLoaders
input_size = 5
output_size = 2

batch_size = 30
data_size = 100

# 设置device
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

5.2 虚拟数据集

制作一个虚拟(随机)数据集, 你只需实现 __getitem__.

class RandomDataset(Dataset):

    def __init__(self, size, length):
        self.len = length
        self.data = torch.randn(length, size)

    def __getitem__(self, index):
        return self.data[index]

    def __len__(self):
        return self.len

rand_loader = DataLoader(dataset=RandomDataset(input_size, data_size),
                         batch_size=batch_size, shuffle=True)

5.3 简单模型

作为演示,我们的模型只接受一个输入,执行一个线性操作,然后得到结果。 说明:DataParallel能在任何模型(CNN,RNN,Capsule Net等)上使用。我们在模型内部放置了一条打印语句来打印输入和输出向量的大小。

请注意批次的秩为0时打印的内容。

class Model(nn.Module):
    # Our model

    def __init__(self, input_size, output_size):
        super(Model, self).__init__()
        self.fc = nn.Linear(input_size, output_size)

    def forward(self, input):
        output = self.fc(input)
        print("\tIn Model: input size", input.size(),
              "output size", output.size())

        return output

5.4 创建一个模型和数据并行

这是本教程的核心部分。首先,我们需要创建一个模型实例和检测我们是否有多个GPU。 如果有多个GPU,使用nn.DataParallel来包装我们的模型。 然后通过model.to(device)把模型放到GPU上。

model = Model(input_size, output_size)
if torch.cuda.device_count() > 1:
    print("Let's use", torch.cuda.device_count(), "GPUs!")
    # dim = 0 [30, xxx] -> [10, ...], [10, ...], [10, ...] on 3 GPUs
    model = nn.DataParallel(model)

model.to(device)

输出模型的结构:

Model(
  (fc): Linear(in_features=5, out_features=2, bias=True)
)

5.5 运行模型

现在可以看到输入和输出张量的大小。

for data in rand_loader:
    input = data.to(device)
    output = model(input)
    print("Outside: input size", input.size(),
          "output_size", output.size())

打印出输入和输出张量的大小。

In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
	In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
	In Model: input size torch.Size([30, 5]) output size torch.Size([30, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
	In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])

5.6 结果

当没有或者只有一个GPU时,对30个输入和输出进行批处理,得到了期望的一样得到30个输入和输出,但是如果你有多个GPU,你得到如下的结果。

2 GPUs ~

If you have 2, you will see:

.. code:: bash

# on 2 GPUs
Let's use 2 GPUs!
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
    In Model: input size torch.Size([15, 5]) output size torch.Size([15, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([5, 5]) output size torch.Size([5, 2])
    In Model: input size torch.Size([5, 5]) output size torch.Size([5, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])

3 GPUs ~

If you have 3 GPUs, you will see:

.. code:: bash

Let's use 3 GPUs!
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
    In Model: input size torch.Size([10, 5]) output size torch.Size([10, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])

8 GPUs ~~

If you have 8, you will see:

.. code:: bash

Let's use 8 GPUs!
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([4, 5]) output size torch.Size([4, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
Outside: input size torch.Size([30, 5]) output_size torch.Size([30, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
    In Model: input size torch.Size([2, 5]) output size torch.Size([2, 2])
Outside: input size torch.Size([10, 5]) output_size torch.Size([10, 2])

总结

DataParallel会自动的划分数据,并将作业发送到多个GPU上的多个模型。 并在每个模型完成作业后,收集合并结果并返回。

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐