可以使用 torch.nn 包构建一个神经网络。

现在,我们已经大致了解了autogradnn 是依赖在 autograd 定义的模型和区分它们。一个 nn.Module 包含了层、 forward(input) 方法返回 output

举个例子,看下面的神经网络用来分类数字图片:

神经网络 - 基于 PyTorch

这是一个简单的前馈神经网络。它接受一个输入,通过几层一个接一个的前馈,最终得到输出。

一个典型的训练神经网络的过程大致如下:

  • 定义神经网络包含一些可学习的参数(或权重)
  • 遍历数据集的所有样本给输入
  • 通过网络处理输入
  • 计算损失度(和正确的输出相差多少)
  • 反馈梯度给网络的参数
  • 更新网络的权重,典型的更新规则是: 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 \times 32\) 的输入。注意,这个网络(LeNet)期待的输入大小是 \(32\times 32\) 。为了将此网络使用在 MNIST 数据集上,将数据集的图像大小调整到 \(32 \times 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()  # 更新

相关文章:

  • 2021-11-18
  • 2021-11-23
  • 2022-12-23
  • 2022-12-23
  • 2021-06-07
  • 2022-01-07
  • 2022-12-23
  • 2021-11-23
猜你喜欢
  • 2022-01-01
  • 2021-11-25
  • 2021-10-07
  • 2021-09-25
  • 2021-06-08
  • 2021-12-21
  • 2021-12-14
相关资源
相似解决方案