模型在深度学习中扮演着重要的角色,好的模型极大地促进了深度学习的发展进步,比如:
当了解使用了深度学习的项目时,一般首先需要了解该项目使用了哪些模型,因此本节将主要讲解模型定义的方式、看懂GitHub上的模型定义、根据实际需求灵活选取模型定义方式。
Module类是torch.nn模块中提供的一个模型构造类,是所有神经网络模块的积累,可以继承它来定义我们想要的模型。
PyTorch模型定义应包括两个主要部分:
__init__
方法;forward
方法基于nn.Module,可以通过Sequential,ModuleList和ModuleDict三种方式定义PyTorch模型
对应nn.Sequential()
,当模型的前向计算为简单串联各个层的计算时,Sequential类可以通过更加简单的方式定义模型。它可以通过接收一个子模块的有序字典(OrderedDict)或者一系列子模块作为参数来逐一按添加的顺序计算。
下面结合原生Module给出Sequential的定义:
class MySequential(nn.Module):
from collections import OrderedDict
def __init__(self, *args):
super(MySequential, self).__init__()
if len(args) == 1 and isinstance(args[0], OrderedDict): # 如果传入的是一个OrderedDict
for key, module in args[0].items():
self.add_module(key, module)
# add_module方法会将module添加进self._modules(一个OrderedDict)
else: # 传入的是一些Module
for idx, module in enumerate(args):
self.add_module(str(idx), module)
def forward(self, input):
# self._modules返回一个 OrderedDict,保证会按照成员添加时的顺序遍历成
for module in self._modules.values():
input = module(input)
return input
下面结合实例给出Sequential定义模型的方法:
import torch.nn as nn
net = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10),
)
print(net)
# output
Sequential(
(0): Linear(in_features=784, out_features=256, bias=True)
(1): ReLU()
(2): Linear(in_features=256, out_features=10, bias=True)
)
import collections
import torch.nn as nn
net2 = nn.Sequential(collections.OrderedDict([
('fc1', nn.Linear(784, 256)),
('relu1', nn.ReLU()),
('fc2', nn.Linear(256, 10))
]))
print(net2)
# output
Sequential(
(fc1): Linear(in_features=784, out_features=256, bias=True)
(relu1): ReLU()
(fc2): Linear(in_features=256, out_features=10, bias=True)
)
可以看到,使用Sequential定义模型非常简单、易读,同时使用Sequential定义的模型不需要再写forward。
但同时,使用Sequential会使得模型定义丧失灵活性,因为Sequential的forward方法已经定义好,没法修改模型的中间结果
对应nn.ModuleList()
,接收一个子模块(或层,即继承自nn.Module类)的列表作为输入,然后可以类似List使用append或extend添加新的子模块,同时也可以类似于List方式去访问其中的子模块。
注:ModuleList并没有定义一个网络,它只是将不同的子模块存储起来,不像Sequential那样定义了forward方法,因此需要自己根据实际情况去实现相关的forward方法。
net = nn.ModuleList([nn.Linear(784, 256), nn.ReLU()])
net.append(nn.Linear(256, 10)) # 类似List的append操作
print(net[-1]) # 类似List的索引访问
print(net)
对应nn.ModuleDict()
,其作用和ModuleList类型,只是ModuleDict能够更方便地为神经网络层添加名称(类似于python中的dict操作)
net = nn.ModuleDict({
'linear': nn.Linear(784, 256),
'act': nn.ReLU(),
})
net['output'] = nn.Linear(256, 10) # add new module like python dict
print(net['linear']) # access dict value like python dict
print(net)
__init__
和forward
方法在实际场景中,如果一个模型的深度非常大,即需要非常多层时,使用Sequential定义模型结构就需要添加多个层,使用起来不方便。
而对于大部分的模型结构(如ResNet、DenseNet等),可以发现,虽然模型有很多层,但是其中的层有很多重复出现的结构。
因此可以考虑将这些重复出现的结构抽象为一个“模块”(也叫模型块),每次向模型添加对应的模块来构造模型,从而方便模型的构建。
因此,接下来将介绍如果构建模块,以及如何利用模块快速搭建复杂模型(以U-Net为例)。
U-Net是分割(Segmentation)模型的杰作,在以医学影像为代表的诸多领域有广泛的应用。其模型结构如下,通过残差连接结构解决了模型学习中的退化问题,使得神经网络的深度能够不断扩展。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A1YPREm8-1663511567324)(images/5.2.1unet.png)]
除了自己从零开始构建PyTorch模型,有时候也会使用到现场的模型,但是模型的某部分结构不符合我们的要求,这个时候就需要对模型进行修改,常见的修改包括如下:
import torchvision.models as models
net = models.resnet50()
print(net)
# output
# ResNet(
# (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)
# (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
# (relu): ReLU(inplace=True)
# ..............
# (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))
# (fc): Linear(in_features=2048, out_features=1000, bias=True)
# )
# modify the model output so it can be a classifier
from collections import OrderedDict
classifier = nn.Sequential(OrderedDict([('fc1', nn.Linear(2048, 128)),
('relu1', nn.ReLU()),
('output', nn.Softmax(dim=1))
]))
net.fc = classifier
上述操作相当于将原本模型的最后一层替换为一个名为classifier
的结构。
当模型训练好后,我们应该如何保存和读取训练好的模型呢?另外,很多场景下我们会使用多GPU训练,模型会分布于各个GPU上,模型的保存和读取又是怎样的呢?
本节将介绍: