Pytorch学习笔记(五)

讲解以下内容:(基础)

class torch.nn.parameter.Parameter

  1. 参数注册

class torch.nn.Module

  1. .add_module()
  2. .children()
  3. .named_children()
  4. .modules()
  5. .named_modules()
  6. .zero_grad()
  7. .parameters()
  8. .named_parameters()
  9. .state_dict()
  10. load_state_dict()

class torch.nn.Sequential

  1. .add_module()

class torch.nn.ModuleList
class torch.nn.ModuleDict
class torch.nn.ParameterList
class torch.nn.ParameterDict

  1. .clear()
  2. .items()
  3. .keys()
  4. .pop()
  5. .update()
  6. .values()

torch.nn.parameter.Parameter

可以理解为与torch.nn.Parameter一样。

(点开nn.parameter源码和nn.parameter.Parameter源码会发现二者源码不同,文件名相同但后缀不同。点开nn.parameter.Parameter源码和nn.Parameter源码会发现二者是同一个文件。即nn.Parameter等于nn.parameter.Parameternn.parameter不等于nn.Parameter。因此不区分nn.parameter.Parameternn.Parameter。至于原理可见博客:pyi与py的区别)

用法:torch.nn.parameter.Parameter(data=None, requires_grad=True)。这两个参数都比较熟悉了,不再解释。

torch.nn.parameter.ParameterTensor的子类,和Tensor不同的是如果一个TensorParameter,那么它会自动被添加到模型的参数列表里,来看下面这个例子。

class MyModel(nn.Module):
    def __init__(self, **kwargs):
        super(MyModel, self).__init__(**kwargs)
        self.weight1 = nn.Parameter(torch.rand(20, 20)) # Parameter
        self.weight2 = torch.rand(20, 20)				# tensor
    def forward(self, x):
        pass

n = MyModel()
for name, param in n.named_parameters():
    print(name)
"""
weight1
"""

上面的代码中weight1在参数列表中但是weight2却没在参数列表中。我们也称weight1被注册了,而weight2未被注册。

有关参数注册的问题

如果参数属于某个模型了,那么说明参数被注册了。可以通过上面讲到的model.parameters()model.named_parameters()来查看注册到网络上的参数。

有关注册的问题一般出现在网络迁移到GPU上训练时。当参数注册到这个网络上时,它会随着你在外部调用model.cuda()后自动迁移到GPU上,而没有注册的参数则不会随着网络迁到GPU上,这就可能导致输入在GPU上而参数不在GPU上,从而出现错误,为了说明这个现象。举个例子。

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
	def __init__(self):
		super(Net,self).__init__()
		self.weight = torch.rand((3,4)) 
	
	def forward(self,x):
		return F.linear(x,self.weight)

if __name__ == "__main__":
	batch_size = 10
	dummy = torch.rand((batch_size,4))
	net = Net()
	print(net(dummy))

上面的代码可以成功运行,因为所有的数值都是放在CPU上的,但是,一旦我们要把模型移到GPU上时

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
	def __init__(self):
		super(Net,self).__init__()
		self.weight = torch.rand((3,4))
	
	def forward(self,x):
		return F.linear(x,self.weight)

if __name__ == "__main__":
	batch_size = 10
	dummy = torch.rand((batch_size,4)).cuda()
	net = Net().cuda()
	print(net(dummy))

运行后会出现RuntimeError: Expected object of backend CUDA but got backend CPU for argument #2 'mat2'

这就是因为self.weight没有随着模型一起移到GPU上的原因,此时我们查看模型的参数,会发现并没有self.weight

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
	def __init__(self):
		super(Net,self).__init__()
		self.weight = torch.rand((3,4))
	
	def forward(self,x):
		return F.linear(x,self.weight)

if __name__ == "__main__":
	net = Net()
	for parameter in net.parameters():
		print(parameter)

上面的代码没有输出,因为net根本没有参数。

那么为了让net有参数,我们需要手动地将self.weight注册到网络上。

import torch
import torch.nn as nn
import torch.nn.functional as F

class Net(nn.Module):
	def __init__(self):
		super(Net,self).__init__()
		self.weight = nn.Parameter(torch.rand((3,4))) # 被注册的参数必须是nn.Parameter类型
			
	def forward(self,x):
		return F.linear(x,self.weight)

