torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可用来定义和运行神经网络。nn.Module是nn中最重要的类,可以把它看做一个网络的封装,包含网络各层定义及forward方法,forward方法可返回前向传播的结果。
定义网络时,需要继承nn.Module,并实现它的forward方法,把网络中具有可学习参数的层放在构造函数__init__中。如果某一层(如ReLU)不具有可学习的参数,则既可以放在构造函数中,也可以不放。
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
# nn.Module子类的函数必须在构造函数中执行父类的构造函数
super(Net, self).__init__()
# 卷积层‘1’表示输入图片为单通道,‘6’表示输出通道数
# ‘5’表示卷积核为5*5
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)
# reshape,‘-1’表示自适应
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()
只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。在forward函数中可以使用任何Variable支持的函数,还可以使用if、for、print、log等Python语法。
网络的可学习参数通过net.parameters()返回,net.named_parameters可同时返回可学习的参数及名称。
params = list(net.parameters())
for name, parameters in net.named_parameters():
print(name, ':', parameters.size())
forward函数的输入和输出都是Variable,只有Variable才具有自动求导功能,Tensor是没有的,所以在输入时,需要把Tensor封装成Variable
input = Variable(t.randn(1, 1, 32, 32))
out = net(input)
# 所有参数的梯度清零
net.zero_grad()
torch.nn只支持mini-batches,不支持一次只输入一个样本,即一次必须是一个batch。如果只想输入一个样本,则用input.unsqueeze(0)将batch_size设为1。
nn实现了神经网络中大多数的损失函数,例如nn.MSELoss用来计算均方误差,nn.CrossEntropyLoss用来计算交叉熵损失。
output = net(input)
target = Variable(t.arange(0, 10))
criterion = nn.MSELoss()
loss = criterion(output, target)
计算图如下:
input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d
-> view -> linear -> relu -> linear -> relu -> linear
-> MSELoss -> loss
当调用loss.backward()时,该图会动态生成并自动微分,也会自动计算图中参数的导数。
在反向传播计算完所有参数的梯度后,还需要使用优化方法更新网络的权重和参数。例如,随机梯度下降法(SGD)的更新策略如下
weight = weight - learning_rate * gradient
torch.optim中实现了深度学习中绝大多数的优化方法,例如RMSProp、Adam、SGD等,更便于使用。
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.01)
# 在训练过程中
# 先梯度清零
optimizer.zero_grad()
# 计算损失
output = net(input)
less = criterion(output, target)
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
对于常用的数据集,PyTorch也提供了封装好的借口供用户快速调用,这些数据集主要保存在torchvision中。
torchvision实现了常用的图像数据加载功能,例如Imagenet、CIFAR10、MNIST等,以及常用的数据转换操作,这极大地方便了数据加载。
常用的图像相关层
- 卷积层(Conv)
- 前向卷积
- 逆卷积
- 池化层(Pool)
- 平均池化(AvgPool)
- 最大值池化(MaxPool)
- 自适应池化(AdaptiveAvgPool)
池化层可以看作是一种特殊的卷积层,用来下采样。但池化层没有可学习参数,其weight是固定的。
其它的深度学习常用层
- Linear:全连接层
- BatchNorm:批规范化层
- Dropout:dropout层,用来防止过拟合
对于前馈神经网络,如果每次都写复杂的forward函数会有些麻烦,可以用Sequential进行简化。Sequential是一个特殊的Module,它包含几个子module,前向传播时会将输入一层接一层地传递下去。
# Sequential的三种写法
net1 = nn.Sequential()
net1.add_module('conv', nn.Conv2d(3, 3, 3))
net1.add_module('batchnorm', nn.BatchNorm2d(3))
net1.add_module('activation_layer', nn.ReLU())
net2 = nn.Sequential(
nn.Conv2d(3, 3, 3),
nn.BatchNorm2d(3),
nn.ReLU()
)
from collections import OrderedDict
net3 = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(3, 3, 3)),
('bn1', nn.BatchNorm2d(3)),
('relu1', nn.ReLU())
)
# 可根据名字或序号取出子module
net1.conv, net2[0], net3.conv1
PyTorch将深度学习中常用的优化方法全部封装在torch.optim中。
所有的优化方法都是继承基类optim.Optimizer,并实现了自己的优化步骤。
SGD的使用示例:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(3, 6, 5),
nn.ReLU(),
nn.MaxPool2d(2, 2),
nn.Conv2d(6, 16, 5),
nn.ReLU(),
nn.MaxPool2d(2, 2)
)
self.classifier = nn.Sequential(
nn.Linear(16 * 5 * 5, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, 10)
)
def forward(self, x):
x = self.features(x)
x = x.view(-1, 16 * 5 * 5)
x = self.classifier(x)
return x
net = Net()
from torch import optim
optimizer = optim.SGD(params=net.parameters(), lr=1)
# 梯度清零
optimizer.zero_grad()
input = V(t.randn(1, 3, 32, 32))
output = net(input)
output.backward(output)
# 执行优化
optimizer.step()
# 为不同子网络设置不同的学习率,在finetune中经常用到
# 如果对某个参数不指定学习率,就使用默认学习率
optimizer = optim.SGD([
{'params' : net.features.parameters()},
{'params' : net.classifier.parameters(), 'lr' : 1e-2}
], lr=1e-5
)
nn中还有一个很常用的模块:nn.functional。nn中大多数layer在functional中都有一个与之相对应的函数。nn.functional中的函数和nn.Module的主要区别在于,用nn.Module实现的layers是一个特殊的类,都是由class Layer(nn.Module)定义,会自动提取可学习的参数;而nn.functional中的函数更像是纯函数,由def function(input)定义。
如果模型有可学习的参数,最好用nn.Module,否则既可以使用nn.functional也可以使用nn.Module,二者在性能上没有太大差异。
dropout建议使用nn.Dropout而不是nn.functional.dropout,因为dropout在训练和测试两个阶段的行为有所差别,使用nn.Module对象能够通过model.eval操作加以区分。
PyTorch中的nn.init模块专门为初始化设计,实现了常用的初始化策略。
nn.Module基类的构造函数的源代码:
def __init__(self):
self._parameters = OrderedDict()
self._modules = OrderedDict()
self._buffers = OrderedDict()
self._backward_hooks = OrderedDict()
self._forward_hooks = OrderedDict()
self.training = True
属性的解释:
- _parameters:字典,保存用户直接设置的parameter
- _modules:子module
- _buffers:缓存。如batchnorm使用momentum机制,每次前向传播需用到上一次前向传播的结果
- _backward_hooks与_forward_hooks:钩子技术,用来提取中间变量
- training:BarchNorm与Dropout层在训练阶段和测试阶段中采取的策略不同,通过判断training值决定前向传播策略
在PyTorch中保存模型十分简单,所有的Module对象都具有state_dict()函数,返回当前Module所有的状态数据。将这些状态数据保存后,下次使用模型时即可利用model.load_state_dict()函数将状态加载进来。
# 保存模型
t.save(net.state_dict(), 'net.pth')
# 加载已保存的模型
net2 = Net()
net2.load_state_dict(t.load('net.pth'))
将Module放在GPU上运行
- model = model.cuda():将模型的所有参数转存到GPU
- input.cuda():将输入数据放置到GPU上
在多个GPU上并行计算:
class torch.nn.DataParallel(module, device_ids=None, output_device=None, dim=0)