- 访问参数,用于调试、诊断和可视化;
- 参数初始化;
- 在不同模型组件间共享参数。
这里需要注意:对全连接层的参数只有两个,权重和偏置,可以读取网络中对应的参数。
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(), nn.Linear(8, 1))
X = torch.rand(size=(2, 4))
net(X)
# 当通过Sequential类定义模型时, 我们可以通过索引来访问模型的任意层。 这就像模型是一个列表一样,每层的参数都在其属性中。
# 对全连接层,参数只有两个:权重和偏差
print(net[2].state_dict())
# 是parameter类型的,表示可优化
# print(type(net[2].bias))
# print(net[2].bias)
# print(net[2].bias.data)
# 一次性访问所有参数
# 用*解包,得到具体参数
print(*[(name, param.shape) for name, param in net[0].named_parameters()])
print(*[(name, param.shape) for name, param in net.named_parameters()])
# 同样的,可以根据名字来获取我们所需的参数--得到最后一层的偏移
print(net.state_dict()['2.bias'].data)
从嵌套块中收集参数
def block1():
return nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
nn.Linear(8, 4), nn.ReLU())
def block2():
net = nn.Sequential()
# 其实功能和sequential一样,就是方便假如字符串进行标注
for i in range(4):
# 在这里嵌套
net.add_module(f'block {i}', block1())
return net
rgnet = nn.Sequential(block2(), nn.Linear(4, 1))
rgnet(X)
# 打印出网络结构
print(rgnet)
网络中的大致架构:就是sequential中总共有哪两个大块,每个大块中有哪些子块,每个子块有哪些小层。
参数初始化:自己编写初始化函数,声明net实例后实现apply即可。可以对整体实现初始化,也可以对部分层实现不同的初始化
def init_normal(m):
# 如果是全连接层,设置其均值与方差...
if type(m) == nn.Linear:
# 下划线_在后面,表示是一个激活函数,而不是返回什么东西,可以理解为原地赋值
nn.init.normal_(m.weight, mean=0, std=0.01)
nn.init.zeros_(m.bias)
# 遍历每一个网络去使用该function
net.apply(init_normal)
print(net[0].weight.data[0], net[0].bias.data[0])
def init_xavier(m):
if type(m) == nn.Linear:
nn.init.xavier_uniform_(m.weight)
def init_42(m):
if type(m) == nn.Linear:
nn.init.constant_(m.weight, 42)
# 也可以分别对部分层实现不同的初始化操作
net[0].apply(init_xavier)
net[2].apply(init_42)
print(net[0].weight.data[0])
print(net[2].weight.data)
# 自定义初始化
def my_init(m):
if type(m) == nn.Linear:
print("Init", *[(name, param.shape)
for name, param in m.named_parameters()][0])
nn.init.uniform_(m.weight, -10, 10)
m.weight.data *= m.weight.data.abs() >= 5
net.apply(my_init)
print(net[0].weight[:2])
参数绑定---在某些层共享权重
- 这里需要进一步理解,我的理解是就是把同一个module对象传进去了,然后改参数时一起改
- 像下面的例子中,第二、第三个隐藏层的架构是完全一样的,声明的是同一个对象,改变权值是针对对象的,对象一变,sequential序列中的对应块也跟着改变。
# 我们需要给共享层一个名称,以便可以引用它的参数
shared = nn.Linear(8, 8)
# 第二,第三个隐藏层是一样的,改变权重同时改
# 就是把同一个module对象传进去了
net = nn.Sequential(nn.Linear(4, 8), nn.ReLU(),
shared, nn.ReLU(),
shared, nn.ReLU(),
nn.Linear(8, 1))
net(X)
# 检查参数是否相同
print(net[2].weight.data[0] == net[4].weight.data[0])
net[2].weight.data[0, 0] = 100
# 确保它们实际上是同一个对象,而不只是有相同的值
print(net[2].weight.data[0] == net[4].weight.data[0])
自定义层
- 特别需要注意,起名字时要赋属性,因为只有parameter才能被用于优化
class MyLinear(nn.Module):
def __init__(self, in_units, units):
super().__init__()
# 输入size和输出size
# 注意,起名字的时候要给这些东西赋上parameter属性。
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)
linear = MyLinear(5, 3)
print(linear.weight)
读写文件
- 针对tensor,save/load即可
- 针对网络架构及其权重
# 加载和保存模型参数
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(20, 256)
self.output = nn.Linear(256, 10)
def forward(self, x):
return self.output(F.relu(self.hidden(x)))
net = MLP()
X = torch.randn(size=(2, 20))
Y = net(X)
# 存参数
torch.save(net.state_dict(), 'mlp.params')
# 需要注意声明一个对象与原架构一致
clone = MLP()
# 这里要load回来
clone.load_state_dict(torch.load('mlp.params'))
clone.eval()