依旧选择之前定义的模型
import torchfrom torch import nnfrom torch.nn import initnet = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1)) # pytorch已进行默认初始化print(net)X = torch.rand(2, 4)print((net(X).sum()))
访问模型参数
可以通过Module类的parameters()或者named_parameters方法来访问所有参数(以迭代器的形式返回),前者只会返回参数,后者除了返回参数Tensor外还会返回其名字。
print(net.parameters())print(net.named_parameters())for param in net.parameters():print(param.size())for name, param in net.named_parameters():print(name, param.size())结果:<generator object Module.parameters at 0x000001F5F75BF900><generator object Module.named_parameters at 0x000001F5F75BF900>0.weight torch.Size([3, 4])0.bias torch.Size([3])2.weight torch.Size([1, 3])2.bias torch.Size([1])
可以发现这些参数自动加上了层数索引作为前缀。我们也可以直接使用net[i]访问网络任意层。索引0表示隐藏层为最先添加的层。
for name, param in net[0].named_parameters():print(name, param.size(), type(param))结果:weight torch.Size([3, 4]) <class 'torch.nn.parameter.Parameter'>bias torch.Size([3]) <class 'torch.nn.parameter.Parameter'>
因为这里是单层的所以没有了层数索引的前缀。另外返回的
param的类型为torch.nn.parameter.Parameter,其实这是Tensor的子类,和Tensor不同的是如果一个Tensor是Parameter,那么它会自动被添加到模型的参数列表里,来看下面这个例子。
class MyModel(nn.Module):def __init__(self, **kwargs):super(MyModel, self).__init__(**kwargs)self.weight1 = nn.Parameter(torch.rand(20, 20))self.weight2 = torch.rand(20, 20)def forward(self, x):passn = MyModel()for name, param in n.named_parameters():print(name)结果:weight1
我们发现weight1在参数列表中,但weight2不在,因为weight1是parameter类型参数。它也可以执行跟tensor完全一样的操作。
初始化模型参数
我们可以使用这样的方式进行参数初始化:
for name, param in net.named_parameters():if 'weight' in name:init.normal_(param, mean=0, std=0.01)print(name, param.data)
这样之后,每一层的权重参数就都被初始化了。
也可以采用常数初始化权重参数:
for name, param in net.named_parameters():if 'bias' in name:init.constant_(param, val=0)print(name, param.data)
自定义初始化方法
我们可以自己设定参数初始化函数,这里以pytorch的正态分布初始化为例:
def normal_(tensor, mean=0, std=1):with torch.no_grad():return tensor.normal_(mean, std)
可以看到,在参数初始化过程中,我们使用了with torch.no_grad(),这是为了不追踪梯度,因为还没有开始参数的运算。
在下面的例子里,我们令权重有一半概率初始化为0,有另一半概率初始化为0和[-10.-5],[5,10]两个区间里均匀分布的随机数。
def init_weight_(tensor):with torch.no_grad():tensor.uniform_(-10, 10)tensor *= (tensor.abs() >= 5).float()for name, param in net.named_parameters():if 'weight' in name:init_weight_(param)print(name, param.data)结果:0.weight tensor([[ 5.5789, -0.0000, -0.0000, -5.6637],[-0.0000, -7.1063, -9.6837, 9.8249],[ 0.0000, -0.0000, -0.0000, 0.0000]])2.weight tensor([[7.6618, -0.0000, 6.5314]])
可以看到,初始化的参数绝对值都大于5。
共享模型参数
前面提到过,想要实现模型参数的共享可以在forward函数中多次调用同一个层。在使用Sequential类时,想要达到参数共享可以在定义层时传入同一个Module实例,如:
linear = nn.Linear(1, 1, bias=False)net = nn.Sequential(linear, linear)print(net)for param in net[0].parameters():init.constant_(param, val=3)for name, param in net[1].named_parameters():print(name, param)结果:Sequential((0): Linear(in_features=1, out_features=1, bias=False)(1): Linear(in_features=1, out_features=1, bias=False))weight Parameter containing:tensor([[3.]], requires_grad=True)
可以看到,尽管只对第一层进行了参数初始化,但是第二层的参数也相应地做了初始化,因为这两个线性层就是同一个对象。
但要注意,在计算反向传播时,共享的参数梯度是累加的:
x = torch.ones(1, 1)y = net(x).sum()print(y)y.backward()print(net[0].weight.grad) # 单次梯度是3,两次所以就是6输出:tensor(9., grad_fn=<SumBackward0>)tensor([[6.]])
由于两个隐藏层对应函数都是y=3x,因此y对x求导得3,那么计算两次就是将3乘2,梯度得6。
