模型构造

一、层

1、单一输出神经网络

  • 接受一些输入
  • 生成相应标量输出
  • 具有一组相关参数(这些参数可以更新以优化某些感兴趣的目标函数)

2、多个输出神经网络(利用矢量化算法来描述)

  • 接受一组输入
  • 生成相应的输出
  • 由一组可调整参数描述

3、对于多层感知机而言,整个模型接受原始输入(特征),生成输出(预测),并包含一些参数(所有组成层的参数集合)。同样,每个单独的层接收输入(由前一层提供)生成输出(到下一层的输入),并且具有一组可调参数,这些参数根据从下一层反向传播的信号进行更新。

 

二、块

1、虽然你可能认为神经元、层和模型为我们的业务提供了足够的抽象,但事实证明,我们经常发现谈论比单个层大但比整个模型小的组件更方便。

2、为了实现这些复杂的网络,我们引入了神经网络新概念——块。快可以描述单个层,由多个层组成的组件或整个模型本身。使用块进行抽象的一个好处是可以将一些块组合成更大的组件

3、从编程的角度来看,块由类(class)表示。它的任何子类都必须定义一个将其输入转换为输出的正向传播函数,并且必须存储任何必需的参数。注意,有些块不需要任何参数。最后,为了计算梯度,块必须具有反向传播函数。(反向传播函数由系统自动实现,我们只需要考虑正向传播函数和必需的参数)

 

三、多层感知机的简单实现

1、下面的代码生成如下网络,其中包含一个具有256个单元和ReLU激活函数的全连接的隐藏层,然后是一个具有10个单元且不带激活函数的全连接的输出层

import torch
from torch import nn
from torch.nn import functional as F

# 可以发现层的执行顺序是作为参数传递的
# 实例化nn.Sequential构建模型
net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))

X = torch.rand(2, 20)
# net(X)其实是在运行net.__call__(X)
net(X)

2、nn.Sequential定义了一种特殊的Module,即在PyTorch中表示一个块的类。它维护了一个由Module组成的有序列表。

3、注意,两个全连接层都是Linear类的实例,Linear类本身就是Module的子类。

4、正向传播(forward)函数:它将列表中的每个块连接在一起,将每个块的输出作为下一个块的输入。

 

四、块的自定义实现

1、每个块的基本功能

  • 将输入数据作为其正向传播函数的参数
  • 通过正向传播函数生成输出。输出的形状可能与输入的形状不同
  • 计算其输出关于输入的梯度,可通过其反向传播函数进行访问。通常这是自动发生的
  • 存储和访问正向传播计算所需的参数
  • 根据需要初始化模型参数

2、下面代码构建了一个块,包含一个多层感知机,其具有256个隐藏单元的隐藏层和一个10维输出层。下面实现的MLP类继承了表示块的类,实现大部分依赖于父类,只需要提供构造函数和正向传播函数。

# 建立新的块,只需要实现新的构造函数和正向传播函数就行

class MLP(nn.Module):
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self):
        # 调用`MLP`的父类`Block`的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数`params`(稍后将介绍)
        super().__init__()
        
        #实例化隐藏层
        self.hidden = nn.Linear(20, 256)
        #实例化输出层
        self.out = nn.Linear(256, 10)

    # 定义模型的正向传播,即如何根据输入`X`返回所需的模型输出
    def forward(self, X):
        
        # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
        # 只对隐藏层调用ReLU激活函数
        return self.out(F.relu(self.hidden(X)))
print(X)
net = MLP()

# 每个模型最后有10个输出
net(X)

#输出结果
tensor([[0.4465, 0.9386, 0.7474, 0.6366, 0.5086, 0.7784, 0.4356, 0.3269, 0.3139,
         0.0684, 0.3844, 0.8814, 0.2747, 0.1932, 0.0266, 0.8907, 0.4390, 0.8401,
         0.4647, 0.4691],
        [0.9919, 0.1950, 0.4420, 0.5582, 0.7696, 0.4768, 0.2522, 0.6498, 0.1012,
         0.6853, 0.8432, 0.6293, 0.4477, 0.0510, 0.3639, 0.9606, 0.8294, 0.4492,
         0.6005, 0.8976]])
