跳转至

神经网络

神经网络(Neural Networks)

可以使用 torch.nn 包构造神经网络。

现在我们已经初步了解了 autogradnn 依靠 autograd 定义模型以及求微分。 一个 nn.Module 包含多个层,一个返回 outputforward(input) 方法。

例如,这个数字图像分类的网络图:

convnet

convnet

它是一个简单的前馈网络。它接受输入,一个接一个地通过几个层输入,然后最终给出输出。

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

  • 定义具有一些可学习参数(或权重)的神经网络
  • 迭代输入数据集
  • 通过网络处理输入
  • 计算损失(输出距离正确多远)
  • 将渐变传播回网络参数
  • 更新网络权重,通常使用简单的更新规则:weight = weight - learning_rate * gradient

定义网络

我们来定义网络:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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)
1
2
3
4
5
6
7
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 函数中可以使用任何一种张量运算。

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

1
2
3
params = list(net.parameters())
print(len(params))
print(params[0].size())  # conv1's .weight
1
2
10
torch.Size([6, 1, 5, 5])

让我们尝试一个随机的32x32输入。注意:此网络(LeNet)的预期输入大小为32x32。要在MNIST数据集上使用此网络,请将数据集中的图像调整为32x32。

1
2
3
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
1
2
tensor([[ 0.0108, -0.0120, -0.0415, -0.0964,  0.0957,  0.0217, -0.1095,  0.0581,
          0.0173, -0.0540]], grad_fn=<AddmmBackward>)

将所有参数和带有随机梯度的反向传播的梯度缓冲区归零:

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

Note:


torch.nn 仅支持小批次。整个 torch.nn 包仅支持小批次的样本,而不是单个样本。

例如,nn.Conv2d 采用 nSamples x nChannels x Height x Width 4维张量。

如果是单个样本,要用 input.unsqueeze(0) 把它加到一个假的批次维度。

在继续之前,让我们回顾一下到目前为止看到的所有课程。

概括: - torch.Tensor - 它是支持像backward()这种autograd运算的多维数组,还能保存张量的梯度。 - nn.Module - 神经网络模块。提供方便的参数封装方式,移至GPU、导出、加载等辅助功能。 - nn.Parameter - 一种张量,当赋值给Module对象的属性时,它作为参数被自动注册。 - autograd.Function - 实现autograde运算的forward()backward()定义。 每次Tensor运算至少创建一个Function节点,该节点连接到创建TensorFunction对象,并编码其历史

在这一点上,我们涵盖了: - 定义审机构网络Defining a neural network - 处理输入调用backward

还剩下: - 计算损失 - 更新网络权重

损失函数

损失函数采用outputtarget输入对,计算输出与目标的距离估算值。

nn 包下有多种不同的损失函数。 比如 nn.MSELoss 就是一个简单的损失函数,它计算输入和目标之间的均方误差。

例如:

1
2
3
4
5
6
7
output = net(input)
target = torch.randn(10)  # a dummy target, for example
target = target.view(1, -1)  # make it the same shape as output
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)
1
tensor(1.4750, grad_fn=<MseLossBackward>)

现在,如果按 loss 的反方向,使用 .grad_fn 属性,就可看到这样的计算图:

1
2
3
4
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
      -> view -> linear -> relu -> linear -> relu -> linear
      -> MSELoss
      -> loss

所以,当我们调用 loss.backward(),就会求整个图关于损失的微分,图中所有具有 requires_grad=TrueTensor 对象的 .grad 张量属性都使用梯度累加。

为了说明这一点,我们进行几步反向:

1
2
3
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
1
2
3
<MseLossBackward object at 0x7fa1140c1fd0>
<AddmmBackward object at 0x7fa1140c6080>
<AccumulateGrad object at 0x7fa1140c1fd0>

反向传播

要反向传播误差,我们锁要做的就是 loss.backward()。 不过我们需要清除已有的梯度,否则梯度将被累积到已有的梯度上。

现在可以调用 loss.backward(),看看 conv1 在调用之前和之后和偏差梯度。

1
2
3
4
5
6
7
8
9
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)
1
2
3
4
conv1.bias.grad before backward
tensor([0., 0., 0., 0., 0., 0.])
conv1.bias.grad after backward
tensor([-0.0245, -0.0181,  0.0134,  0.0136,  0.0242,  0.0009])

到目前位置, 我们已经看到了如何使用损失函数。

延后阅读:

神经网络包具有由于构建深度神经网络的各种模块和损失函数。带有文档的完整列表在 https://pytorch.org/docs/nn

还剩下一个要学习的是:

  • 更新网络的权重

更新权重

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

1
 weight = weight - learning_rate * gradient

可以用简单的Python代码实现它:

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

不过,当使用神经网络时,还需要使用各种不同的更新规则,例如SGD,Nesterov-SGD,Adam,RMSProp等。 为了实现这一点,我们构建了一个小包:torch.optim,它实现了所有这些方法。使用它非常简单:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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

Note:


观察如何使用 optimizer.zero_grad() 手动将梯度缓冲区设置为零。这是因为梯度累积的,参见 反向传播 部分中的说明。