神经网络
可以使用 torch.nn 包来构建神经网络。 前面已经介绍了 autograd,nn 包则依赖于 autograd 包来定义模型并对它们求导。一个 nn.Module 包含了各个层和一个 forward(input) 方法,该方法返回 output。
例如,下面这个神经网络可以对数字进行分类:
这是一个简单的前馈神经网络(feed-forward network),它接受一个输入,然后将它送入下一层,一层接一层的传递,最后给出输出。
一个神经网络的典型训练过程如下:
- 定义包含一些可学习参数(或者叫权重)的神经网络
- 在输入数据集上迭代
- 通过网络处理输入
- 计算损失(输出和正确答案的距离)
- 将梯度反向传播给网络的参数
- 更新网络的权重,一般使用一个简单的规则: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__()
self.conv1 = nn.Conv2d(1, 6, (5, 5))
self.conv2 = nn.Conv2d(6, 16, (5, 5))
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
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:]
num_features = 1
for s in size:
num_features *= s
return num_features
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)
)
我们只需要定义 forward() 函数,backward() 函数会在使用 autograd 时自动定义,backward() 函数用来计算导数。可以在 forward() 函数中使用任何针对张量的操作和计算。
一个模型的可学习参数可以通过 parameters() 方法返回。
net = Net()
print(net.parameters())
params = list(net.parameters())
print(len(params))
print(params[0].size())
print(params[1].size())
print(params[2].size())
print(params[3].size())
print(params[4].size())
print(params[5].size())
print(params[6].size())
print(params[7].size())
print(params[8].size())
print(params[9].size())
---------
<generator object Module.parameters at 0x0000023FB6C3FAC0>
10
torch.Size([6, 1, 5, 5])
torch.Size([6])
torch.Size([16, 6, 5, 5])
torch.Size([16])
torch.Size([120, 400])
torch.Size([120])
torch.Size([84, 120])
torch.Size([84])
torch.Size([10, 84])
torch.Size([10])
我们尝试一个随机的 32*32 的输入。 注意:这个网络(Net)的期待输入是 32*32,如果使用 MNIST 数据集来训练这个网络,就要把图片大小重新调整为 32*32。
net = Net()
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(input)
print(out)
---------
tensor([[[[-2.0935e-01, -4.8346e-01, 5.7126e-01, ..., 6.5599e-01,
-5.4405e-01, -7.0407e-01],
[ 2.6363e+00, 5.8908e-01, 1.6754e-03, ..., 1.5790e+00,
-5.2369e-01, 1.0194e+00],
[-1.1680e+00, -3.1898e-01, 9.2407e-02, ..., 1.3243e-01,
1.7143e-01, -1.2635e+00],
...,
[-5.8946e-01, 2.5062e-01, 9.4433e-01, ..., -4.5857e-01,
-7.0780e-02, 1.7527e-01],
[ 5.1474e-01, 1.4821e+00, 1.2028e-01, ..., -1.7058e-01,
7.1429e-03, -3.3488e-01],
[ 3.4315e-01, 1.1843e+00, -8.3370e-01, ..., -6.0783e-01,
-1.1337e+00, -1.2487e+00]]]])
tensor([[ 0.0056, -0.0029, 0.0504, -0.0139, 0.0537, 0.0753, 0.1373, -0.0854, 0.0370, -0.0780]], grad_fn=<AddmmBackward0>)
将所有参数的梯度缓存清零,然后进行随机梯度的反向传播。
net.zero_grad()
out.backward(torch.randn(1, 10))
注意:torch.nn 只支持小批量处理(mini-batches),整个 torch.nn 包只支持小批量样本的输入,不支持单个样本。 比如,nn.Conv2d 接受一个 4 维的张量,即 BatchSize * Channels * Height * Width。如果是一个单独的样本,就需要使用 input.unsqueeze(0) 来添加一个“假的”批大小维度。
损失函数
一个损失函数接受一对数据(output, target)作为输入,计算一个值来估计网络的输出和目标值相差多少。
nn 包中有很多不同的损失函数,如 nn.MSELoss 就是其中比较简单的一种,它计算输出和目标的均方误差(mean-squared error)。
net = Net()
input = torch.randn(1, 1, 32, 32)
output = net(input)
target = torch.randn(10)
target = target.view(1, -1)
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
---------
tensor(1.0086, grad_fn=<MseLossBackward0>)
如果使用 loss 的 grad_fn 属性跟踪反向传播过程,会看到计算图如下:
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)
print(loss.grad_fn.next_functions[0][0])
print(loss.grad_fn.next_functions[0][0].next_functions[0][0])
---------
<MseLossBackward0 object at 0x0000023A9946D6D0>
<AddmmBackward0 object at 0x0000023A9946D640>
<AccumulateGrad object at 0x0000023A9946D640>
反向传播
我们只需要调用 loss.backward() 来进行反向传播,在这之前我们需要将现有的梯度清零,否则反向传播的梯度将会与已有的梯度累加。
我们调用 loss.backward(),并查看 conv1 层的偏置(bias)在反向传播前后的梯度。
net = Net()
input = torch.randn(1, 1, 32, 32)
output = net(input)
target = torch.randn(10)
target = target.view(1, -1)
criterion = nn.MSELoss()
loss = criterion(output, target)
net.zero_grad()
print(net.conv1.bias.grad)
loss.backward()
print(net.conv1.bias.grad)
---------
None
tensor([ 0.0262, 0.0150, 0.0157, 0.0111, -0.0205, -0.0289])
更新权重
最简单的更新规则是随机梯度下降法(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、Adam 等。为此,构建了一个较小的包 torch.optim,它实现了所有的更新方法。
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.01)
optimizer.zero_grad()
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step()
|