系统学习Pytorch笔记四:模型创建Module、模型容器Containers、AlexNet网络搭建及常用网络层

模型创建Module、模型容器Containers、AlexNet网络搭建及常用网络层

背景

学习知识先有框架(至少先知道有啥东西)然后再通过实战(各个东西具体咋用)来填充这个框架。 而这个系列的目的就是在脑海中先建一个Pytorch的基本框架出来。关于系统学习Pytorch,逻辑上就是按照机器学习的那五大步骤进行的, 步骤为:数据模块 -> 模型模块 -> 损失函数 -> 优化器 -> 迭代训练

基于上次的学习Pytorch的数据读取机制DataLoader和Dataset的运行机制,然后学习图像的预处理模块transforms的原理,这次主要是学习,进入模型模块的学习,进行模型的创建和模型容器的知识学习。首先学习模型的创建步骤和nn.Module的相关细节, 然后学习搭建模型的容器Containers,这里面包括nn.Sequential, nn.ModuleList, nn.ModuleDict, 它们各自有各自的特点和应用场景。 最后我们分析一个经典的网络AlexNet。

Pytorch模型的创建

机器模型学习的五大模块,分别是数据,模型,损失函数,优化器,迭代训练. 模型创建是模型模块的一个分支,和数据模块一样,我们先看一下模型模块的具体内容:
在这里插入图片描述

模型的创建步骤

下面是我们在人民币二分类任务过程中用到的LeNet模型,我们可以看一下LeNet的组成。
在这里插入图片描述

构建我们模型的两大要素:

  • 构建子模块(比如LeNet里面的卷积层,池化层,全连接层),这个是在自己建立的模型(继承nn.Module)的__init__()方法
  • 拼接子模块(有了子模块,我们把子模块按照一定的顺序,逻辑进行拼接起来得到最终的LeNet模型),forward()方法里面定义子模块的拼接方法。

在模型的概念当中,我们有一个非常重要的概念叫做nn.Module ,我们所有的模型都是继承于这个类。所以我们非常有必要了解nn.Module这个类。

nn.Module类

在介绍nn.Module之前,我们先介绍与其相关的几个模块, 建立一个框架出来,看看Module这个模块在以一个什么样的逻辑存在, 这样的目的依然是把握宏观。
在这里插入图片描述

torch.nn 这是Pytorch的神经网络模块, 这里的Module就是它的子模块之一,另外还有几个与Module并列的子模块, 这些子模块协同工作,各司其职。
代码中导入常用 import torch.nn as nn

nn.Parameter

在Pytorch中,模型的参数是需要被优化器训练的,因此,通常要设置参数为 requires_grad = True 的张量。同时,在一个模型中,往往有许多的参数,要手动管理这些参数并不是一件容易的事情。Pytorch一般将参数用nn.Parameter来表示,并且用nn.Module来管理其结构下的所有参数

## nn.Parameter 具有 requires_grad = True 属性
w = nn.Parameter(torch.randn(2,2))
print(w)   # tensor([[ 0.3544, -1.1643],[ 1.2302,  1.3952]], requires_grad=True)
print(w.requires_grad) 
####################/*输出*/#########################
Parameter containing:
tensor([[-0.2368,  1.2895],
        [-0.0656, -0.0071]], requires_grad=True)
True

## nn.ParameterList 可以将多个nn.Parameter组成一个列表
params_list = nn.ParameterList([nn.Parameter(torch.rand(8,i)) for i in range(1,3)])
print(params_list)
print(params_list[0].requires_grad)
####################/*输出*/#########################
ParameterList(
    (0): Parameter containing: [torch.float32 of size 8x1]
    (1): Parameter containing: [torch.float32 of size 8x2]
)
True

## nn.ParameterDict 可以将多个nn.Parameter组成一个字典
params_dict = nn.ParameterDict({"a":nn.Parameter(torch.rand(2,2)),
                               "b":nn.Parameter(torch.zeros(2))})
print(params_dict)
print(params_dict["a"].requires_grad)
####################/*输出*/#########################
ParameterDict(
    (a): Parameter containing: [torch.FloatTensor of size 2x2]
    (b): Parameter containing: [torch.FloatTensor of size 2]
)
True

