Pytorch神经网络
一、Pytorch神经网络介绍
神经网络可以通过torch.nn包来创建。我们之前简单的了解了autograd,而nn会使用autograd来定义模型以及求梯度。一个nn.Module对象包括了许多网络层(layer),并且有一个forward(input)方法来返回output。
训练一个神经网络需要如下的步骤:
- 定义一个神经网络,它通常有一些可以训练的参数
- 迭代一个数据集
- 处理网络的输入
- 计算loss(会调用Module对象的forward方法)
- 计算loss对参数的梯度
- 更新参数,通常会使用如下的梯度下降方法来更新:
$$
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个通道的灰度图,输出6个通道(feature map),使用5x5的卷积核
self.conv1 = nn.Conv2d(1, 6, 5)
# 第二个卷积层也是5x5,有16个通道
self.conv2 = nn.Conv2d(6, 16, 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):
# 32x32 -> 28x28 -> 14x14
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 14x14 -> 10x10 -> 5x5
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:] # 除了batch维度之外的其它维度。
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创建。在forward函数里可以使用任何处理Tensor的函数。我们可以使用函数net.parameters()来得到模型所有的参数。
params = list(net.parameters())
print(len(params))
# 10
print(params[0].size()) # conv1的weight
# torch.Size([6, 1, 5, 5])
三、测试网络
接着我们尝试一个随机的32x32的输入来检验(sanity check)网络定义没有问题。注意:这个网络(LeNet)期望的输入大小是32x32。如果使用MNIST数据集(28x28),我们需要缩放到32x32。
input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)
# tensor([[-0.0021, 0.0369, 0.0975, -0.0612, 0.1577, 0.0053, 0.1004, -0.0794,
# 0.1761, 0.0336]], grad_fn=<AddmmBackward0>)
默认的梯度会累加,因此我们通常在backward之前清除掉之前的梯度值:
net.zero_grad()
out.backward(torch.randn(1, 10))
注意:torch.nn只支持mini-batches的输入。整个torch.nn包的输入都必须第一维是batch,即使只有一个样本也要弄成batch是1的输入。
比如,nn.Conv2d的输入是一个4D的Tensor,shape是nSamples x nChannels x Height x Width。如果你只有一个样本(nChannels x Height x Width),那么可以使用input.unsqueeze(0)来增加一个batch维。
四、损失函数
损失函数的参数是(output, target)对,output是模型的预测,target是实际的值。损失函数会计算预测值和真实值的差别,损失越小说明预测的越准。
PyTorch提供了这里有许多不同的损失函数: http://pytorch.org/docs/nn.html#loss-functions。最简单的一个损失函数是:nn.MSELoss,它会计算预测值和真实值的均方误差。比如:
output = net(input)
target = torch.arange(1, 11) # 随便伪造的一个“真实值”
target = target.view(1, -1) # 把它变成output的shape(1, 10)
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
如果从loss往回走,需要使用tensor的grad_fn属性,我们Negative看到这样的计算图:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss
-> loss
因此当调用loss.backward()时,PyTorch会计算这个图中所有requires_grad=True的tensor关于loss的梯度。
print(loss.grad_fn) # MSELoss
print(loss.grad_fn.next_functions[0][0]) # Add
print(loss.grad_fn.next_functions[0][0].next_functions[0][0]) # Expand
# <MseLossBackward0 object at 0x000001643BE00220>
# <AddmmBackward0 object at 0x000001643BDD2EB0>
# <AccumulateGrad object at 0x000001643BE00220>
五、计算梯度
在调用loss.backward()之前,我们需要清除掉tensor里之前的梯度,否则会累加进去。
net.zero_grad() # 清掉tensor里缓存的梯度值。
print('conv1.bias.grad before backward')
print(net.conv1.bias.grad)
loss.backward()
print('conv1.bias.grad after backward')
print(net.conv1.bias.grad)
六、更新参数
更新参数最简单的方法是使用随机梯度下降(SGD):
$$ weight=weight−learningrate∗gradientweight=weight−learningrate∗gradient
$$
我们可以使用如下简单的代码来实现更新:
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,需要传入参数和learning rate
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 清除梯度
optimizer.zero_grad()
output = net(input)
loss = criterion(output, target)
loss.backward()
optimizer.step() # optimizer会自动帮我们更新参数
评论区