依旧选择之前定义的模型

  1. import torch
  2. from torch import nn
  3. from torch.nn import init
  4. net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1)) # pytorch已进行默认初始化
  5. print(net)
  6. X = torch.rand(2, 4)
  7. print((net(X).sum()))

访问模型参数

可以通过Module类的parameters()或者named_parameters方法来访问所有参数(以迭代器的形式返回),前者只会返回参数,后者除了返回参数Tensor外还会返回其名字。

  1. print(net.parameters())
  2. print(net.named_parameters())
  3. for param in net.parameters():
  4. print(param.size())
  5. for name, param in net.named_parameters():
  6. print(name, param.size())
  7. 结果:
  8. <generator object Module.parameters at 0x000001F5F75BF900>
  9. <generator object Module.named_parameters at 0x000001F5F75BF900>
  10. 0.weight torch.Size([3, 4])
  11. 0.bias torch.Size([3])
  12. 2.weight torch.Size([1, 3])
  13. 2.bias torch.Size([1])

可以发现这些参数自动加上了层数索引作为前缀。我们也可以直接使用net[i]访问网络任意层。索引0表示隐藏层为最先添加的层。

  1. for name, param in net[0].named_parameters():
  2. print(name, param.size(), type(param))
  3. 结果:
  4. weight torch.Size([3, 4]) <class 'torch.nn.parameter.Parameter'>
  5. bias torch.Size([3]) <class 'torch.nn.parameter.Parameter'>

因为这里是单层的所以没有了层数索引的前缀。另外返回的param的类型为torch.nn.parameter.Parameter,其实这是Tensor的子类,和Tensor不同的是如果一个TensorParameter,那么它会自动被添加到模型的参数列表里,来看下面这个例子。

  1. class MyModel(nn.Module):
  2. def __init__(self, **kwargs):
  3. super(MyModel, self).__init__(**kwargs)
  4. self.weight1 = nn.Parameter(torch.rand(20, 20))
  5. self.weight2 = torch.rand(20, 20)
  6. def forward(self, x):
  7. pass
  8. n = MyModel()
  9. for name, param in n.named_parameters():
  10. print(name)
  11. 结果:
  12. weight1

我们发现weight1在参数列表中,但weight2不在,因为weight1是parameter类型参数。它也可以执行跟tensor完全一样的操作。

初始化模型参数

我们可以使用这样的方式进行参数初始化:

  1. for name, param in net.named_parameters():
  2. if 'weight' in name:
  3. init.normal_(param, mean=0, std=0.01)
  4. print(name, param.data)

这样之后,每一层的权重参数就都被初始化了。

也可以采用常数初始化权重参数:

  1. for name, param in net.named_parameters():
  2. if 'bias' in name:
  3. init.constant_(param, val=0)
  4. print(name, param.data)

自定义初始化方法

我们可以自己设定参数初始化函数,这里以pytorch的正态分布初始化为例:

  1. def normal_(tensor, mean=0, std=1):
  2. with torch.no_grad():
  3. return tensor.normal_(mean, std)

可以看到,在参数初始化过程中,我们使用了with torch.no_grad(),这是为了不追踪梯度,因为还没有开始参数的运算。

在下面的例子里,我们令权重有一半概率初始化为0,有另一半概率初始化为0和[-10.-5],[5,10]两个区间里均匀分布的随机数。

  1. def init_weight_(tensor):
  2. with torch.no_grad():
  3. tensor.uniform_(-10, 10)
  4. tensor *= (tensor.abs() >= 5).float()
  5. for name, param in net.named_parameters():
  6. if 'weight' in name:
  7. init_weight_(param)
  8. print(name, param.data)
  9. 结果:
  10. 0.weight tensor([[ 5.5789, -0.0000, -0.0000, -5.6637],
  11. [-0.0000, -7.1063, -9.6837, 9.8249],
  12. [ 0.0000, -0.0000, -0.0000, 0.0000]])
  13. 2.weight tensor([[7.6618, -0.0000, 6.5314]])

可以看到,初始化的参数绝对值都大于5。

共享模型参数

前面提到过,想要实现模型参数的共享可以在forward函数中多次调用同一个层。在使用Sequential类时,想要达到参数共享可以在定义层时传入同一个Module实例,如:

  1. linear = nn.Linear(1, 1, bias=False)
  2. net = nn.Sequential(linear, linear)
  3. print(net)
  4. for param in net[0].parameters():
  5. init.constant_(param, val=3)
  6. for name, param in net[1].named_parameters():
  7. print(name, param)
  8. 结果:
  9. Sequential(
  10. (0): Linear(in_features=1, out_features=1, bias=False)
  11. (1): Linear(in_features=1, out_features=1, bias=False)
  12. )
  13. weight Parameter containing:
  14. tensor([[3.]], requires_grad=True)

可以看到,尽管只对第一层进行了参数初始化,但是第二层的参数也相应地做了初始化,因为这两个线性层就是同一个对象。
但要注意,在计算反向传播时,共享的参数梯度是累加的

  1. x = torch.ones(1, 1)
  2. y = net(x).sum()
  3. print(y)
  4. y.backward()
  5. print(net[0].weight.grad) # 单次梯度是3,两次所以就是6
  6. 输出:
  7. tensor(9., grad_fn=<SumBackward0>)
  8. tensor([[6.]])

由于两个隐藏层对应函数都是y=3x,因此y对x求导得3,那么计算两次就是将3乘2,梯度得6。