tensor([[ 0.0730,  0.0123,  0.0363,  0.1018,  0.0403,  0.0562,  0.2169, -0.0669,
         -0.2341,  0.0335],
        [ 0.2106, -0.0089,  0.0546,  0.0550,  0.0157,  0.1161,  0.1976, -0.1997,
         -0.2641,  0.0918]], grad_fn=<AddmmBackward>)

  

五、顺序块

1、Sequential的设计是为了把其他模块串起来。为了构建我们自己的简化MySequential,我们只需要定义两个关键函数

  • 一种将逐个追加到列表中的函数
  • 一种正向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”

2、实现

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for block in args:
            # 这里,`block`是`Module`子类的一个实例。我们把它保存在'Module'类的成员变量
            # `_children` 中。`block`的类型是OrderedDict。
            
            # 把每个块逐个添加到有序字典_modules中,在块的参数初始化过程中,系统会从_modules字典中查找需要初始化参数的子块
            self._modules[block] = block

    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X)
        return X

 当MySequential的正向传播函数被调用时,每个添加的块都按照它们添加的顺序执行。

net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))

net(X)

#输出结果

tensor([[-0.0471, -0.2329,  0.0354, -0.1634,  0.1868, -0.0940,  0.1983, -0.1406,
         -0.2532, -0.0207],
        [ 0.0035, -0.2951, -0.0970, -0.1746,  0.2058, -0.1665,  0.0626, -0.1438,
         -0.3025, -0.0999]], grad_fn=<AddmmBackward>)

  

六、在正向传播函数中执行代码

例如,我们需要一个计算函数𝑓(𝐱,𝐰)=𝑐𝐰𝐱的层,其中𝐱是输入,𝐰是我们的参数,𝑐是某个在优化过程中没有更新的指定常量。因此我们实现了一个FixedHiddenMLP

class FixedHiddenMLP(nn.Module):
    def __init__(self):
        super().__init__()
        # 不计算梯度的随机权重参数。因此其在训练期间保持不变。
        self.rand_weight = torch.rand((20, 20), requires_grad=False)
        self.linear = nn.Linear(20, 20)

    def forward(self, X):
        
        X = self.linear(X)
        # 使用创建的常量参数以及`relu`和`dot`函数。
        '''
        torch.mm(mat1, mat2, out=None) → Tensor
        对矩阵mat1和mat2进行相乘。 
        如果mat1 是一个n×m张量,mat2 是一个 m×p 张量,将会输出一个 n×p 张量out
        
        '''
        X = F.relu(torch.mm(X, self.rand_weight) + 1)
        
        # 复用全连接层。这相当于两个全连接层共享参数。
        # 相当于创建了一个隐藏层
        X = self.linear(X)
        
        # 控制流
        # X.abs(),sum():实现L1范数
        while X.abs().sum() > 1:
            # L1范数大于1,将输出向量除以2
            X /= 2
        print(X)
        return X.sum()
net = FixedHiddenMLP()
net(X)

#输出结果

tensor([[ 2.2335e-02, -1.7969e-02, -5.9800e-03, -1.4124e-02, -1.7429e-03,
          1.0494e-02, -4.9748e-02,  9.7850e-03,  1.6539e-02, -3.6469e-02,
          2.6963e-02,  2.0314e-02, -3.4088e-02, -6.6470e-04,  4.2046e-03,
         -1.7319e-02,  3.6255e-02,  2.0598e-02,  1.7070e-02, -1.3224e-02],
        [ 1.4114e-02, -3.5359e-03, -3.5676e-03, -1.3470e-02,  4.3290e-03,
          1.6416e-03, -3.6209e-02, -5.2937e-05,  1.7648e-02, -2.1156e-02,
          2.1955e-02,  1.5639e-02, -2.6354e-02,  1.8862e-03, -2.2098e-04,
         -1.7230e-02,  2.4409e-02,  1.7849e-02,  1.1194e-02,  2.6565e-03]],
       grad_fn=<DivBackward0>)
tensor(0.0048, grad_fn=<SumBackward0>)

  

七、小结

1、层也是块

2、一个块可以由许多层组成

3、一个块可以由许多块组成

4、块可以包含代码

5、块复制大量的内部处理,包括参数初始化和反向传播

6、层和块的顺序连接有Sequential块处理

posted @ 2021-07-30 20:20  小秦同学在上学  阅读(257)  评论(0编辑  收藏  举报