可以用Module把这些参数管理起来

# module.parameters()返回一个生成器,包括其结构下的所有parameters

module = nn.Module()
module.w = w
module.params_list = params_list
module.params_dict = params_dict

num_param = 0
for param in module.parameters():
    print(param,"\n")
    num_param = num_param + 1
print("number of Parameters =",num_param)

####################/*输出*/#########################
Parameter containing:
tensor([[-0.8213, -0.1783],
        [-0.0146, -0.9373]], requires_grad=True) 

Parameter containing:
tensor([[0.5714],
        [0.4149],
        [0.8643],
        [0.0692],
        [0.6026],
        [0.8652],
        [0.5425],
        [0.3034]], requires_grad=True) 

Parameter containing:
tensor([[0.2103, 0.7612],
        [0.1356, 0.6239],
        [0.6932, 0.0615],
        [0.9962, 0.6491],
        [0.3414, 0.4696],
        [0.9505, 0.7242],
        [0.7028, 0.6475],
        [0.9386, 0.6142]], requires_grad=True) 

Parameter containing:
tensor([[0.6707, 0.6721],
        [0.4088, 0.9275]], requires_grad=True) 

Parameter containing:
tensor([0., 0.], requires_grad=True) 

number of Parameters = 5


nn.functional

nn.functional(代码中导入常用:import torch.nn.functional as F)有各种功能组件的函数实现

  • 激活函数系列(F.relu, F.sigmoid, F.tanh, F.softmax)
  • 模型层系列(F.linear, F.conv2d, F.max_pool2d, F.dropout2d, F.embedding)
  • 损失函数系列(F.binary_cross_entropy, F.mse_loss, F.cross_entropy)

为了便于对参数进行管理, 一般通过继承nn.Module转换为类的实现形式, 并直接封装在nn模块下

  • 激活函数变成(nn.ReLu, nn.Sigmoid, nn.Tanh, nn.Softmax)
  • 模型层(nn.Linear, nn.Conv2d, nn.MaxPool2d, nn.Embedding)
  • 损失函数(nn.BCELoss, nn.MSELoss, nn.CrossEntorpyLoss)

所以我们表面上用nn建立的这些激活函数、 层、损失函数, 背后都在functional里面具体实现。 但是nn.Module这个模块确实非常强大, 除了可以管理其引用的各种参数,还可以管理其引用的子模块。 我们下面就来揭开nn.Module的神秘面纱。

nn.Module

今天的重点是nn.Module这个模块, 这里面是所有网络层的基类,管理有关网络的属性。其中有几个重要的属性, 用于管理整个模型,他们都是以有序字典的形式存在。
在这里插入图片描述
主要参数有:

_parameters存储管理属于nn.Parameter类的属性,例如权值,偏置这些参数
_modules存储管理nn.Module类, 比如LeNet中,会构建子模块,卷积层,池化层,就会存储在_modules中
_buffers存储管理缓冲属性, 如BN层中的running_mean, std等都会存在这里面

nn.Module构建属性的一个机制,简单回顾一下,先有一个大的Module继承nn.Module这个基类, 比如上面的LeNet,然后这个大的Module里面又可以有很多的子模块,这些子模块同样也是继承于nn.Module, 在这些Module的__init__方法中,会先通过调用父类的初始化方法进行8个属性的一个初始化。 然后在构建每个子模块的时候,其实分为两步,第一步是初始化,然后被__setattr__这个方法通过判断value的类型将其保存到相应的属性字典里面去,然后再进行赋值给相应的成员。 这样一个个的构建子模块,最终把整个大的Module构建完毕。

下面对nn.Module进行总结:

  • 一个module可以包含多个子module(LeNet包含卷积层,池化层,全连接层)
  • 一个module相当于一个运算, 必须实现forward()函数(从计算图的角度去理解)
  • 每个module都有8个字典管理它的属性(最常用的就是_parameters,_modules )
    一般情况下,我们都很少直接使用 nn.Parameter来定义参数构建模型,而是通过拼装一些常用的模型层来构造模型。这些模型层也是继承自nn.Module的对象,本身也包括参数,属于我们要定义的模块的子模块。

