背景介绍
Pytorch是torch的一个衍生品,在Python语言中可以替代numpy的一个强大的科学计算库。
Pytorch与TensorFlow的主要区别:
- TensorFlow是基于静态计算图,需要先定义再运行,一次定义多次运行;
- Pytorch基于动态计算图,在运行过程中进行定义,可以实现多次构建多次运行。
神经网络
torch.nn
是神经网络的模块化接口,nn
构建于Autograd
之上,可用来定义和运行神经网络。torch.nn.module
是nn一个重要的类,既可以表示神经网络的某一层(layer),也可以表示一个包含很多层的神经网络。一般在使用中,可以继承nn.module
,编写自己的网络层。
一个典型的神经网络训练过程包括以下几点:
- 定义一个包含可训练参数(或权重)的神经网络;
- 迭代整个输入;
- 通过神经网络处理输入;
- 计算损失(当前输出的准确程度);
- 反向传播梯度到神经网络的参数;
- 更新网络的参数;
定义网络
现以Lenet网络为示例,演示一个前馈神经网络的定义过程:该网络的代码描述分为三个部分:卷积->激活->池化。
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
# nn.Module的子类函数必须在构造函数中执行父类的构造函数
super(Net, self).__init__() # 等价于nn.Module.__init__(self)
# 卷积层
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)
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(x.size()[0], -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)
)
网络的可训练参数可通过net.parameters()
返回,net.named_parameters()
可同时返回名称与参数:
param = list(net.parameters())
print(len(param))
for name, parameters in net.named_parameters():
print(name, ':', parameters.size())
输出:
10
conv1.weight : torch.Size([6, 1, 5, 5])
conv1.bias : torch.Size([6])
conv2.weight : torch.Size([16, 6, 5, 5])
conv2.bias : torch.Size([16])
fc1.weight : torch.Size([120, 400])
fc1.bias : torch.Size([120])
fc2.weight : torch.Size([84, 120])
fc2.bias : torch.Size([84])
fc3.weight : torch.Size([10, 84])
fc3.bias : torch.Size([10])
对此的x尝试32*32的输入。由于torch.nn
只支持mini-batches,一次输入必须以一个batch为单位。比如nn.Conv2d
的输入需要4维数据,形如nSamples × nChannels × Height × Width,可将样本数设为1:
input = t.randn(1, 1, 32, 32)
out = net(input)
print(out)
net.zero_grad()
out.backward(t.rand(1, 10))
输出:
tensor([[-0.0379, 0.0291, -0.0321, 0.1000, -0.0400, -0.0975, -0.0263, 0.0553, 0.0381, -0.0784]], grad_fn=<AddmmBackward>)
损失函数
nn库实现了神经网络中的大多数损失函数。例如nn.MSELoss
用来计算均方误差,nn.CrossEntropyLoss
用来计算交叉熵损失。对上例中的x定义一个随机指定的目标向量:
output = net(input)
target = t.randn(10)
target = target.view(1, -1)
criterion = nn.MSELoss()
loss = criterion(output, target)
print(loss)
print(loss.grad_fn)
print(net.conv1.bias.grad)
输出:
tensor(1.0112, grad_fn=<MseLossBackward>)
<MseLossBackward object at 0x0000024DA94798D0>
tensor([-0.0333, -0.0038, -0.0306, 0.0085, 0.0234, -0.0376])
反向传播
如果对loss进行反向传播溯源,可观察到其计算图的序列如下:input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss
,当调用loss.backward()
,该图会自动生成并自动微分,也会自动计算图中parameters的导数,而且所有在图中requires_grad = True
的张量也会让他们的grad张量累计梯度。
net.zero_grad() # 将net中所有可学习参数的梯度清零
print('反向传播之前conv1.bias的梯度')
print(net.conv1.bias.grad)
loss.backward()
print('反向传播之后conv1.bias的梯度')
print(net.conv1.bias.grad)
反向传播之前conv1.bias的梯度
tensor([0., 0., 0., 0., 0., 0.])
反向传播之后conv1.bias的梯度
tensor([ 0.0040, 0.0052, 0.0036, 0.0031, 0.0193, -0.0061])
优化器
反向传播计算完所有参数的梯度后,需要使用优化方法来更新网络的权重和参数。一般采用随机梯度下降(SGD):
torch.optim
实现了神经网络中的绝大多数优化方法,例如SGD,Adam,RMSProp等。
import torch.optim as optim
# 新建一个优化器,指定要调整的参数和学习率
optimizer = optim.SGD(net.parameters(), lr = 0.01)
# 训练之前先进行梯度清零(等同于net.zreo_grad)
optimizer.zero_grad()
# 计算损失
output = net(input)
loss = criterion(output, target)
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
print(net.conv1.bias.grad)
tensor([ 0.0018, 0.0017, 0.0028, 0.0059, 0.0082, -0.0086])