if __name__ == "__main__":
	net = Net()
	for parameter in net.parameters():
		print(parameter)

	batch_size = 10
	net = net.cuda()
	dummy = torch.rand((batch_size,4)).cuda()
	print(net(dummy))

此时网络的参数就有了输出,同时会随着一起迁到GPU上,输出就类似这样

Parameter containing:
tensor([...])
tensor([...])

类似的,模型注册也是这个道理。

因为ParameterTensor,即Tensor拥有的属性它都有,比如可以根据data来访问参数数值,用grad来访问参数梯度。可以自己尝试输出一下。


下面将讲解一些Containers,与torch.nn.parameter.Parameter属于同的类别吧。

torch.nn.Sequential

先不说torch.nn.Module,先说说这个。

Sequential类是继承自Module类的。其实,pytorch里面一切自定义操作基本上都是继承nn.Module类来实现的。

由Sequential创建的我们认为有序,所以可以通过索引获取Sequential中包含的每一层,因此我们称之为序贯模型。

Sequential类的三种不同实现

  • 最简单的序贯模型
import torch.nn as nn
model = nn.Sequential(
                  nn.Conv2d(1,20,5),
                  nn.ReLU(),
                  nn.Conv2d(20,64,5),
                  nn.ReLU()
                )
 
print(model)
print(model[2]) # 通过索引获取第几个层
'''
Sequential(
  (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (1): ReLU()
  (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  (3): ReLU()
)
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
'''

从输出中,我们可以看出每个层前面都对应着索引,我们可以根据索引取出对应的层。

  • 为每一层添加名称
import torch.nn as nn
from collections import OrderedDict  # 注意是有序字典,属于Python中的用法

model = nn.Sequential(OrderedDict([
    ('conv1', nn.Conv2d(1, 20, 5)),
    ('relu1', nn.ReLU()),
    ('conv2', nn.Conv2d(20, 64, 5)),
    ('relu2', nn.ReLU())
]))

print(model)
print(model[2])     # 通过索引获取第几个层
print(model.conv2)  # 通过.名字的方式获取某一层
'''
Sequential(
  (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (relu1): ReLU()
  (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  (relu2): ReLU()
)
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
'''

再观察输出,前面的索引换成了我们定义的名字,但我们并不能通过名字进行索引从而获取对应的层,还是只能通过索引值进行获取,因为Sequential只支持索引值访问。但是可以通过.名字的方式获取某一层,这与通过索引获取的结果完全一样。

model[0]		# √
model['conv1'] 	# ×
model.conv1		# √
  • 一层一层地添加进序列模型
import torch.nn as nn

model = nn.Sequential()
model.add_module("conv1", nn.Conv2d(1, 20, 5))
model.add_module('relu1', nn.ReLU())
model.add_module('conv2', nn.Conv2d(20, 64, 5))
model.add_module('relu2', nn.ReLU())

print(model)
print(model[2])  # 通过索引获取第几个层
print(model.conv2)

有点像Java可视化的感觉。

add_module()方法是在Module类中定义的,Sequential只是继承了。支持的访问方式与上面相同。

torch.nn.Module

前面几篇笔记只是去模仿着使用Module构建网络,接下来该详细说说怎么用了。

torch.nn.Module是所有网络的基类。我们创建的任何模型都应该继承这个类。

构建模型

前面已经提过了。

基本形式如下:

class myModel(nn.Module):
    def __init__(self):
        # 继承父类构造函数
        super(myModel, self).__init__()
        # 这里我们定义一些层次实例。
        self.my_conv_layer = nn.Linear()

    def forward(self, x):
        # 这里我们调用在__init__中定义好的层次实例。
        y_hat = my_conv_layer(x)
        return y_hat

添加子模型

上面也说了“add_module()方法是在Module类中定义的,Sequential只是继承了”,自然,Module中也可以该方法添加层。

self.add_module("name", torch.nn.XXX(arguements))

import torch
import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()

        self.add_module("conv1", nn.Conv2d(1, 20, 5))
        self.add_module('relu1', nn.ReLU()),
        self.add_module('conv2', nn.Conv2d(20, 64, 5)),
        self.add_module('relu2', nn.ReLU())

model = Model()

print(model)
print(model.conv2)  # 通过.名字的方式获取某一层
"""
Model(
  (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
  (relu1): ReLU()
  (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))
  (relu2): ReLU()
)
Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
"""

