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

本文介绍了如何使用PyTorch的Sequential、ModuleList和ModuleDict构建神经网络模型,包括模块的添加顺序、参数初始化和共享方法。重点讲解了如何通过这些类实现模型构造,以及如何利用内置初始化和自定义策略来管理模型参数。
摘要由CSDN通过智能技术生成

一、利用 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]) <class 'torch.nn.parameter.Parameter'>
bias torch.Size([3]) <class 'torch.nn.parameter.Parameter'>
'''

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.]])
'''
'''------注意------'''
# 在内存中,这两个线性层其实一个对象
# 在反向传播计算时,这些共享参数的梯度是累加的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值