动手学习深度学习笔记3:Sequential构建模型与参数初始化

一、利用 Module的 Sequential子类构建模型  

        Module 类是一个通用的模型构造类,是所有神经网络模块的基类。可以基于该类构建神经网络的层(layer, 如Linear层)或者直接构建模型。继承该函数一般需要重载__init__函数和forward函数,分别用于创建模型参数和定义前向计算。 除了采用直接继承定义模型以外,pytorch 还提供了更加还实现了继承自Module的,可以方便构建模型的类: 如Sequential、ModuleList 和ModuleList等。

  • Sequential 类:可以通过添加子模块的有序字典或者是多个子模块作为参数来逐一添加Module 实例。模块之间有先后顺序关系,上一子模块的输入为下一个子模块的输出(所以需要保证相邻层的输入输出匹配),无需定义forward函数。
    import torch
    from torch import nn
    
    # 法1:直接使用多个子模块作为参数
    net = nn.Sequential(
            nn.Linear(784, 256),
            nn.ReLU(),
            nn.Linear(256, 10), 
            )
    
    # 法2:采用add_module来添加子模块
    net =  nn.Sequential()
    net.add_module(nn.Linear(784, 256))
    net.add_module(nn.ReLU())
    net.add_module(nn.Linear(256, 10))
    
    # 法3:采用添加有序字典的方式
    from collections import OrderedDict
    net = nn.Sequential(OrderedDict([
              ('linear', nn.Linear(784, 256))
              ('ReLU', nn.ReLU())
              ('linear', nn.Linear(256, 10))
            ]))
    
  • ModuleList:一个存储module的列表,与Sequential不同的是里面的模块之间没有顺序关系(无需保证相邻层的输入输出匹配),需要自行定义子模块之间的forward函数。使用语法类似于python 中的List,与List 不同的是所有加入到ModuleList的模型参数会被自动添加到整个网络。
class MyModule(nn.Module):
    def __init__(self):
        super(MyModule, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(10)])
        #也可以用append来添加子模块,如
        # self.linears.append(nn.Linear(256, 10))

    # 需要自行定义ModuleList 中模型的forward 函数
    def forward(self, x):
        # ModuleList can act as an iterable, or be indexed using ints
        for i, l in enumerate(self.linears):
            x = self.linears[i // 2](x) + l(x)
        return x
  • ModuleDict类:接受子模块的字典作为输入参数,访问操作类似于字典,其中的子模块的存储顺序是随机的。与ModuleList类似,所有ModuleDict中的子模型参数会自动添加到模型中,也同样需要自定义forward函数。​​​​​​​
    net = nn.ModuleDict({
        'linear': nn.Linear(784, 256),
        'act': nn.ReLU(),
    })
    net['output'] = nn.Linear(256, 10) # 添加

二、模型参数初始化与共享

        nn中的内置模型,如Linear、ReLU、Linear是会默认采用一定得策略对模型的参数做初始化,所以当使用这些子模块时可以不用自行定义参数初值,采用pytorch内置的初始化方式。

import torch
from torch import nn
from torch.nn import init

# pytorch会对Sequential中的内置子模块作默认的模型参数初始化
net = nn.Sequential(nn.Linear(4, 3), nn.ReLU(), nn.Linear(3, 1))  

# 访问模型参数
for name, param in net.named_parameters():
    print(name, param.size())

'''输出
0.weight torch.Size([3, 4]) #0表示第0层
0.bias torch.Size([3])
2.weight torch.Size([1, 3])
2.bias torch.Size([1])      #2表示第2层
'''
# 可以使用net[i].named_parameters() 访问第i层的参数
for name, param in net[0].named_parameters():
    print(name, param.size(), type(param))
'''输出
weight torch.Size([3, 4]) 
bias torch.Size([3]) 
'''

torch.nn.parameter.Parameter是Tensor的子类,所以如果一个Tensor被定义为Parameter,那么会被自动加到模型参数中。因为Parameter是Tensor,即用于Tensor的所有属性,比如可以根据Data来访问参数数值,用Grad来访问参数梯度。

weight_0 = list(net[0].parameters())[0] 
print(weight_0.data)
print(weight_0.grad) # 反向传播前梯度为None
Y.backward() # backward()计算梯度的值
print(weight_0.grad)

'''输出
tensor([[ 0.2719, -0.0898, -0.2462,  0.0655],
        [-0.4669, -0.2703,  0.3230,  0.2067],
        [-0.2708,  0.1171, -0.0995,  0.3913]])
None
tensor([[-0.2281, -0.0653, -0.1646, -0.2569],
        [-0.1916, -0.0549, -0.1382, -0.2158],
        [ 0.0000,  0.0000,  0.0000,  0.0000]])
'''

        除了nn.Module模块内置的初始化,还可以使用PyTorch的init模块进行初始化,init模块中提供了多种预设模型初始化方法。

from torch.nn import init
for name, param in net.named_parameters():
    if 'weight' in name:
        init.normal_(param, mean=0, std=0.01) # 正态初始化
        print(name, param.data)
    ''' # 常数初始化
    if 'bias' in name: 
        init.constant_(param, val=0) 
        print(name, param.data)
    '''

如果init模块中提供常用初始化方法不够用的话,还可以自行定义初始化函数


'''#下面的例子里,令权重有一半概率初始化为0,
#有另一半概率初始化为[−10,−5][−10,−5]和[5,10][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([[ 7.0403,  0.0000, -9.4569,  7.0111],
        [-0.0000, -0.0000,  0.0000,  0.0000],
        [ 9.8063, -0.0000,  0.0000, -9.7993]])
2.weight tensor([[-5.8198,  7.7558, -5.0293]])
'''

共享模型参数有两种方式:1)在Module类的forward函数里多次调用同一个层;2)传入Sequential 的模块是同一个Module实例,参数也是共享。

(1)在Module类的forward函数里多次调用同一个层

class FancyMLP(nn.Module):
    def __init__(self, **kwargs):
        super(FancyMLP, self).__init__(**kwargs)

        self.rand_weight = torch.rand((20, 20), requires_grad=False) # 不可训练参数(常数参数)
        self.linear = nn.Linear(20, 20)
    def forward(self, x):
        x = self.linear(x)
        # 使用创建的常数参数,以及nn.functional中的relu函数和mm函数
        x = nn.functional.relu(torch.mm(x, self.rand_weight.data) + 1)
        # 复用全连接层。等价于两个全连接层共享参数
        x = self.linear(x)
        # 控制流,这里我们需要调用item函数来返回标量进行比较
        while x.norm().item() > 1:
            x /= 2
        if x.norm().item() < 0.8:
            x *= 10
        return x.sum()

(2)  传入Sequential 的模块是同一个Module实例,那么这两个实例参数共享

linear = nn.Linear(1, 1, bias=False)
net = nn.Sequential(linear, linear) # 两次使用同一个实例,共享参数
print(net)
for name, param in net.named_parameters():
    init.constant_(param, val=3)
    print(name, param.data)

'''输出
Sequential(
  (0): Linear(in_features=1, out_features=1, bias=False)
  (1): Linear(in_features=1, out_features=1, bias=False)
)
0.weight tensor([[3.]])
'''
'''------注意------'''
# 在内存中,这两个线性层其实一个对象
# 在反向传播计算时,这些共享参数的梯度是累加的

你可能感兴趣的:(读书笔记,pytorch,深度学习)