由于Module不是序列模型了,所以不支持索引访问,只能通过.名字的方式获取某一层。(忽略没有实现forward函数)

使用Sequential来包装

当我们的网络层次非常多的时候,在__init__中定义了很多层次后,如果要在forward函数中进行调用需要一个个嵌套,代码量将非常大,比如“笔记四”中的“简单手写数字识别”的代码。如下:

class SimpleCNN(nn.Module):
    def __init__(self):
        #继承父类构造函数
        super(SimpleCNN, self).__init__()
        # 这里我们定义一些层次实例。
        # 比如:
        # 先卷积,再relu,再池化
        self.myconv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=(3, 3), padding=1, stride=1, bias=True)
        self.myrelu1 = nn.ReLU(inplace=True)
        self.mymaxpooling1 = nn.MaxPool2d(kernel_size=(2, 2), stride=1)

        # 先卷积,再relu,再池化
        self.myconv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=(3, 3), padding=1, stride=1, bias=True)
        self.myrelu2 = nn.ReLU(inplace=True)
        self.mymaxpooling2 = nn.MaxPool2d(kernel_size=(2, 2), stride=1)

        # 先卷积,再relu,再池化
        self.myconv3 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=(3, 3), padding=1, stride=1, bias=True)
        self.myrelu3 = nn.ReLU(inplace=True)
        self.mymaxpooling3 = nn.MaxPool2d(kernel_size=(2, 2), stride=1)

        # 开始全连接 2048 -> 512 -> 64 -> 10
        self.myfullconnected1 = nn.Linear(in_features=2048, out_features=512)
        self.myrelu4 = nn.ReLU(inplace=True)
        self.myfullconnected2 = nn.Linear(in_features=512, out_features=64)
        self.myrelu5 = nn.ReLU(inplace=True)
        self.myfullconnected3 = nn.Linear(in_features=64, out_features=10)

    def forward(self, x):
        # 这里我们调用在__init__中定义好的层次实例。
        conv1 = self.myconv1(x)
        relu1 = self.myrelu1(conv1)
        maxpooling1 = self.mymaxpooling1(relu1)

        conv2 = self.myconv2(maxpooling1)
        relu2 = self.myrelu2(conv2)
        maxpooling2 = self.mymaxpooling2(relu2)

        conv3 = self.myconv3(maxpooling2)
        relu3 = self.myrelu3(conv3)
        maxpooling3 = self.mymaxpooling3(relu3)

        output = maxpooling3.view(maxpooling3.size(0), -1) # 不要忘记Linear函数的输入必须是二维的!

        fullconnected1 = self.myfullconnected1(output)
        relu4 = self.myrelu4(fullconnected1)

        fullconnected2 = self.myfullconnected2(relu4)
        relu5 = self.myrelu5(fullconnected2)

        y_hat = self.myfullconnected3(relu5)

        return y_hat

为了解决这一问题,我们可以在定义层次时,将若干个有联系的层次封装到一个Sequential中,多个独立的层次封装到不同Sequential中,这样给人的直观感受就是这个网络由多个Sequential构成。在forward中就可以只调用在__init__中定义的Sequential即可,这样不就变得容易多了嘛。

用Sequential封装一下上面的代码。
(可以采用上面讲到的Sequential封装层次的任何一种方式进行)

import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()

        # 第一次:卷积 -> relu -> 池化
        self.layer1 = nn.Sequential()
        self.layer1.add_module('conv1', nn.Conv2d(3, 32, (3, 3), (1, 1), padding=1))
        self.layer1.add_module('relu1', nn.ReLU(True))
        self.layer1.add_module('pool1', nn.MaxPool2d(2, 2))

        # 第二次:卷积 -> relu -> 池化
        self.layer2 = nn.Sequential()
        self.layer2.add_module('conv2', nn.Conv2d(32, 64, (3, 3), (1, 1), padding=1))
        self.layer2.add_module('relu2', nn.ReLU(True))
        self.layer2.add_module('pool2', nn.MaxPool2d(2, 2))

        # 第三次:卷积 -> relu -> 池化
        self.layer3 = nn.Sequential()
        self.layer3.add_module('conv3', nn.Conv2d(64, 128, (3, 3), (1, 1), padding=1))
        self.layer3.add_module('relu3', nn.ReLU(True))
        self.layer3.add_module('pool3', nn.MaxPool2d(2, 2))

        # 多个全连接层
        self.layer4 = nn.Sequential()
        self.layer4.add_module('fc1', nn.Linear(2048, 512))
        self.layer4.add_module('fc_relu1', nn.ReLU(True))
        self.layer4.add_module('fc2', nn.Linear(512, 64))
        self.layer4.add_module('fc_relu2', nn.ReLU(True))
        self.layer4.add_module('f3', nn.Linear(64, 10))

    def forward(self, x):
        conv1 = self.layer1(x)                      # 一句代码传播了三层
        conv2 = self.layer2(conv1)
        conv3 = self.layer3(conv2)
        input = conv3.view(conv3.size(0), -1)    # 转换到2维
        output = self.layer4(input)
        return output

