网络模型由layer层和module块组成。多个layer形成层组,层组即为block块。layer 和 block都是由module的子类构成的,而net(x)
实际上调用的是net.__call__(x)
。
无论是自定义块还是自定义层都需要定义__init__
和__forward__
方法。其中nn.Sequential方法用于叠加多个层,可以参考nn.ModuleList和nn.Sequential有什么区别,例子https://blog.csdn.net/qq_43369406/article/details/129998217
,其中torch.nn.Module.add_module()
方法用于添加子类,而torch.nn.Module.children()
方法用于查看该类中的所有子类。
class MySequential(nn.Module):
def __init__(self, *args):
super().__init__()
for idx, module in enumerate(args):
self.add_module(str(idx), module) # layer_name be not the same, like parameters str.
def forward(self, X):
for module in self.children():
X = module(X)
return X
需要注意的是,如果你不使用nn模块下的方法例如nn.Parameter()或者nn.Linear()定义层的话,它的required_grad属性默认是False,即**权重不是一个模型参数,不会被反向传播更新。即在求偏微分时候,他并不是作为变量出现,而永远被视作链式求导法则中的一个常量。**可以参考torch.tensor和torch.nn.Parameterhttps://blog.csdn.net/qq_43369406/article/details/131234557
self.rand_weight = torch.rand((20, 20)) # required_grad == False
self.linear = nn.LazyLinear(20) # True
你甚至可以不用例如nn.Linear
预制的层而使用自定义层如下,参考torch.tensor和torch.nn.Parameterhttps://blog.csdn.net/qq_43369406/article/details/131234557
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
self.weight = nn.Parameter(torch.randn(in_units, units))
self.bias = nn.Parameter(torch.randn(units,))
def forward(self, X):
linear = torch.matmul(X, self.weight.data) + self.bias.data
return F.relu(linear)
我们首先需要知道parameters模型参数往往分为weight权重和bias偏置,而模型参数的类型都为torch.nn.parameter.Parameter
类型。
我们可以通过net[0].weight
访问parameter中的weight param(param往往是个复合的对象,包含值,梯度和额外信息),通过net[0].weight.data
访问值,通过net[0].weight.grad
访问梯度(但在没有经过反向传播loss.backward()
时,梯度矩阵往往是None)。
我们也可以通过net[0].state_dict()
以键值对的形式来访问param;通过net.named_parameters()
生成param的生成器(包含值,梯度等等信息),通过next(net.named_parameters())
来访问键值对,需要注意的是torch.save(model.state_dict())不会保存梯度信息。该函数仅保存模型的参数(权重和偏置),而不包括梯度信息。如果要保存模型的梯度信息,可以使用完整的模型保存方法,如torch.save(model)。例子如下:
net = nn.Sequential(nn.LazyLinear(8),
nn.ReLU(),
nn.LazyLinear(1))
X = torch.rand(size=(2, 4)) # 2 means the number of sample. Linear() just chage the last dimension's data.
Y = net(X)
# type
print(type(net.state_dict()))
print(type(next(net.named_parameters())))
print(type(net.named_parameters()))
# value
print(net.state_dict())
print([(name, param.shape) for name, param in net.named_parameters()])
print(net.state_dict()['2.bias'].data)
'''
output is ::
OrderedDict([('0.weight', tensor([[ 0.0387, 0.0717, 0.0398, 0.3735],
[ 0.1486, -0.3150, -0.3813, 0.3046],
[ 0.3214, -0.0327, 0.3165, -0.3807],
[-0.4867, -0.4954, 0.3559, 0.1104],
[ 0.0462, -0.2147, 0.3825, 0.1375],
[ 0.3976, -0.2493, 0.1906, -0.4750],
[ 0.1445, 0.0632, -0.1869, -0.0446],
[-0.2179, 0.1317, -0.4357, 0.1538]])), ('0.bias', tensor([ 0.4576, -0.0437, -0.0475, 0.0576, -0.1058, 0.4215, -0.1188, 0.1157])), ('2.weight', tensor([[ 0.2158, 0.0116, 0.0566, 0.3138, -0.0825, -0.1470, -0.1105, -0.2869]])), ('2.bias', tensor([-0.2899]))])
[('0.weight', torch.Size([8, 4])), ('0.bias', torch.Size([8])), ('2.weight', torch.Size([1, 8])), ('2.bias', torch.Size([1]))]
tensor([-0.2899])
'''
我们需要知道的是,param是个tuple,是复合的对象,包含值,梯度和额外信息,对于param的值,不论是weight还是bias,都是由类似下面的tuple组成的(参数名称, tensor())
,参数名称唯一,这和层的名称一样也是唯一的,nn.Module.add_module(层名称, 对象)
。也是由于tuple中的tensor的存在,这也是后面为啥能查看模型在哪个设备的原因,也是通过获得Tensor.device方法获取的,
[(参数名称, tensor())]
[('weight', tensor([[ 0.2336, -0.2090, 0.0561, 0.1469, 0.1116, -0.0786, 0.1960, 0.0033]])), ('bias', tensor([-0.3357]))]
关于权重初始化可以参考pytorch权重初始化/参数初始化https://blog.csdn.net/qq_43369406/article/details/131342983
模型初始化是指模型在训练前对参数进行的赋值,需要搭配model.apply(func)
和类似nn.init.normal_
init中的方法来实现。如果你对整个网络apply将会递归其中的children都使用相同方法初始化(注意children指继承自nn.Module的子类),如果对单个层apply将只有单层初始化。下面是系统内置的初始化权重方法:
"""
6.3.1. Built-in Initialization
using built-in func to init.
- `nn.init.normal_(module.weight, mean=0, std=0.01)`
- `nn.init.zeros_(module.bias)`
- `nn.init.constant_(module.weight, 1)`
- `nn.init.zeros_(module.bias)`
- `nn.init.xavier_uniform_(module.weight)`
- `nn.init.kaiming_uniform_(module.weight)` # default one for Linear, and the type is Leaky_ReLU
- `nn.init.uniform_(module.weight, -10, 10)`
"""
def init_normal(module):
if type(module) == nn.Linear:
nn.init.normal_(module.weight, mean=0, std=0.01)
nn.init.zeros_(module.bias)
net.apply(init_normal)
print(net[0].weight.data[0])
print(net[0].bias.data[0])
def init_constant(module):
if type(module) == nn.Linear:
nn.init.constant_(module.weight, 1)
nn.init.zeros_(module.bias)
net.apply(init_constant)
print(net[0].weight.data[0]); print(net[0].bias.data[0])
def init_xavier(module):
if type(module) == nn.Linear:
nn.init.xavier_uniform_(module.weight)
def init_42(module):
if type(module) == nn.Linear:
nn.init.constant_(module.weight, 42)
net[0].apply(init_xavier); net[2].apply(init_42)
print(net[0].weight.data[0]); print(net[2].weight.data)
def _weights_init(m):
"""
intro:
weights init.
finish these:
- torch.nn.Linear
>>> version 1.0.0
if type(m) == nn.Linear:
print("Init", *[(name, param.shape) for name, param in m.named_parameters()][0]) # linear - param - weight
nn.init.trunc_normal_(m.weight, std=.01)
if m.bias is not None:
print("Init", *[(name, param.shape) for name, param in m.named_parameters()][1]) # linear - param - bias
nn.init.zeros_(m.bias)
args:
:param torch.parameters m: nn.Module
"""
classname = m.__class__.__name__
if type(m) == nn.Linear:
print("Init", *[(name, param.shape) for name, param in m.named_parameters()][0]) # linear - param - weight
nn.init.trunc_normal_(m.weight, std=.01)
if m.bias is not None:
print("Init", *[(name, param.shape) for name, param in m.named_parameters()][1]) # linear - param - bias
nn.init.zeros_(m.bias)
elif isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode="fan_out")
if m.bias is not None:
nn.init.zeros_(m.bias)
elif isinstance(m, nn.LayerNorm):
nn.init.zeros_(m.bias)
nn.init.ones_(m.weight)
elif classname.startswith('Conv'):
m.weight.data.normal_(0.0, 0.02)
elif classname.find('BatchNorm') != -1:
m.weight.data.normal_(1.0, 0.02)
m.bias.data.fill_(0)
net = nn.Sequential(nn.LazyLinear(8), nn.ReLU(), nn.LazyLinear(1))
X = torch.rand(size=(2, 4))
net.apply(_weights_init)
所有带Lazy的层如nn.LazyLinear
都是lazy init延后初始化,需要传入一个Tensor后才确定模型。
当你的model网络模型和张量数据tensor在CPU上时,其实是CPU和内存配合工作;而当你的model网络模型和张量数据tensor在GPU上时,其实是对应的GPU和对应的GPU的显存配合工作。但是内存断电后即停止,如果存储到外存上进行永久存储呢,Pytorch将这两个方法封装在了torch.save
和torch.load
中。
x = torch.arange(4)
torch.save(x, 'x-file')
x2 = torch.load('x-file')
需要搭配torch.nn.state_dict()
方法和torch.nn.load_state_dict()
方法。且由于我们只存储模型的参数,并不存储模型的样子,所有我们先要实例化模型后,再将参数载入。
torch.save(net.state_dict(), 'mlp.params')
net_clone = MLP()
net_clone.load_state_dict(torch.load('mlp.params'))
载入时可以指定载入到哪个设备,例如
checkpoint = torch.load(load_path, map_location="cpu")
设备分为GPU和CPU,tensor和model的指定设备,转移设备,查看在哪个设备的方法如下,也可参考设备指定_指定GPU设备常用操作/cuda设备指定/查看模型在哪https://blog.csdn.net/qq_43369406/article/details/129816988
# tensor
X = torch.zeros((2, 3), device=torch.device("cuda:1"))
X = X.to(device=torch.device("cuda:0"))
print(X.device)
# model
net = nn.Sequential(nn.LazyLinear(1))
net = net.to(device=torch.device("cuda:0"))
print(net[0].weight.data.device)
查看有哪些设备
print(torch.device("cpu"))
print(torch.device("cuda:1"))
print(torch.cuda.is_available())
print(torch.cuda.device_count())