神经网络
神经网络(Neural Networks)¶
可以使用 torch.nn
包构造神经网络。
现在我们已经初步了解了 autograd
,nn
依靠 autograd
定义模型以及求微分。
一个 nn.Module
包含多个层,一个返回 output
的 forward(input)
方法。
例如,这个数字图像分类的网络图:
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
节点,该节点连接到创建Tensor
的Function
对象,并编码其历史。
在这一点上,我们涵盖了: - 定义审机构网络Defining a neural network - 处理输入调用backward
还剩下: - 计算损失 - 更新网络权重
损失函数¶
损失函数采用output
,target
输入对,计算输出与目标的距离估算值。
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=True
的 Tensor
对象的 .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()
手动将梯度缓冲区设置为零。这是因为梯度累积的,参见 反向传播 部分中的说明。