model = SimpleCNN()
print(model)
"""
SimpleCNN(
  (layer1): Sequential(
    (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (relu1): ReLU(inplace=True)
    (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer2): Sequential(
    (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (relu2): ReLU(inplace=True)
    (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer3): Sequential(
    (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (relu3): ReLU(inplace=True)
    (pool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer4): Sequential(
    (fc1): Linear(in_features=2048, out_features=512, bias=True)
    (fc_relu1): ReLU(inplace=True)
    (fc2): Linear(in_features=512, out_features=64, bias=True)
    (fc_relu2): ReLU(inplace=True)
    (f3): Linear(in_features=64, out_features=10, bias=True)
  )
)
"""

我们将卷积、relu和池化封装成独立的一层,这样就存在了三个三个卷积层,最后再将全连接层封装成一层,总共四层,在forward中调用时就更加简单了,而且还更容易理解了,岂不美哉。

当然也可以采用OrderedDict来构建Sequential,只不过我不喜欢。

获取子模型

上面提到了只能通过.名字获取子模型,不能使用索引。如果想要获取全部子模型可以采用下面的方法。

  • model.children()方法

还是上面那段手写数字识别的代码,如果我将输出改为:(降低文章冗余)

for m in model.children() :
    print(m)
    print(type(m))
    print()

输出为:

"""
Sequential(
  (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu1): ReLU(inplace=True)
  (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)


Sequential(
  (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu2): ReLU(inplace=True)
  (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)


Sequential(
  (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu3): ReLU(inplace=True)
  (pool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)


Sequential(
  (fc1): Linear(in_features=2048, out_features=512, bias=True)
  (fc_relu1): ReLU(inplace=True)
  (fc2): Linear(in_features=512, out_features=64, bias=True)
  (fc_relu2): ReLU(inplace=True)
  (f3): Linear(in_features=64, out_features=10, bias=True)
)

"""

总共四个直接子模型,所以输出四个,每个都是Sequential类型。

  • model.named_children()方法

输出改为:

for m in model.named_children() :
    print(m)
    print(type(m))
    print()

输出为:

