神经网络
可以使用 torch.nn
包构建一个神经网络。
现在,我们已经大致了解了autograd
, nn
是依赖在 autograd
定义的模型和区分它们。一个 nn.Module
包含了层、 forward(input)
方法返回 output
。
举个例子,看下面的神经网络用来分类数字图片:
这是一个简单的前馈神经网络。它接受一个输入,通过几层一个接一个的前馈,最终得到输出。
一个典型的训练神经网络的过程大致如下:
- 定义神经网络包含一些可学习的参数(或权重)
- 遍历数据集的所有样本给输入
- 通过网络处理输入
- 计算损失度(和正确的输出相差多少)
- 反馈梯度给网络的参数
- 更新网络的权重,典型的更新规则是:
weight = weight - learning_rate * gradient
定义网络
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 个输入 image channel,6 个输出 channel,3x3 square convolution
# kernel
self.conv1 = nn.Conv2d(1, 6, 3)
self.conv2 = nn.Conv2d(6, 16, 3)
# 一个伪运算符:y = Wx + b
self.fc1 = nn.Linear(16 * 6 * 6, 120) # 6*6 from image dimension
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 最大池化在 (2, 2) 窗口上
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 如果尺寸是方(square)的,你可以仅指定一个值
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=(3, 3), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(3, 3), stride=(1, 1))
(fc1): Linear(in_features=576, 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()
得到。
params = list(net.parameters())
print(len(params))
print(params[0].size()) # conv1 的权重(.weight)
10
torch.Size([6, 1, 3, 3])
尝试 (32 imes 32) 的输入。注意,这个网络(LeNet)期待的输入大小是 (32 imes 32) 。为了将此网络使用在 MNIST 数据集上,将数据集的图像大小调整到 (32 imes 32) 。
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
tensor([[ 0.0760, -0.0790, 0.0214, 0.1041, 0.0785, 0.0156, 0.0075, 0.0907,
0.0538, -0.0357]], grad_fn=<AddmmBackward>)
将所有参数的梯度缓冲区清零和随机化反向传播的梯度:
net.zero_grad()
out.backward(torch.randn(1, 10))
torch.nn
仅仅支持小批量。整个torch.nn
包仅支持训练样本的小批量作为输入,而且不能是一个样本。
举个例子,nn,.Conv2d
将接受一个 4D 的张量:nSamples * nChannels * Height * Width
如果你只有一个样本,那就使用input.unsqueeze(0)
添加一个伪批量维度。
在进一步处理之前,让我们回顾一下。
回顾
torch.Tensor
一个支持例如backward()
这样的操作的自动求梯度运算的多维数组。而且还有关于张量的梯度。nn.Module
神经网络模块。封装参数的便捷方法,并将参数移入 GPU、导出、加载等等。nn.Parameter
一种张量,当分配为Module
的属性时,可以自动地注册为参数。autograd.Function
实现自动求梯度运算的前向和后向的定义。每一个Tensor
运算至少创建一个Function
节点,连接创建Tensor
的函数并对历史进行编码。
至此,我们介绍了
- 一个神经网络的定义
- 处理输入和调用 backward
还剩
- 计算损失
- 更新网络的权重
损失函数(loss function)
损失函数接受一对输入(output,target),并计算输出的值和目标值相差多少。
有几个不同的损失函数定义在 nn 包下。一个简单的损失函数:nn.MSELoss
计算输入和目标的均方误差。
output = net(input)
target = torch.randn(10) # 本例的一个伪目标值
target = target.view(1, -1) # 将其改为和输出相同的 shape
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
tensor(1.4172, grad_fn=<MseLossBackward>)
现在,如果跟着 loss
的反向传播方向,使用它的 .grad_fn
属性,你将会看到如下所示的计算图:
print('''input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss''')
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 0x0000021A309C3040>
<AddmmBackward object at 0x0000021A309C30D0>
<AccumulateGrad object at 0x0000021A309C3040>
反向传播
为了反向传播误差,我们要做的就是 loss.backward()
我们需要清除现有的梯度,否则梯度将会累积到已经存在的梯度上。
现在,我们将调用 loss.backward()
看一下 conv1 的偏置在调用之前和之后的梯度。
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.0189, 0.0098, 0.0226, -0.0010, 0.0068, 0.0108])
至此,我们已经看到了如何使用损失函数。
神经网络的包包含了各种各样的模块和损失函数,这些模块和损失函数形成深度神经网络的组成要素。
可以去官方文档查看完整的列表。
现在,只剩下一件事:更新网络权重。
更新权重
实践中,最简单的更新规则就是随机梯度下降(SGD:Stochastic Gradient Descent):
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、BMSPrp 等等。为了使用它们,我们构建了一个小包: torch.optim
它实现了这些所有方法。使用它们非常简单:
import torch.optim as optim
# 创建你的优化控制器(optimizer)
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 在训练的循环中:
optimizer.zero_grad() # 清零梯度缓冲区
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # 更新