跟李沐学AI随记-8-神经网络基础-2

104 阅读3分钟
  • 访问参数,用于调试、诊断和可视化;
  • 参数初始化;
  • 在不同模型组件间共享参数。

这里需要注意:对全连接层的参数只有两个,权重和偏置,可以读取网络中对应的参数。

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中总共有哪两个大块,每个大块中有哪些子块,每个子块有哪些小层。 image.png

参数初始化:自己编写初始化函数,声明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()