('layer1', Sequential(
  (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu1): ReLU(inplace=True)
  (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
))
<class 'tuple'>

('layer2', Sequential(
  (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu2): ReLU(inplace=True)
  (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
))
<class 'tuple'>

('layer3', Sequential(
  (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu3): ReLU(inplace=True)
  (pool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
))
<class 'tuple'>

('layer4', Sequential(
  (fc1): Linear(in_features=2048, out_features=512, bias=True)
  (fc_relu1): ReLU(inplace=True)
  (fc2): Linear(in_features=512, out_features=64, bias=True)
  (fc_relu2): ReLU(inplace=True)
  (f3): Linear(in_features=64, out_features=10, bias=True)
))
<class 'tuple'>

输出的是元组了,总共四个元组,每个元组由两个元素构成,第一个是子模型名称,第二个是子模型,也就是.children()方法的输出。

因为是元组,所以改写for语句,直接分别获取名称和模型:

for name, m in model.named_children() :
    print(name)
    print(m)
    print()
  • model.modules()方法

将输出改为:

for m in model.modules() :
    print(m)
    print()

输出:

"""
SimpleCNN(
  (layer1): Sequential(
    (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (relu1): ReLU(inplace=True)
    (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer2): Sequential(
    (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (relu2): ReLU(inplace=True)
    (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer3): Sequential(
    (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (relu3): ReLU(inplace=True)
    (pool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (layer4): Sequential(
    (fc1): Linear(in_features=2048, out_features=512, bias=True)
    (fc_relu1): ReLU(inplace=True)
    (fc2): Linear(in_features=512, out_features=64, bias=True)
    (fc_relu2): ReLU(inplace=True)
    (f3): Linear(in_features=64, out_features=10, bias=True)
  )
)

Sequential(
  (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu1): ReLU(inplace=True)
  (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)

Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

ReLU(inplace=True)

MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)

Sequential(
  (conv2): Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu2): ReLU(inplace=True)
  (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)

Conv2d(32, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

ReLU(inplace=True)

MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)

Sequential(
  (conv3): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (relu3): ReLU(inplace=True)
  (pool3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
)

Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))

ReLU(inplace=True)

MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)

Sequential(
  (fc1): Linear(in_features=2048, out_features=512, bias=True)
  (fc_relu1): ReLU(inplace=True)
  (fc2): Linear(in_features=512, out_features=64, bias=True)
  (fc_relu2): ReLU(inplace=True)
  (f3): Linear(in_features=64, out_features=10, bias=True)
)

Linear(in_features=2048, out_features=512, bias=True)

ReLU(inplace=True)

Linear(in_features=512, out_features=64, bias=True)

ReLU(inplace=True)

Linear(in_features=64, out_features=10, bias=True)
"""

扫一眼就能看出来,不过是在children方法的基础上递归地将Sequential中的子模型输出了而已。

  • model.named_modules()方法

不用多说了吧,光看名字也知道咋输出了。就是model.named_children()model.modules()特点的结合。

模型参数梯度清零

将module中的所有模型参数的梯度设置为0。

model.zero_grad()

模型参数的迭代器

model.parameters():返回一个 包含模型所有参数的迭代器。**一般用来当作optimizer的参数。**例如:optimizer = torch.optim.Adam(model.parameters(), lr=1e-5)

迭代器输出:for param in model.parameters()

import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        # w11 * x1 + w12 * x2 + w13 * x3 + w14 * x4 + b1 = y1
        # w21 * x1 + w22 * x2 + w23 * x3 + w24 * x4 + b2 = y2
        self.linear = nn.Linear(in_features=4, out_features=2, bias=True)

model = Model()

print(model.parameters())
for i in model.parameters() :
    print(i)
"""

Parameter containing:
tensor([[ 0.4989, -0.3205,  0.1430,  0.4302],
        [ 0.0959, -0.1399,  0.4538,  0.3097]], requires_grad=True)
Parameter containing:
tensor([-0.1364,  0.4204], requires_grad=True)
"""

模型参数的键值对迭代器

model.named_parameters():每次迭代得到的是一个元组,元组的第一个元素是字符串,即参数名,第二个元素是张量,即参数值。

用法:for name, paras in model.named_parameters()

模型参数的键值对字典

model.state_dict():返回字典,由参数名作为键,由参数值作为值构成的键值对。一般多见于模型的保存。 例如:torch.save(model.state_dict(), 'best_model.pth')

保存和加载模型参数

load_state_dict(state_dict, strict=True):state_dict保存的模型参数;当strict=True,要求预训练权重层数的键值与新构建的模型中的权重层数名称完全吻合;如果新构建的模型在层数上进行了部分微调,则上述代码就会报错:说key对应不上。此时,如果我们采用strict=False 就能够完美的解决这个问题。也即,与训练权重中与新构建网络中匹配层的键值就进行使用,没有的就默认初始化。strict默认是True。

保存用法举例:torch.save(model.state_dict(), PATH)

加载用法举例:model.load_state_dict(torch.load(PATH))

(推荐的文件后缀名是pt或pth)

import torch.nn as nn

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        # w11 * x1 + w12 * x2 + w13 * x3 + w14 * x4 + b1 = y1
        # w21 * x1 + w22 * x2 + w23 * x3 + w24 * x4 + b2 = y2
        self.linear = nn.Linear(in_features=4, out_features=2, bias=True)

model = Model()

print(model.state_dict())
"""
OrderedDict([('linear.weight', tensor([[-0.2771,  0.4257, -0.3358, -0.2229],
        [ 0.0083,  0.0629, -0.1918,  0.2075]])), ('linear.bias', tensor([0.0448, 0.2053]))])
"""

保存和加载整个模型

保存:torch.save(model, PATH)

加载:model = torch.load(PATH)

GPU与CPU之间的模型保存与读取

  • Save on GPU, Load on CPU

save:

torch.save(model.state_dict(), PATH)

Load:

device = torch.device('cpu')
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH, map_location=device))
  • Save on GPU, Load on GPU

Save:

torch.save(model.state_dict(), PATH)

Load:

device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH))
model.to(device)
# Make sure to call input = input.to(device) on any input tensors that you feed to the model
  • Save on CPU, Load on GPU

