zoukankan      html  css  js  c++  java
  • 动手学深度学习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可以极大的拓展构造模型的灵活性。
  • 相关阅读:
    SpringBoot之集成slf4j日志框架
    Maven项目优势
    Idea操作技巧
    Nginx服务器之负载均衡策略(6种)
    Git操作规范
    Mybatis之Tk
    MyEclipse取消验证Js的两种方法
    文件异步上传,多文件上传插件uploadify
    EasyMock的使用
    jquery 中post 、get的同步问题,从外部获取返回数据
  • 原文地址:https://www.cnblogs.com/onemorepoint/p/12113001.html
Copyright © 2011-2022 走看看