返回顶部

请叫我杨先生

导航

Pytorch 5.1 深度学习计算:层和块

此前,我们学习了多层感知机(Multi Layer Perceptron ,MLP) 。神经网络的核心组件是\((layer)\),它是一种数据处理模块,我们可以将其看作数据过滤器。输入一些数据经过处理后,输出的数据变得更加有用。

\((block)\)可以描述单个层、由多个层组成的组件或整个模型本身。比如下例图中的所有都是块。

理解

从编程的角度上来说,每一个块都是一个类\((class)\),或者说可以使用一个类来表示。 它的任何子类都必须定义一个将其输入转换为输出的前向传播函数, 并且必须存储任何必需的参数。

回顾 MLP 框架的实现:

import torch 
from torch.nn import functional as F 
from torch import nn 
# Reviewing MLP  
net = nn.Sequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))  
X = torch.rand(2,20) 
net(X) 
[Out:]
tensor([[-0.0345,  0.0057,  0.0227, -0.1984, -0.2237, -0.0464, -0.0189, -0.2631,
         -0.2752, -0.1415],
        [-0.1851,  0.0243, -0.0286, -0.1372, -0.1288, -0.1205,  0.1659, -0.3036,
         -0.2170, -0.0545]], grad_fn=<AddmmBackward>)

我们通过实例化 \(nn.Sequential\) 来构建我们的模型, 层的执行顺序是作为参数传递的。 我们通过 \(net(x)\) 调用我们的模型来获得模型的输出 , 这个其实是 \(net.\_\_call\_\_(X)\) 的简写。这个是python 类自带的一些函数。

自定义块实现

class MLP(nn.Module):
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self): 
        super().__init__() # 调用父类nn.Module进行初始化(相当于Cpp中的构造函数) 
        self.hidden = nn.Linear(20,256)  # Hidden Layer 
        self.out = nn.Linear(256,10)   # Output Layer  
    # 定义模型的前向传播,即如何根据输入X返回所需的模型输出
    def forward(self, X):
        # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
        return self.out(F.relu(self.hidden(X)))
net = MLP() 
net(X)
[Out:] 
tensor([[ 0.0214, -0.1030,  0.0920,  0.0377,  0.0155,  0.1304, -0.0631, -0.2136,
         -0.0108, -0.0536],
        [-0.0288,  0.0612,  0.0023,  0.0298, -0.0258,  0.0822, -0.0131, -0.2086,
          0.0226, -0.0265]], grad_fn=<AddmmBackward0>)

这里就要解释下为何要如此定义这个类了, 从块的功能入手: (或者直接看官网实例:CLICK HERE)
(1)将输入数据作为其前向传播函数的参数。
(2)过前向传播函数来生成输出。
(3)计算其输出关于输入的梯度,可通过其反向传播函数进行访问。
(4)存储和访问前向传播计算所需的参数。
(5)根据需要初始化模型参数。

代码中的super 函数是python类里面的继承的函数 , 可以理解\(super().\_\_init\_\_()\)\(nn.Module().\_\_init\_\_()\) 。可以将其看成Cpp中的构造函数,作用是直接初始化,不必要写重复的代码,具体可以查看我的python基础的另外一篇文章。 CLICK HERE

而我们导入的 \(torch.nn.functional\) 这个库是Pytorch 的一个函数的一个库,其中有Convolution functions : conv1d 、 conv2d 、 conv3d ;Pooling functions: avg_pool1d、avg_pool2d、avg_pool3d 等等等等,可以自行查阅官方文档:CLICK HERE

顺序块

为了更加直观查看 \(torch.nn.Sequential()\) 是如何工作的,我们会尝试写一个自己的 \(Sequential\) 。 我们知道\(Sequential\) 的功能是:
(1) 一种将块逐个追加到列表中的函数。
(2)一种前向传播函数,用于将输入按追加块的顺序传递给块组成的“链条”。

class MySequential(nn.Module): 
    def __init__(self,*args): 
        super().__init__() 
        for idx,module in enumerate(args): # 返回索引值和使用什么操作 
            self._modules[str(idx)] = module # 用字典存起来 
        
    def forward(self,X):
        for block in self._modules.values(): 
            X = block(X) 
        return X 
    
net = MySequential(nn.Linear(20,256),nn.ReLU(),nn.Linear(256,10))  
net(X)
[Out:]
tensor([[ 0.1353,  0.1192, -0.0364, -0.0749, -0.2969, -0.2655, -0.0070,  0.0905,
          0.0866,  0.2755],
        [ 0.1561,  0.2128, -0.0533, -0.1302, -0.3092, -0.2072,  0.0382, -0.0209,
         -0.0411,  0.2099]], grad_fn=<AddmmBackward0>)

\(self.\_modules\) 我们称之为有序词典 , 是我们自己创建的。 通过 \(enumerate()\) 这个函数得到索引值和我们传入的 "操作" , 比如说 \(nn.Linear(in\_features,out\_features)\) 。 而索引值 \(idx\) 就是我们的顺序,我们通过字典的形式存入 \(self.\_modules\) 中,通过遍历这个字典中的 \(values\) 我们可以依次拿到 "操作" 。

当然你也可以嵌套使用自己定义的 "块"\(torch.nn.Sequential()\) ,因为它们都是 \(nn.Module()\) 的子类。 更多用法可以看沐神的d2l: CLICK HERE

posted on 2022-01-20 22:54  YangShusen'  阅读(259)  评论(0编辑  收藏  举报