Save:

torch.save(model.state_dict(), PATH)

Load:

device = torch.device("cuda")
model = TheModelClass(*args, **kwargs)
model.load_state_dict(torch.load(PATH, map_location="cuda:0"))  # Choose whatever GPU device number you want
model.to(device)
# Make sure to call input = input.to(device) on any input tensors that you feed to the model

torch.nn.ModuleList

将子模型保存在一个list中。

ModuleList可以像一般的Python list一样被索引。而且ModuleList中包含的modules已经被正确的注册,对所有的module method可见。

创建ModuleList

import torch
from torch.autograd import Variable
import torch.nn as nn

# 创建一个model
class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        # 构建一个moduleList,里面连续放10个linear字Module
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
        
    def forward(self, x):
        # 可以这样取出来
        for i, l in enumerate(self.linears):
            x = l(x)
        return x
    
# 创建一个model
class MyModule2(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        # 构建一个moduleList,里面连续放10个linear字Module
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
        
    def forward(self, x):
        for i, l in enumerate(self.linears):
            # 也可以根据索引来取
            x = self.linears[i // 2](x)
        return x
    
model = MyModule()
print(model)
"""
MyModule(
  (linears): ModuleList(
    (0): Linear(in_features=10, out_features=10, bias=True)
    (1): Linear(in_features=10, out_features=10, bias=True)
    (2): Linear(in_features=10, out_features=10, bias=True)
    (3): Linear(in_features=10, out_features=10, bias=True)
    (4): Linear(in_features=10, out_features=10, bias=True)
    (5): Linear(in_features=10, out_features=10, bias=True)
    (6): Linear(in_features=10, out_features=10, bias=True)
    (7): Linear(in_features=10, out_features=10, bias=True)
    (8): Linear(in_features=10, out_features=10, bias=True)
    (9): Linear(in_features=10, out_features=10, bias=True)
  )
)
"""

由于是list类型,所以可以使用append和extend实现扩充子模型。


既然Sequential和ModuleList都可以进行列表化构造网络,那二者区别是什么呢。

ModuleList仅仅是一个储存各种模块的列表,这些模块之间没有联系也没有顺序(所以不用保证相邻层的输入输出维度匹配),而且没有实现forward功能需要自己实现,所以直接执行net(x)会报NotImplementedError;而Sequential内的模块需要按照顺序排列,要保证相邻层的输入输出大小相匹配,内部forward功能已经实现。

另外,ModuleList不同于一般的Python的list,加入到ModuleList里面的所有模块的参数会被自动添加到整个网络中(被注册),下面看一个例子对比一下。

class Module_ModuleList(nn.Module):
    def __init__(self):
        super(Module_ModuleList, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(10, 10)])

class Module_List(nn.Module):
    def __init__(self):
        super(Module_List, self).__init__()
        self.linears = [nn.Linear(10, 10)]

net1 = Module_ModuleList()
net2 = Module_List()

print("net1:")
for p in net1.parameters():
    print(p.size())

print("net2:")
for p in net2.parameters():
    print(p)

输出

net1:
torch.Size([10, 10])
torch.Size([10])
net2:

torch.nn.ModuleDict

将子模型保存在一个Dict中。

创建ModuleDict

ModuleDict接收一个子模块的字典作为输入,然后也可以类似字典那样进行添加访问操作:

net = nn.ModuleDict({
    'linear': nn.Linear(784, 256),
    'act': nn.ReLU(),
})
net['output'] = nn.Linear(256, 10) # 添加
print(net['linear']) # 访问
print(net.output)
print(net)
# net(torch.zeros(1, 784)) # 会报NotImplementedError

输出:

Linear(in_features=784, out_features=256, bias=True)
Linear(in_features=256, out_features=10, bias=True)
ModuleDict(
  (act): ReLU()
  (linear): Linear(in_features=784, out_features=256, bias=True)
  (output): Linear(in_features=256, out_features=10, bias=True)
)

和ModuleList一样,ModuleDict实例仅仅是存放了一些模块的字典,并没有定义forward函数需要自己定义。同样,ModuleDict也与Python的Dict有所不同,ModuleDict里的所有模块的参数会被自动添加到整个网络中。

torch.nn.ParameterList

torch.nn.ModuleList类似,该函数可以一次性生成一组参数。与普通的list不同的是,如此定义的参数被注册了,可以使用模型方法对参数操作。要求ParameterList必须由nn.Parameter组成。

import torch
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self, **kwargs):
        super(MyModel, self).__init__(**kwargs)
        self.weight1 = nn.ParameterList([nn.Parameter(torch.randn(10, 10)) for i in range(10)])
        self.weight2 = list([nn.Parameter(torch.randn(10, 10)) for i in range(10)])

    def forward(self, x):
        pass

