目录
1. 模型定义方式
Sequential
ModuleList
ModuleDict
1.1 nn.Sequential()
nn.Sequential()
使用方法:
- 直接排列
net1 = nn.Sequential(
nn.Linear(784, 256),
nn.ReLU(),
nn.Linear(256, 10),
)
- 使用
collections.OrderedDict
net2 = nn.Sequential(collections.OrderedDict([
('fc1',nn.Linear(784, 256)),
('relu1',nn.ReLU())
]))
net2.fc2 = nn.Linear(256, 10) # net2['fc2'] = nn.Linear(256, 10)
Sequential(
(fc1): Linear(in_features=784, out_features=256, bias=True)
(relu1): ReLU()
(fc2): Linear(in_features=256, out_features=10, bias=True)
)
[补充] 关于nn.Sequential()
如何实现层之间级联的代码示意:
- 在
__init__()
中分别对 直接排列的层 和 OrderDict 中的层 利用 add_module方法添加进self._modules(一个OrderedDict) forward()
: self._modules返回一个 OrderedDict, 在forward() 按照添加顺序遍历
from collections import OrderedDict
class MySequential(nn.Module):
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
1.2 nn.ModuleList() & nn.ModuleDict()
ModuleDict官网介绍 可通过遍历keys()
: Return an iterable of the ModuleDict keys.
实现forward()
ModuleList官网介绍
[注] 不同于
nn.Sequential()
,nn.ModuleList()
和nn.ModuleDict()
需定义forward()
函数。
class Net3(nn.Module):
def __init__(self):
super().__init__()
self.moduledict = nn.ModuleDict({
'fc1': nn.Linear(784, 256),
'relu1': nn.ReLU()}
)
self.moduledict['fc2'] = nn.Linear(256, 10)
def forward(self, x):
# x = self.moduledict['fc1'](x)
# x = self.moduledict['relu1'](x)
# x = self.moduledict['fc2'](x)
for key in self.moduledict.keys():
x = self.moduledict[key](x)
return x
class Net4(nn.Module):
def __init__(self):
super().__init__()
self.modulelist = nn.ModuleList([nn.Linear(784, 256), nn.ReLU()])
self.modulelist.append(nn.Linear(256, 10))
def forward(self, x):
for layer in self.modulelist:
x = layer(x)
return x
net3 = Net3()
net4 = Net4()
out3 = net3(x)
out4 = net4(x)
net3, out3, net4, out4
(Net3(
(moduledict): ModuleDict(
(fc1): Linear(in_features=784, out_features=256, bias=True)
(relu1): ReLU()
(fc2): Linear(in_features=256, out_features=10, bias=True)
)
),
tensor([[-0.2505, 0.3850, 0.2285, -0.2629, 0.4223, -0.1359, -0.0014, -0.0182,
0.2301, -0.2082],
[-0.2505, 0.3850, 0.2285, -0.2629, 0.4223, -0.1359, -0.0014, -0.0182,
0.2301, -0.2082],
[-0.2505, 0.3850, 0.2285, -0.2629, 0.4223, -0.1359, -0.0014, -0.0182,
0.2301, -0.2082],
[-0.2505, 0.3850, 0.2285, -0.2629, 0.4223, -0.1359, -0.0014, -0.0182,
0.2301, -0.2082]], grad_fn=<AddmmBackward0>),
Net4(
(modulelist): ModuleList(
(0): Linear(in_features=784, out_features=256, bias=True)
(1): ReLU()
(2): Linear(in_features=256, out_features=10, bias=True)
)
),
tensor([[-0.0620, 0.1596, -0.1370, 0.3433, -0.1042, -0.1538, -0.0203, -0.2982,
-0.0164, -0.3869],
[-0.0620, 0.1596, -0.1370, 0.3433, -0.1042, -0.1538, -0.0203, -0.2982,
-0.0164, -0.3869],
[-0.0620, 0.1596, -0.1370, 0.3433, -0.1042, -0.1538, -0.0203, -0.2982,
-0.0164, -0.3869],
[-0.0620, 0.1596, -0.1370, 0.3433, -0.1042, -0.1538, -0.0203, -0.2982,
-0.0164, -0.3869]], grad_fn=<AddmmBackward0>))
PyTorch 中的 ModuleList 和 Sequential: 区别和使用场景
2. 搭建复杂网络
组装U-Net!
几点讨论:
-
卷积中的
padding
值的设置问题:
设置padding=0(默认值),符合U-Net网络结构原图,窄卷积,尺寸减少,之后上采样拼接过程中需添加裁剪的代码。
设置padding=1(教程示范代码),等宽卷积,尺寸不变,后续上采样过程中无需实现裁剪操作。 -
在上采样过程中使用了两种方法:
nn.Upsample
( nn.Upsample解析):采用线性插值(mode=‘bilinear’),宽高变成两倍(scale_factor=2),但使用nn.Upsample
通道数不会发生变化,需在后续双层卷积操作中调整。nn.ConvTranspose2d
: 转置卷积,设置宽高翻倍,通道减半。
-
关于上采样中拼接部分分析:
在Up类定义的forward中,完成上采样和拼接操作。
由于不是等宽卷积,拼接前需要对两个特征图x1和x2进行宽高的匹配。
对尺寸较小的x1进行扩充,采用F.pad() (形象理解参考F.pad() ),实现分别在宽和高两个维度的左右两侧均匀的补零操作。接着便可以用torch.cat()
在dim=1
即在通道channel上拼接。
3. 修改模型
3.1 修改模型若干层
如修改ResNet50
模型最后的fc层
- 定义替换 fc 层的分类器
from collections import OrderedDict
classifier = nn.Sequential(OrderedDict([('fc1', nn.Linear(2048, 128)),
('relu1', nn.ReLU()),
('dropout1',nn.Dropout(0.5)),
('fc2', nn.Linear(128, 10)),
('output', nn.Softmax(dim=1))
]))
- 替换
ResNet50
的fc层
net.fc = classifier
3.2 添加外部输入
如在模型中添加额外的输入变量,可以通过torch.cat((x,add_x),dim=1)
在通道上拼接。
此时需注意:
- 修改
forward()
中传入的参数,添加add_x
- 在
__init__()
中,修改输入拼接后会影响的层的参数,例如后一层的卷积的输入通道数
3.3 添加额外输出
需要输出forward()
过程中的中间变量,可在return
中返回。
需要注意,此时实例化模型,传入数据后会得到两个(或多个)输出值。
4. 模型保存和读取
4.1 保存模型
存储模型主要采用pkl,pt,pth
三种格式
关于.pt、.pth与.pkl 的区别:
PyTorch模型 .pt,.pth,.pkl 的区别 – 编码无悔 / Intent & Focused (codelast.com)
不存在格式上的区别,只是后缀名不同而已。在用torch.save()
函数保存模型文件的时候,有些人喜欢用.pt
后缀,有些人喜欢用.pth
或.pkl
,用相同的torch.save()
语句保存出来的模型文件没有什么不同。
为什么会有.pkl
这种后缀名呢?因为Python
有一个序列化模块 -pickle
,使用它保存模型时,通常会起一个以.pkl
为后缀名的文件。torch.save()
正是使用pickle
来保存模型的。
pytorch的模型和参数是分开的,可以分别保存或加载模型和参数。
pytorch有两种模型保存方式:
-
保存整个神经网络的的结构信息和模型参数信息,save的对象是网络
net
-
只保存神经网络的训练模型参数,save的对象是
net.state_dict()
# 保存整个模型
torch.save(model, save_dir)
# 保存模型权重
torch.save(model.state_dict, save_dir)
4.2 加载模型
对应两种保存模型的方式,pytorch也有两种加载模型的方式:
-
第一种保存方式,加载模型时通过
torch.load('.pth')
直接初始化新的神经网络对象; -
第二种保存方式,需要首先导入对应的网络,再通过
net.load_state_dict(torch.load('.pth'))
完成模型参数的加载。
具体又可以分为以下几种:
- 单卡保存 + 单卡加载
- 单卡保存 + 多卡加载
- 多卡保存 + 单卡加载
- 多卡保存 + 多卡加载
细节可参考深入浅出Pyotch_加载模型