nn.Module提供了一些方法可以管理这些子模块。

  • children() 方法: 返回生成器,包括模块下的所有子模块。
  • named_children()方法:返回一个生成器,包括模块下的所有子模块,以及它们的名字。
  • modules()方法:返回一个生成器,包括模块下的所有各个层级的模块,包括模块本身。
  • named_modules()方法:返回一个生成器,包括模块下的所有各个层级的模块以及它们的名字,包括模块本身。

其中chidren()方法和named_children()方法较多使用。modules()方法和named_modules()方法较少使用,其功能可以通过多个named_children()的嵌套使用实现。

关于Pytorch提取神经网络层结构、层参数及自定义初始化, 参考这篇文章

模型容器Containers

先观察整体框架
在这里插入图片描述
Containers这个容器里面包含3个子模块,分别是nn.Sequential, nn.ModuleList, nn.ModuleDict

nn.Sequential

这是nn.module的容器,用于按顺序包装一组网络层

在机器学习中,特征工程部分是一个很重要的模块,但是到了深度学习中,这部分的重要性就弱化了,深度学习中更偏向于让网络自己提取特征,然后进行分类或者回归任务, 所以就像上面的LeNet那样,对于图像的特征,我们完全不需要人为的设计, 只需要从前面加上卷积层让网络自己学习提取,后面加上几个全连接层进行分类等任务。 所以在深度学习时代,也有习惯,以全连接层为界限,将网络模型划分为特征提取模块和分类模块以便更好的管理网络。

总结nn.Sequentialnn.module的容器, 用于按顺序包装一组网络层

  • 顺序性: 各网络层之间严格按照顺序构建,这时候一定要注意前后层数据的关系
  • 自带forward(): 自带的forward里,通过for循环依次执行前向传播运算

nn.ModuleList

nn.ModuleListnn.module的容器, 用于包装一组网络层, 以迭代方式调用网络层, 主要方法:

  • append(): 在ModuleList后面添加网络层
  • extend(): 拼接两个ModuleList
  • insert(): 指定在ModuleList中位置插入网络层

方法的作用其实类似于我们的列表,只不过元素换成网络层而已。ModuleList来循环迭代的实现一个20个全连接层的网络的构建

class ModuleList(nn.Module):
    def __init__(self):
        super(ModuleList, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(20)])

    def forward(self, x):
        for i, linear in enumerate(self.linears):
            x = linear(x)
        return x

ModuleList构建网络层就可以使用列表生成式构建,然后前向传播的时候也是遍历每一层,进行计算即可。完成一个20层的全连接层的网络的实现,借助nn.ModuleList只需要一行代码就可以搞定。这就是nn.ModuleList的使用了,最重要的就是可以迭代模型,索引模型。

nn.ModuleDict

nn.ModuleDictnn.module的容器, 用于包装一组网络层, 以索引方式调用网络层, 主要方法:

  • clear(): 清空ModuleDict
  • items(): 返回可迭代的键值对(key-value pairs)
  • keys(): 返回字典的键(key)
  • values(): 返回字典的值(value)
  • pop(): 返回一对键值对, 并从字典中删除

可以通过ModuleDict实现网络层的选取, 我们看下面的代码:

class ModuleDict(nn.Module):
    def __init__(self):
        super(ModuleDict, self).__init__()
        self.choices = nn.ModuleDict({
            'conv': nn.Conv2d(10, 10, 3),
            'pool': nn.MaxPool2d(3)
        })

        self.activations = nn.ModuleDict({
            'relu': nn.ReLU(),
            'prelu': nn.PReLU()
        })

    def forward(self, x, choice, act):
        x = self.choices[choice](x)
        x = self.activations[act](x)
        return x
    
net = ModuleDict()
fake_img = torch.randn((4, 10, 32, 32))
output = net(fake_img, 'conv', 'relu')    # 在这里可以选择我们的层进行组合
print(output)

前面通过self.choices这个ModuleDict可以选择卷积或者池化, 而下面通过self.activations这个ModuleDict可以选取是用哪个激活函数, 这个东西在选择网络层的时候挺实用,比如要做时间序列预测的时候,我们往往会用到GRU或者LSTM, 我们就可以通过这种方式来对比哪种网络的效果好。 而具体选择哪一层是前向传播那完成,会看到多了两个参数。也是比较简单的。