n = MyModel()
for name, param in n.named_parameters():
    print(name, param.size())

输出如下:

weight1.0 torch.Size([10, 10])
weight1.1 torch.Size([10, 10])
weight1.2 torch.Size([10, 10])
weight1.3 torch.Size([10, 10])
weight1.4 torch.Size([10, 10])
weight1.5 torch.Size([10, 10])
weight1.6 torch.Size([10, 10])
weight1.7 torch.Size([10, 10])
weight1.8 torch.Size([10, 10])
weight1.9 torch.Size([10, 10])

可以看到weight2没有被注册。

但是self.weight2 = nn.Parameter(torch.randn(10, 10))就可以。

nn.ParameterList对象也可以使用appendextend方法。

需要注意的是,虽然直接放在python list中的参数不会自动注册,但如果只是暂时放在list里,随后又调用了nn.Sequential把整个list整合起来,参数仍然是会自动注册的。
另外一点要注意的是ModuleList和ModuleDict里面只能放Module的子类,也就是nn.Conv,nn.Linear这样的,但不能放nn.Parameter,如果要放nn.Parameter,用nn.ParameterList即可,用法和nn.ModuleList一样。

torch.nn.ParameterDict

创建ParameterDict

torch.nn.ParameterDict(parameters=None):传入一个字典,键为字符串表示名字,值为nn.Parameter类型参数。

import torch
import torch.nn as nn

class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })
            
    def forward(self, x):
        pass
        
net = MyDictDense()
print(net)
"""
MyDictDense(
  (params): ParameterDict(
      (linear1): Parameter containing: [torch.FloatTensor of size 4x4]
      (linear2): Parameter containing: [torch.FloatTensor of size 4x1]
  )
)
"""

update()

update(parameters):传入一个字典,键为字符串表示名字,值为nn.Parameter类型参数。如果名字已经存在,则更新原来的参数为新传入的参数;如果名字不存在,则相当于添加一个新的参数。

import torch
import torch.nn as nn

class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })

        self.params.update({'linear3': nn.Parameter(torch.ones(4, 2))}) # 新增
		self.params.update({'linear2': nn.Parameter(torch.zeros(4, 2))}) # 修改
		
    def forward(self, x):
        pass
        
net = MyDictDense()
print(net)
"""
MyDictDense(
  (params): ParameterDict(
      (linear1): Parameter containing: [torch.FloatTensor of size 4x4]
      (linear2): Parameter containing: [torch.FloatTensor of size 4x2]
      (linear3): Parameter containing: [torch.FloatTensor of size 4x2]
  )
)
"""

根据上面输出的参数个数和参数size,可以看出新增和修改。

clear()

clear():将调用该方法的nn.ParameterDict对象中的参数全部清除。

import torch
import torch.nn as nn

class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })

        self.params.update({'linear3': nn.Parameter(torch.ones(4, 2))}) # 新增
        self.params.update({'linear2': nn.Parameter(torch.zeros(4, 2))}) # 修改

        self.params.clear() # 移除全部的参数

    def forward(self, x):
        pass
        
net = MyDictDense()
print(net)
"""
MyDictDense(
  (params): ParameterDict()
)
"""

items()

items():返回一个迭代器,迭代器每次返回一个元组,元组的第一个元素是键(参数名),第二个元素是值(参数张量)

