动手学深度学习18- 模型构造基础Module类的操作手册

模型的构造

回顾之前的3.10章节中的多层感知机的简洁实现中,含但隐藏层的多层感知机的实现方法中,我们首先构造了Sequential实例,然后依次天机了两个全连接层。其中第一层的输出大小为256,即隐藏层的单元个数为256;第二层的输出大小为10,即输出层的单元个数为10。我们再上一章也使用了Sequential类构件模型。这里我们介绍另外一种基于Module类的模型构件方法:模型构件更加灵活。

通过Module类来构造模型

Module类是nn模块里提供的一个模型构造类。是所有神经网络模块 的基类,我们可以继承他来定义我们想要的模型。下面继承Module类构造本节开头提到的剁成感知机。这里定义的MLP类 重载了Module类的__init__函数和forward函数。它们分别用于创建模型参数和定义前向计算(正向传播)。

import torch 
from torch import nn
class MLP(nn.Module):
    # 声明带有模型参数的层,这里声明了两个全连接层
    def __init__(self,**kwargs):
        # 调用MLP父类Module的构造函数来进行必要的初始化。这样的构造实例时 还可以指定其他的函数 
        # 参数,可以 使用super重载初始化参数
        super(MLP,self).__init__(**kwargs)
        self.hidden = nn.Linear(784,256) # 隐藏层
        self.act = nn.ReLU()
        self.output = nn.Linear(256,10)
        
    def forward(self,x):
        a = self.act(self.hidden(x))
        return self.output(a)
        

以上的MLP类中无须定义反向传播函数,系统将通过自动求梯度而自动生成反向传播所需的backward函数。
我, 可以通过实例化MLP类得到模型变量net。下面的代码初始化net并传入数据 X做一次前向计算。其中,net(X)会调用的MLP继承在Module类的__call__函数,这个函数将调用MLP类定义的forward函数来完成前向计算。

X= torch.rand(2,784)  
#生成一个包含了从区间[0, 1)的均匀分布中抽取的一组随机数
#  torhc.randn 标准正态分布,其中2,784 是对应行和列也就是**size
# print(X)
print(X.shape)
net = MLP()
print(net)
net(X)
tensor([[0.2540, 0.1617, 0.8079,  ..., 0.1507, 0.4027, 0.2912],
        [0.2456, 0.0723, 0.1746,  ..., 0.3392, 0.4100, 0.4439]])
torch.Size([2, 784])
MLP(
  (hidden): Linear(in_features=784, out_features=256, bias=True)
  (act): ReLU()
  (output): Linear(in_features=256, out_features=10, bias=True)
)





tensor([[-0.2296,  0.0047, -0.0727,  0.1340, -0.2250,  0.0936, -0.0266, -0.0105,
          0.0701,  0.1474],
        [-0.3118, -0.0495, -0.1344,  0.1733, -0.2783,  0.0599, -0.2457,  0.0853,
          0.0538,  0.0893]], grad_fn=<AddmmBackward>)

Module的子类既可以是一个层(如pytorch提供的Linear类),也可以是个模型(这里的定义的MLP)或者是模型的一部分。

Module的子类

Module类是一个通用的部件。事实上,还可以通过继承Pytorch的Module类构建其他的类,如Sequential,ModuleList,ModuleDict等类。首先我们先继承Module简单实现一个Sequential的类。

Sequential类

当模型的前向计算为简单的串联各个层的计算时,Sequential类可以通过更加简单的方式定义模型。这正是Sequential列的目的:他可以接收一个子模块的有序字典(OrderedDict)或者一系列的子模块作为参数来逐一添加Module的实例,而模型的前向计算就是将这些实例 按照添加的顺序逐一计算。
下面我们在继承Module的基础上实现一个跟Sequential类功能相同的MySequential类。

class MySequential(nn.Module):
    from collections import OrderedDict
    def __init__(self,*args):
        super(MySequential,self).__init__()
        if len(args)==1 and isinstance(args[0],OrderedDict):
            for key,module in args[0].items():
                self.add_module(key,module)
        else:  # 传入的是一些Module
            for idx,module in enumerate(args):
                self.add_module(str(idx),module)
    def forward(self,input):
        for module in self._modules.values():
            input = module(input)
        return input
        

我们用MySequential类来实现前面所描述的MLP类,并使用随机初始化的模型做一次前向计算。