这里我们就学习了三个容器,nn.Sequential, nn.ModuleList, nn.ModuleDict。 下面总结一下它们的应用场合
在这里插入图片描述

AlexNet构建

这是一个划时代的卷积神经网络,2012年在ImageNet分类任务中获得了冠军,开创了卷积神经网络的新时代。 AlexNet的特点如下:

  • 采用ReLu: 替换饱和激活函数, 减轻梯度消失
  • 采用LRN(Local Response Normalization): 对数据归一化,减轻梯度消失(后面被Batch归一化取代了)
  • Dropout: 提高全连接层的鲁棒性,增加网络的泛化能力
  • Data Augmentation: TenCrop, 色彩修改

下面就看看AlexNet的结构
在这里插入图片描述
看看AlexNet的源代码

class AlexNet(nn.Module):

    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

它这个就是用Sequential进行搭建的,分三部分, 第一部分是一个Sequential,由一系列的卷积池化模块构成,目的是提取图像的特征, 然后是一个全局的池化层把特征进行整合,最后有一个Sequential是全连接层组成的,用于模型的分类。 这样就完成了AlexNet网络的搭建, 模型结构相比现在的模型还是比较简单的。

常用网络层

学习几个重要的子模块,比如卷积层,池化层,激活函数,全连接层等。 首先我们从卷积层开始, 学习一下1/2/3维度卷积,然后学习一下nn.Conv2d这个方法,最后介绍一下什么是转置卷积。 然后我们进行池化层的学习,包括平均池化和最大池化,然后介绍一下全连接层,这个一般称为线性层, 最后我们介绍一些常用的非线性激活函数,为什么会用到非线性激活函数?

引入非线性激活函数是为了增加神经网络模型的非线性。如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机(Perceptron)。如果使用非线性激活函数的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。

卷积运算与卷积层

卷积运算就是卷积核在输入信号(图像)上滑动, 相应位置上进行乘加。 卷积核又称为过滤器, 可识别出特点的模式、特征。

nn.Conv2d

在这里插入图片描述
主要参数:

  • in_channels: 输入通道数
  • out_channels: 输出通道数, 等价于卷积核个数
  • kernel_size: 卷积核尺寸, 这个代表着卷积核的大小
  • stride: 步长, 这个指的卷积核滑动的时候,每一次滑动几个像素。 步长的概念:左边那个的步长是1, 每一次滑动1个像素,而右边的步长是2,会发现每一次滑动2个像素。
  • padding: 填充个数, 通常用来保持输入和输出图像的一个尺寸的匹配, 当没有padding的卷积,输入图像是4 * 4, 经过卷积之后,输出图像就变成了2 * 2的了, 这样分辨率会遍变低,并且我们会发现这种情况卷积的时候边缘部分的像素参与计算的机会比较少。所以加入考虑padding的填充方式,这个也比较简单,就是在原输入周围加入像素,这样就可以保证输出的图像尺寸分辨率和输入的一样,并且边缘部分的像素也受到同等的关注了。
  • dilation: 孔洞卷积大小。孔洞卷积就可以理解成一个带孔的卷积核, 常用于图像分割任务,主要功能就是提高感受野。也就是输出图像的一个参数,能看到前面图像更大的一个区域。
  • groups: 分组卷积设置, 分组卷积常用于模型的轻量化, 可以减少模型参数。我们之前的AlexNet其实就可以看到分组的身影, 两组卷积分别进行提取,最后合并。
  • bias: 偏置

输出维度计算:
在这里插入图片描述
图像尺寸的变化小例子:

卷积前尺寸:torch.Size([1, 3, 512, 512])
卷积后尺寸:torch.Size([1, 1, 510, 510])

卷积前,图像尺寸是512 × 512 , 卷积后, 图像尺寸是510 × 510 。我们这里的卷积核设置, 输入通道3, 卷积核个数1, 卷积核大小3, 无padding,步长是1, 那么我们根据上面的公式, 输出尺寸:( 512 − 3 ) / 1 + 1 = 510

卷积的矩阵运算

正常的卷积在代码实现过程中的一个具体操作: 对于正常的卷积,我们需要实现大量的相乘相加操作,而这种乘加的方式恰好是矩阵乘法所擅长的。 所以在代码实现的时候,通常会借助矩阵乘法快速的实现卷积操作, 那么这是怎么做的呢?