import torch
import torch.nn as nn

class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })

        self.params.update({'linear3': nn.Parameter(torch.ones(4, 2))}) # 新增
        self.params.update({'linear2': nn.Parameter(torch.zeros(4, 2))}) # 修改

        for key, value in self.params.items() :
            print(f'name is {key}')
            print(value)
            print(id(value) == id(self.params[key])) # 可以通过参数名作为索引,直接访问参数值
            print()

    def forward(self, x):
        pass
        
net = MyDictDense()
"""
name is linear1
Parameter containing:
tensor([[-0.6649,  0.2069,  0.1977, -0.6041],
        [ 0.3809, -1.9162,  0.6687, -0.4399],
        [-0.5952,  0.3620,  1.0929, -0.4170],
        [ 0.2388,  0.0443,  1.6960, -0.0550]], requires_grad=True)
True

name is linear2
Parameter containing:
tensor([[0., 0.],
        [0., 0.],
        [0., 0.],
        [0., 0.]], requires_grad=True)
True

name is linear3
Parameter containing:
tensor([[1., 1.],
        [1., 1.],
        [1., 1.],
        [1., 1.]], requires_grad=True)
True
"""

pop()

pop(key):从调用该方法的参数字典中弹出(删除)键(参数名)为key的参数,并返回其值。

import torch
import torch.nn as nn

class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })

        self.params.update({'linear3': nn.Parameter(torch.ones(4, 2))}) # 新增
        self.params.update({'linear2': nn.Parameter(torch.zeros(4, 2))}) # 修改

        print(self.params.pop('linear3'))
        # print(self.params['linear3']) # 报错

    def forward(self, x):
        pass
net = MyDictDense()
"""
Parameter containing:
tensor([[1., 1.],
        [1., 1.],
        [1., 1.],
        [1., 1.]], requires_grad=True)
"""

keys() & values()

keys()values()均返回迭代器,前者返回键(参数名)的迭代器,后者返回值(参数值)的迭代器。

import torch
import torch.nn as nn

class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
                'linear1': nn.Parameter(torch.randn(4, 4)),
                'linear2': nn.Parameter(torch.randn(4, 1))
        })

        self.params.update({'linear3': nn.Parameter(torch.ones(4, 2))}) # 新增
        self.params.update({'linear2': nn.Parameter(torch.zeros(4, 2))}) # 修改

        for key, value in zip(self.params.keys(), self.params.values()) :
            print(f'name is {key}')
            print(value)
            print(id(value) == id(self.params[key]))
            print()


    def forward(self, x):
        pass
net = MyDictDense()
"""
name is linear1
Parameter containing:
tensor([[ 0.7863, -1.0259, -0.9842, -0.3811],
        [ 1.2082, -0.1945,  0.5558, -1.0744],
        [ 1.0119, -1.3584, -0.5845, -0.2084],
        [ 0.3565,  0.9772, -1.6793,  0.6265]], requires_grad=True)
True

name is linear2
Parameter containing:
tensor([[0., 0.],
        [0., 0.],
        [0., 0.],
        [0., 0.]], requires_grad=True)
True

name is linear3
Parameter containing:
tensor([[1., 1.],
        [1., 1.],
        [1., 1.],
        [1., 1.]], requires_grad=True)
True
"""

发现,for key, value in zip(self.params.keys(), self.params.values()) :for key, value in self.params.items() :是等价的。

REF

[1] pytorch教程之nn.Sequential类详解——使用Sequential类来自定义顺序连接模型 - CSDN博客

[2] 卷积神经网络中nn.Conv2d()和nn.MaxPool2d()以及卷积神经网络实现minist数据集分类 - 博客园

[3] Pytorch第五课:package-torch.nn详解(1)之Containers(容器) - CSDN博客

[4] 理解 Python 装饰器看这一篇就够了 - FooFish

[5] torch.load_state_dict()函数的用法总结 - CSDN博客

[6] 4.1 模型构造 - Dive-into-DL-PyTorch

[7] Pytorch参数注册问题和nn.ModuleList nn.ModuleDict - CSDN博客

[8] 4.5 读取和存储 - Dive-into-DL-PyTorch

你可能感兴趣的:(【Pytorch学习】,pytorch,深度学习,机器学习)