神经网络可以用torch.nn包来构建。之前的内容已经了解了 自动梯度,神经网络是基于自动梯度来定义一些模型。一个 nn.Module 包含层和返回输出的方法 forward(input)。例如,看看这个分类数字图像的网络:
这是一个简单的前馈网络。 它接受输入,一个接一个地通过几个层,最后给出输出。
神经网络的典型训练过程如下:
- 定义具有一些可学习参数的神经网络(或权重)
- 迭代输入数据集
- 通过网络处理输入
- 计算损失(输出距离正确有多远)
- 将梯度传播回网络的参数
- 更新网络的权重,通常使用简单的更新规则:
weight=weight-learning_rate*gradient
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个输入图像通道,6个输出通道,5x5卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
self.conv2 = nn.Conv2d(6, 16, 5)
# 映射函数(线性):y=Wx+b
self.fc1 = nn.Linear(16 * 5 * 5, 120) # 5*5 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 = torch.flatten(x, 1) # 展平除批次维度以外的所有维度
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
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)
)
我们只需要定义前向函数,因为后向函数(计算梯度的函数)会使用 autograd 自动定义。 我们可以在 forward 前向函数中使用任何张量操作。模型的可学习参数由 net.parameters() 返回:
params = list(net.parameters())#通过调用net.parameters()返回模型可训练的参数
print(len(params))
print(params[0].size()) # conv1的权重
输出结果:
尝试生成一个随机的 32x32 输入。 因为这个网络(LeNet)的预期输入大小是 32x32。 要在 MNIST 数据集上使用此网络,需要将数据集中的图像大小调整为 32x32。
input = torch.randn(1, 1, 32, 32)#迭代一个输入
out = net(input)
print(out)
输出结果:
将所有参数的梯度缓冲区和具有随机梯度的反向传播归零:
net.zero_grad()#将所有参数梯度缓存器置零
out.backward(torch.randn(1, 10))#调用反向传播
2、损失函数
一个损失函数需要一对输入:模型输出和目标,然后计算一个值来评估输出距离目标多远。在nn包下有几个不同的损失函数。一个简单的是:nn.MSELoss,计算输出和目标之间的均方误差。
output = net(input)
target = torch.randn(10) # 例如,一个虚拟目标
target = target.view(1, -1) # 使其形状与输出相同
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
如果使用它的 .grad_fn 属性反向跟踪损失,将看到如下所示的计算图:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> flatten -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
因此,当我们调用 loss.backward() 时,整个图会根据神经网络参数进行微分,并且图中所有 requires_grad=True 的 Tensor 的 .grad Tensor 都会随着梯度累积。
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
3、反向传播
要反向传播loss,我们所要做的就是使用 loss.backward()。 在这之前需要清除现有的梯度,否则梯度将累积到现有的梯度中。现在调用 loss.backward(),然后看一下con1的偏置项在反向传播之前和之后的变化:
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)
运行结果:
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等。为了实现这一点,可以构建一个小软件包:torch.optim 实现了所有这些方法。使用它非常简单:
import torch.optim as optim
# 创建你的优化器,导入参数和学习率
optimizer = optim.SGD(net.parameters(), lr=0.01)
criterion = nn.CrossEntropyLoss()#定义交叉熵损失函数
# 在训练循环中:
optimizer.zero_grad() # 将梯度缓冲归零
output = net(input) # 数据正向传播
loss = criterion(output, target)#计算误差
loss.backward() #误差反向传播
optimizer.step() # 更新
笔记:torch.nn 仅支持小批量。 整个 torch.nn 包仅支持作为小批量样本的输入,而不是单个样本。例如,nn.Conv2d 将采用 nSamples x nChannels x Height x Width 的 4D 张量。如果您只有一个样本,只需使用 input.unsqueeze(0) 添加一个假的批次维度。