首先将图像尺寸的4 × 4 拉长成16 × 1 , 16代表所有的像素,1代表只有1张图片。 然后3 × 3 的卷积核会变成一个4 × 16 的一个矩阵,这是怎么变的, 首先这个16,是先把9个权值拉成一列,然后下面补7个0变成16, 这个4是根据我们输出的尺寸计算的,根据输入尺寸,卷积核大小,padding, stride信息可以得到输出尺寸是( 4 − 3 ) / 1 + 1 = 2, 所以输出是2 × 2 , 那么拉成一列就是4。 这样我们的输出:
O 4 × 1 = K 4 × 16 × I 16 × 1 O_{4\times1} = K_{4\times16}\times I_{16\times1} O4×1=K4×16×I16×1

这样就得到了最后一列输出4个元素,然后reshape就得到了2 × 2 的一个输出特征图了。 这就是用矩阵乘法输出一个二维卷积的这样一个示例。这就是那个过程:
在这里插入图片描述

转置卷积

转置卷积又称为反卷积(当然转置卷积这个名字比逆卷积要好,原因在下面),用于对图像进行上采样。在图像分割任务中经常被使用。 首先为什么它叫转置卷积呢?

转置卷积是一个上采样,输入的图像尺寸是比较小的,经过转置卷积之后,会输出一个更大的图像。(正常卷积操作如果没有padding会是的输出维度尺寸变小。)

转置卷积的矩阵运算

我们这里的输入图像尺寸是2 × 2 , 卷积核为3 × 3 , padding=0, stride=1, 我们的输出图像尺寸是4 × 4 , 我们看看这个在代码中是怎么通过矩阵乘法进行实现的。 首先,依然是把输入的尺寸进行拉长,成一个4 × 1 的一个向量, 然后我们的卷积核会变成16 × 4 的,注意这里的卷积核尺寸,这个4依然是根据卷积核得来的,记得上面的那个16吗? 我们是把卷积核拉长然后补0, 在这里我们不是补0了,而是采用剔除的方法,因为我们根据上面的图像可以发现,虽然这里的卷积核有9个权值,可是能与图像相乘的最多只有四个(也就是卷积核在中间的时候), 所以这里采用剔除的方法,从9个权值里面剔除5个,得到4个权重, 而这个16,依然是根据输出图像的尺寸计算得来的。 因为我们这里的输出是4 × 4 , 这个可以用上面尺寸运算的逆公式。所以这里的输出:

O 16 × 1 = K 16 × 4 × I 4 × 1 O_{16\times1} = K_{16\times4}\times I_{4\times1} O16×1=K16×4×I4×1

这次注意这个卷积核的尺寸是16 × 4 , 而我们正常卷积运算的卷积核尺寸4 × 16, 所以在形状上这两个卷积操作卷积核恰恰是转置的关系,这也就是转置卷积的由来了。这是因为卷积核在转变成矩阵的时候,与正常卷积的卷积核形状上是互为转置,注意是形状,具体数值肯定是不一样的。 所以正常卷积核转置卷积的关系并不是可逆的,故逆卷积这个名字不好。下面就具体学习Pytorch提供的转置卷积的方法:

nn.ConvTranspose2d

在这里插入图片描述
这个参数和卷积运算的参数差不多在这里插入图片描述

池化运算和池化层

池化运算:对信号进行收集并总结.
最大池化就是这些元素里面去最大的值作为最终的结果,平均池化就是这些元素去平均值作为最终值。

看看Pytorch提供的最大池化和平均池化的函数

nn.MaxPool2d

在这里插入图片描述

  • kernel_size: 池化核尺寸
  • stride: 步长
  • padding: 填充个数
  • dilation: 池化核间隔大小
  • ceil_mode: 尺寸向上取整
  • return_indices: 记录池化像素索引

前四个参数和卷积的其实类似, 最后一个参数常在最大值反池化的时候使用。反池化就是将尺寸较小的图片通过上采样得到尺寸较大的图片。 这时候就需要当时最大值池化记录的索引了。用来记录最大值池化时候元素的位置,然后在最大值反池化的时候把元素放回去。

