• TensorFlow学习笔记之 PReLU激活函数原理和代码


    前言

    在开始之前,简单说几句,为什么会想到 PReLU 激活函数?因为最近在复现一个论文时遇到了一个【超分辨率】的关键技术——关键亚像素卷积(Sub-Pixel),详情请看我的另一个 blogblog ——【超分辨率】你真的懂什么是亚像素卷积(Sub-Pixel)嘛???闲言少叙,进入正题。

    激活函数

    ReLU 激活函数(Rectified Linear Unit),顾名思义:线性整流单元。

    为什么要先说 ReLU 函数呢?因为 ReLU 激活函数是目前比较火的一个激活函数,函数表达式:f(x)=max(0,x)f(x)=max (0, x)

    函数图像如下:
    在这里插入图片描述
    相比于传统激活函数,比如: sigmod 函数和 tanh 函数,它有以下几个优点:

    1. 仿生物学原理:相关大脑方面的研究表明生物神经元的信息编码通常是比较分散及稀疏的。通常情况下,大脑中在同一时间大概只有1%-4%的神经元处于活跃状态。使用线性修正以及正则化(regularization)可以对机器神经网络中神经元的活跃度(即输出为正值)进行调试;相比之下,逻辑函数在输入为0时达到 ,即已经是半饱和的稳定状态,不够符合实际生物学对模拟神经网络的期望。

      不过需要指出的是,一般情况下,在一个使用修正线性单元(即线性整流)的神经网络中大概有50%的神经元处于激活态。

    2. 更加有效率的梯度下降以及反向传播:在输入为正数的时候,避免了梯度爆炸和梯度消失问题。

    3. 简化计算过程,计算速度更快:没有了其他复杂激活函数中诸如指数函数的影响;同时活跃度的分散性使得神经网络整体计算成本下降。

    当然,缺点也是有的:

    1. 当输入是负数的时候,ReLU 是完全不被激活的,这就表明一旦输入到了负数,ReLU 就会死掉。这样在前向传播过程中,还不算什么问题,有的区域是敏感的,有的是不敏感的。但是到了反向传播过程中,输入负数,梯度就会完全到0,这个和 sigmod 函数、tanh 函数有一样的问题。

    2. 我们发现 ReLU 函数的输出要么是0,要么是正数,这也就是说,ReLU 函数也不是以0为中心的函数。

    ReLU 代码实现

    代码实现也比较容易,在 TensorFlow 中有对应的函数(https://tensorflow.google.cn/api_docs/python/tf/nn/relu)可以直接调用:

    tf.nn.relu(features, name=None)
    

    该函数用于计算整流线性:max(features,0)max(features, 0)

    ARGS:

    • features:A Tensor。必须是下列类型之一:float32,float64,int32,uint8,int16,int8,int64,bfloat16,uint16,half,uint32,uint64,qint8。
    • name:操作的名称(可选)。

    Returns:

    • 一个Tensor,与features具有相同的类型。

    PReLU 激活函数

    • 论文全称:Delving Deep into Rectifiers : Surpassing Human-Level Performance on ImageNet Classification

    • 论文链接:https://arxiv.org/pdf/1502.01852.pdf

    PReLU 激活函数(Parametric Rectified Linear Unit),顾名思义:带参数的ReLU。二者的定义和区别如下图:

    在这里插入图片描述

    很明显地看出,PReLU 函数是针对 ReLU 函数的一个改进型:在负数区域内,PReLU 激活函数有一个很小的斜率,这样也可以避免 ReLU 激活函数死掉的问题。相比于 ELU,PReLU 在负数区域内是线性运算,斜率虽然小,但是不会趋于0,这算是一定的优势吧。

    我们看 PReLU 的公式:f(x)=max(ax,x)f(x)=max (a x, x),其中 a 是可以学习的的,参数 a 一般是取 0~1 之间的数。如果 a 是一个很小的固定值(如 a = 0.01),则 PReLU 退化为 Leaky ReLU(LReLU),算是PReLU的一种特殊情况吧。

    PReLU 代码实现

    PReLU 激活函数的实现似乎直接基于 PReLU 的函数表达式进行代码实现就可以了(请参阅:更高级别库的 keras 、tflearn 和 tensorlayer )。

    我这里是基于 TensorFlow 框架搭建的网络,所以也可以通过 TensorFlow 进行 PReLU 函数的编程和实现。下面这只是其中一种解决方案:

    def prelu(_x, name):
        """parametric ReLU activation"""
        _alpha = tf.get_variable(name + "prelu",
                                 shape=_x.get_shape()[-1],
                                 dtype=_x.dtype,
                                 initializer=tf.constant_initializer(0.1))
        pos = tf.nn.relu(_x)
        neg = _alpha * (_x - tf.abs(_x)) * 0.5
    
        return pos + neg
    

    博主注:上面的解决方法是经过试验验证可行的,这一点不用担心。

    需要注意的问题

    使用 PReLU 函数会引入一个参数,这会增加你原有网络的复杂性,包括读取数据时的批量和大小,我以自己实验室的情况为例。

    这是我显卡的型号和显存大小:

    在这里插入图片描述
    这是我显卡对应电脑的处理器配置、RAM和操作系统版本:

    在这里插入图片描述
    在使用 PReLU 函数之前

    参数 数量
    迭代次数 30
    训练集数量 8000
    学习率 1e-4
    每次读入训练集的图片数 2
    训练图片大小 512 x 512
    使用 ReLU 函数的数量 34

    在使用 PReLU 函数之后

    参数 数量
    迭代次数 30
    训练集数量 8000
    学习率 1e-4
    每次读入训练集的图片数 1
    训练图片大小 512 x 512
    使用 ReLU 函数的数量 15
    使用 PReLU 函数的数量 19

    我在实际操作的时候:

    • 首先是把全部的 ReLU 函数都换成 PReLU 函数,其他参数不变,出现OOM错误,也就是显存不够的错误;
    • 其次是调整每次读入训练集的图片数,从2到1,其他参数不变,还是出现OOM错误,也就是显存不够的错误;
    • 最后是调整部分 ReLU 函数为 PReLU 函数,其他参数不变,终于可以正常进行训练的过程。

    博主注:我这里使用的这种解决方法有一些细节问题还是需要注意的。

    • 首先是和原论文中的 PReLU 函数,依靠在这里插入图片描述,也就是带动量的更新方式,动量在 AdamOptimizer 优化器中存在。
    • 其次,本文的 PReLU 函数,增加了极少量的参数,也就意味着网络的计算量以及过拟合的危险性都只增加了一点点,但是可以增强网络的特征提取能力,方便于亚像素卷积网络的实现。
    • 最后,因为亚像素卷积网络的上采样过程其实并不是卷积,而是插值,所以没有用于学习的参数,导致整个网络的学习能力只能由前半部分的网络进行学习,引入了一个 PReLU 函数的参数可以增加网络的学习能力。

    如果想要更多的资源,欢迎关注 @我是管小亮,文字强迫症MAX~

    回复【福利】即可获取我为你准备的大礼,包括C++,编程四大件,NLP,深度学习等等的资料。

    想看更多文(段)章(子),欢迎关注微信公众号「程序员管小亮」~

    在这里插入图片描述

    参考文章

  • 相关阅读:
    Python的socket模块详解
    C语言中输入输出重定向,freopen的用法和实例
    JPG BMP TIF PNG 图像编码压缩率和编解码时间比较
    FFMPEG 内部YUV转RGB过程
    STL 中的数据结构
    FFmpeg 深度学习 livevideoStack 笔记
    Docker 编译tensorflow-1.14.0 静态库
    CMake 笔记
    Torch 学习
    Docker 学习笔记
  • 原文地址:https://www.cnblogs.com/hzcya1995/p/13302777.html
Copyright © 2020-2023  润新知