net = MySequential(nn.Linear(784,256),
                  nn.ReLU(),
                   nn.Linear(256,10),
                  )
print(net)
net(X)
MySequential(
  (0): Linear(in_features=784, out_features=256, bias=True)
  (1): ReLU()
  (2): Linear(in_features=256, out_features=10, bias=True)
)





tensor([[-0.0520, -0.2588, -0.0551, -0.2209, -0.1325,  0.1073,  0.1471, -0.0518,
          0.0895, -0.1300],
        [ 0.0592, -0.1857, -0.1532, -0.0446,  0.0069,  0.0621,  0.1925, -0.1252,
          0.0519, -0.1970]], grad_fn=<AddmmBackward>)
ModuleList 类

ModuleList接收衣蛾子模块的列表作为输入,然后可以类似于List那样进行append和extend操作.

net = nn.ModuleList([nn.Linear(784,256),nn.ReLU()])
net.append(nn.Linear(256,10))  # 类似于列表的操作
print(net[-1])  # 可以类似列表通过索引访问
print(net)
Linear(in_features=256, out_features=10, bias=True)
ModuleList(
  (0): Linear(in_features=784, out_features=256, bias=True)
  (1): ReLU()
  (2): Linear(in_features=256, out_features=10, bias=True)
)
ModuleDict类

ModuleDict接收一个子模块的字典作为输入,然后可以类似字典的操作进行添加访问

net = nn.ModuleDict({'linear':nn.Linear(784,256),
                    'act':nn.ReLU(),
                    })
net['output'] = nn.Linear(256,10)
print(net['linear'])
print(net.output)
print(net)
Linear(in_features=784, out_features=256, bias=True)
Linear(in_features=256, out_features=10, bias=True)
ModuleDict(
  (act): ReLU()
  (linear): Linear(in_features=784, out_features=256, bias=True)
  (output): Linear(in_features=256, out_features=10, bias=True)
)
构造复杂的模型

虽然上面介绍的集中方法可以使模型的构造更加简单,且不需要定义forward函数,但直接继承Module类可以极大的狂战模型的构造的灵活性。下面我们构造一个稍微复杂一点的网络FancyMLP。在这个网络中,我们通过get_constant函数创建训练中不被迭代的参数即常数参数。在前向计算中,除了使用创建的常数参数外,我们还会用了Tensor的函数和Python的控制流,并多次调用相同的层。

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()

在这个FancyMLP模型中,我们使用常数权重rand_weight(不可训练的模型参数)、做了矩阵乘法操作torch.mm,并且复用了相同的Linear层,下面我们来测试该模型的前向计算。

X= torch.rand(2,20)
net = FancyMLP()
print(net)
net(X)
FancyMLP(
  (linear): Linear(in_features=20, out_features=20, bias=True)
)





tensor(-3.7364, grad_fn=<SumBackward0>)

因为FancyMLP和Sequential都是Module的子类,我们 可以嵌套调用他们。

class NestMLP(nn.Module):
    def __init__(self,**kwargs):
        super(NestMLP,self).__init__(**kwargs)
        self.net = nn.Sequential(nn.Linear(40,30),nn.ReLU())
        
    def forward(self,x):
        return self.net(x)
    
    
net = nn.Sequential(NestMLP(),nn.Linear(30,20),FancyMLP())
X = torch.rand(2,40)
print(net)
net(X)
# 评论 将40个特征变成30个,将30个变成20,in 和out都是20个特征,三个连续的模型
# 需要保持in于out的一致性。
Sequential(
  (0): NestMLP(
    (net): Sequential(
      (0): Linear(in_features=40, out_features=30, bias=True)
      (1): ReLU()
    )
  )
  (1): Linear(in_features=30, out_features=20, bias=True)
  (2): FancyMLP(
    (linear): Linear(in_features=20, out_features=20, bias=True)
  )
)





tensor(-2.2968, grad_fn=<SumBackward0>)

小结

  • 可以通过继承Module类构造模型
  • Sequential,ModuleList,ModuleDict都是继承自Module类
  • Sequential等类可以使构建模型更加简单吗,但是直接继承Module可以极大的拓展构造模型的灵活性。

posted on 2019-12-28 20:47  多一点  阅读(711)  评论(1编辑  收藏  举报

导航