nn.AvgPool2d

在这里插入图片描述

  • count_include_pad: 填充值用于计算
  • divisor_override: 除法因子, 这个是求平均的时候那个分母,默认是有几个数相加就除以几,当然也可以自己通过这个参数设定
nn.MaxUnpool2d

在这里插入图片描述

这里的参数与池化层是类似的。唯一的不同就是前向传播的时候我们需要传进一个indices, 我们的索引值,要不然不知道把输入的元素放在输出的哪个位置上

线性层

线性层又称为全连接层,其每个神经元与上一层所有神经元相连实现对前一层的线性组

在这里插入图片描述

  • in_features: 输入节点数
  • out_features: 输出节点数
  • bias: 是否需要偏置

激活函数层

在这里插入图片描述
写成矩阵形式:
在这里插入图片描述

如果没有非线性激活函数,我们线性运算的矩阵乘法的结合性,无论多少个线性层的叠加,其实就是矩阵的一个连乘,最后还是一个矩阵。

使用非线性激活函数是为了增加神经网络模型的非线性因素,以便使网络更加强大,增加它的表达能力,使它可以学习复杂的事物,复杂的表单数据,以及表示输入输出之间非线性的复杂的任意函数映射。非线性激活函数常用于隐藏层。

几个常用的非线性激活函数:

常用的激活函数:sigmoid,Softmax ,Tanh,ReLU,Leaky ReLU,PReLU,ELU等

sigmoid函数

将输入投影到(0,1),是一个软的。

函数表达式:

σ ( x ) = 1 1 + e − x \sigma(x) = \frac{1}{1+e^{-x}} σ(x)=1+ex1

函数倒数:
σ ′ ( x ) = σ ( x ) [ 1 − σ ( x ) ] \sigma^{'}(x) = \sigma(x)[1-\sigma(x) ] σ(x)=σ(x)[1σ(x)]

图像:在这里插入图片描述

优点:

1、Sigmoid函数的输出在(0,1)之间,输出范围有限,优化稳定,可以用作输出层。适用于将预测概率作为输出的模型。

2、连续函数,便于求导。

缺点:

  1. sigmoid函数在变量取绝对值非常大时会出现饱和现象,意味着函数会变得很平,并且对输入的微小改变会变得不敏感。在反向传播时,当梯度接近于0,权重基本不会更新,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。

  2. sigmoid函数的输出不是0均值的,破坏了数据的分布,会导致后层的神经元的输入是非0均值的信号,这会对梯度产生影响。

  3. 计算复杂度高,因为sigmoid函数是指数形式(一般认为:gpu上一次指数运算等价于数百次乘法运算的成本!)。

这个激活函数在实际神经网络中并没有使用。任何复杂的函数都可以由一个常量加一堆sigmoid函数模拟出来。

Softmax函数

Softmax 是用于多分类问题的激活函数,在多分类问题中,超过两个类标签则需要类成员关系。对于长度为 K 的任意实向量,Softmax 可以将其压缩为长度为 K,值在(0,1)范围内,并且向量中元素的总和为 1 的实向量。

优点:

1、Softmax 与正常的 max 函数不同:max 函数仅输出最大值,但 Softmax 确保较小的值具有较小的概率,并且不会直接丢弃。Softmax可看作是soft(软化)的max。

2、Softmax 函数的分母结合了原始输出值的所有因子,这意味着 Softmax 函数获得的各种概率彼此相关。

缺点:

1、当使用Softmax函数作为输出节点的激活函数的时候,一般使用交叉熵作为损失函数。由于Softmax函数的数值计算过程中,很容易因为输出节点的输出值比较大而发生数值溢出的现象,在计算交叉熵的时候也可能会出现数值溢出的问题。

Tanh函数

Tanh函数也称为双曲正切函数,取值范围为[-1,1]

在这里插入图片描述

图像:
在这里插入图片描述
优点:
1、Tanh函数是 0 均值的,因此实际应用中 Tanh 会比 sigmoid 更好。

缺点:
1、导数范围为(0,1),仍然存在梯度饱和与exp计算的问题

ReLU函数

整流线性单元(Rectified linear unit,ReLU)是现代神经网络中最常用的激活函数,大多数前馈神经网络默认使用的激活函数。

函数表达式:

f ( x ) = max ⁡ ( 0 , x ) f(x) = \max{(0,x)} f(x)=max(0,x)

图像
在这里插入图片描述
优点:
1、使用ReLU的SGD算法的收敛速度比 sigmoid 和 tanh 快。
2、在x>0区域上,不会出现梯度饱和、梯度消失的问题。
3、计算复杂度低,不需要进行指数运算,只要一个阈值就可以得到激活值。

缺点:
1、ReLU的输出不是0均值的。
2、 Dead ReLU Problem(神经元坏死现象):ReLU在负数区域被kill的现象叫做dead relu。ReLU在训练的时很“脆弱”。在x<0时,梯度为0。这个神经元及之后的神经元梯度永远为0,不再对任何数据有所响应,导致相应参数永远不会被更新。

产生这种现象的两个原因:参数初始化问题;learning rate太高导致在训练过程中参数更新太大。

解决方法:采用Xavier初始化方法,以及避免将learning rate设置太大或使用adagrad等自动调节learning rate的算法。 (不太理解!!!

Leaky ReLU函数

渗漏整流线性单元(Leaky ReLU),为了解决dead ReLU现象。用一个类似0.01的小值来初始化神经元,从而使得ReLU在负数区域更偏向于激活而不是死掉。这里的斜率都是确定的
在这里插入图片描述

PReLU

参数整流线性单元(Parametric Rectified linear unit,PReLU),用来解决ReLU带来的神经元坏死的问题。

公式: f ( x ) = max ⁡ ( α x , x ) f(x)=\max{(\alpha x,x)} f(x)=max(αx,x)
。 其中 α \alpha α不是固定的,是通过反向传播学习出来的。

ELU

具有relu的优势,没有Dead ReLU问题,输出均值接近0,实际上PReLU和Leaky ReLU都有这一优点。有负数饱和区域,从而对噪声有一些鲁棒性。可以看做是介于ReLU和Leaky ReLU之间的一个函数。当然,这个函数也需要计算exp,从而计算量上更大一些。

在这里插入图片描述

总结

今天的内容主要是分为3大块, 第一块就是Pytorch模型的构建步骤有两个子模块的构建和拼接, 然后就是学习了非常重要的一个类叫做nn.Module,这个是非常重要的,后面的模型搭建中我们都得继承这个类。这里面有8个重要的参数字典,其中_parameters_modules更是重中之重,所以以LeNet为例,学习了LeNet的构建过程和细节部分。

第二块是我们的模型容器Containers部分,这里面先学习了nn.Sequential, 这个是顺序搭建每个子模块, 常用于block构建,依然是通过代码调试看了它的初始化和自动前向传播机制。 然后是nn.ModuleList, 这个类似于列表,常用于搭建结构相同的网络子模块, 特点就是可迭代。 最后是nn.ModuleDict, 这个的特点是索引性,类似于我们的python字典,常用于可选择的网络层

第三块就是根据上面的所学分析了一个非常经典的卷积神经网络AlexNet的构建过程, 当然也可以分析其他的经典网络构建的源代码了。

最后,学习了各个子模块的使用。 从比较重要的卷积层开始, 卷积运算其实就是通过不同的卷积核去提取不同的特征。 然后学习了Pytorch的二维卷积运算及转置卷积运算,并进行了对比和分析了代码上如何实现卷积操作。

随后学习了池化运算和池化层的学习,关于池化,一般和卷积一块使用,目的是收集和合并卷积提取的特征,去除一些冗余, 分为最大池化平均池化。 然后学习了全连接层,这个比较简单,不用多说,最后是非线性激活函数,比较常用的sigmoid、 tanh、 relu等。

关于Pytorch的模型模块,到这里就基本结束, 我们的逻辑就是按照机器学习的那五大步骤进行的查看, 数据模块 -> 模型模块 -> 损失函数 -> 优化器 -> 训练等。

参考:
[1]: https://zhongqiang.blog.csdn.net/article/details/105537218
[2]: https://pytorch.org/docs/stable/torch.html
[3]: https://mermaidjs.github.io/
[4]: http://adrai.github.io/flowchart.js/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值