神经网络常用激活函数及其应用举例

ReLU、Leaky ReLU、PReLU

Leaky ReLUs
    ReLU是将所有的负值都设为零,相反,Leaky ReLU是给所有负值赋予一个非零斜率。Leaky ReLU激活函数是在声学模型(2013)中首次提出的。以数学的方式我们可以表示为:

    

ai是(1,+∞)区间内的固定参数。

参数化修正线性单元(PReLU)
    PReLU可以看作是Leaky ReLU的一个变体。在PReLU中,负值部分的斜率是根据数据来定的,而非预先定义的。作者称,在ImageNet分类(2015,Russakovsky等)上,PReLU是超越人类分类水平的关键所在。
随机纠正线性单元(RReLU)
    “随机纠正线性单元”RReLU也是Leaky ReLU的一个变体。在RReLU中,负值的斜率在训练中是随机的,在之后的测试中就变成了固定的了。RReLU的亮点在于,在训练环节中,aji是从一个均匀的分布U(I,u)中随机抽取的数值。形式上来说,我们能得到以下结果:

    

总结
    下图是ReLU、Leaky ReLU、PReLU和RReLU的比较:

    

激活函数简介

神经网络中数据从数据层到最后输出层的流动过程其实就是数据从一种形态到另一种形态,从一个维度到另一个维度的变换过程,例如在Minst数据集训练中,就是把28*28维的图像数据经过变换,映射成0~9之间的一个数字。完成这种数据变换的一个重要工具就是激活函数。

一个典型的人工神经元的模型可以用下图表述:

  • 图中X1~Xn是神经元的输入信号;
  • θ表示一个阈值,或称为偏置(bias),偏置的设置是为了正确分类样本,是模型中一个重要的参数;
  • ∑表示各输入信号X乘以权重后的累加和,是一个线性变换;
  • f(*)称为激活函数或激励函数(Activation Function),激活函数的主要作用是完成数据的非线性变换,解决线性模型的表达、分类能力不足的问题;
激活函数的主要作用是改变之前数据的线性关系,如果网络中全部是线性变换,则多层网络可以通过矩阵变换,直接转换成一层神经网络,所以激活函数的存在,使得神经网络的“多层”有了实际的意义,使网络更加强大,增加网络的能力,使它可以学习复杂的事物,复杂的数据,以及表示输入输出之间非线性的复杂的任意函数映射。
激活函数的另一个重要的作用是执行数据的归一化,将输入数据映射到某个范围内,再往下传递,这样做的好处是可以限制数据的扩张,防止数据过大导致的溢出风险。

常见的激活函数

1.  Sigmoid

通过Sigmoid的函数图像我们可以直观的看到,当输入值X>5或者X<-5的时候,函数的变化曲线已经非常平缓了,这就意味着此时的偏导(即切线的斜率)非常小接近0,这就导致误差反向传播无法通过导数传递到上一层,进而导致训练无法正常训练(导数为0无法更新参数)。为了避免梯度饱和效应的产生,Hinton等人与2010年又提出了ReLU函数,可以有效的避免梯度饱和产生

Sigmod有两个主要缺点,一是Sigmoid容易过饱和,丢失梯度。从Sigmoid的示意图上可以看到,神经元的活跃度在0和1处饱和,梯度接近于0,这样在反向传播时,很容易出现梯度消失的情况,导致训练无法完整;二是Sigmoid的输出均值不是0,基于这两个缺点,SIgmoid使用越来越少了。

2. tanh

tanh是Sigmoid函数的变形,tanh的均值是0,在实际应用中有比Sigmoid更好的效果。

3. ReLU

ReLU是近来比较流行的激活函数,当输入信号小于0时,输出为0;当输入信号大于0时,输出等于输入。

ReLU的优点:

1. ReLU是部分线性的,并且不会出现过饱和的现象,使用ReLU得到的随机梯度下降法(SGD)的收敛速度比Sigmodi和tanh都快。

2. ReLU只需要一个阈值就可以得到激活值,不需要像Sigmoid一样需要复杂的指数运算。

ReLU的缺点:

在训练的过程中,ReLU神经元比价脆弱容易失去作用。例如当ReLU神经元接收到一个非常大的的梯度数据流之后,这个神经元有可能再也不会对任何输入的数据有反映了,所以在训练的时候要设置一个较小的合适的学习率参数。

5. Maxout

Maxout是在2013年才提出的,是一种激发函数形式,一般情况下如果采用Sigmoid函数的话,在前向传播过程中,隐含层节点的输出表达式为:

其中W一般是二维的,这里表示取出的是第i列,下标i前的省略号表示对应第i列中的所有行。而在Maxout激发函数中,在每一个隐含层和输入层之间又隐式的添加了一个“隐含层”,这个“隐隐含层”的激活函数是按常规的Sigmoid函数来计算的,而Maxout神经元的激活函数是取得所有这些“隐隐含层”中的最大值,如上图所示。

Maxout的激活函数表示为:

f(x)=max(wT1x+b1,wT2x+
b2)

可以看到,ReLU 和 Leaky ReLU 都是它的一个变形(比如, 的时候,就是 ReLU)。

Maxout的拟合能力是非常强的,它可以拟合任意的的凸函数,优点是计算简单,不会过饱和,同时又没有ReLU的缺点(容易死掉),但Maxout的缺点是过程参数相当于多了一倍。

其他一些激活函数列表:

激活函数应用
这里以最简单的只有一个神经元(包含激活函数)的网络为例,说明一下激活函数的工作过程。

假设神经元的输入是一个4维的向量 X,其中的值为0或者1,并且只取4中样本:

  • x1=[1,0,0,0]
  • x2=[0,1,0,0]
  • x3=[0,0,1,0]
  • x4=[0,0,0,1]

对这一组样本,采用一个很小的随机数来模拟生成20000个样本,同时,将神经元的4个输出值映射到[0,1],即4个分类分别是[0.25,0.5,0.75,1]。

代码实现如下:

#include <iostream>  
#include <vector>  
  
using namespace std;  
  
vector<vector<double>> inputData;  
vector<double> weight;  
double actual_output;  //网络的实际输出  
vector<double> input;  
  
//生成numofSample个样本,一共分为numofLable类,每个样本的标签是当前样本的下标i%numofLable  
void getSamplesData(int numofSamples, int numofLable)  
{  
    const int iterations = numofSamples; // 样本数量  
    for (int i = 0; i < iterations; i++)  
    {  
        int index = i % numofLable;  
        vector<double> dvect(numofLable, 0);  
        dvect[index] = 1;  
        for (size_t i = 0; i != dvect.size(); i++)  
        {  
            dvect[i] += (5e-3*rand() / RAND_MAX - 2.5e-3);  
        }  
        inputData.push_back(dvect);  
    }  
}  
  
//初始化初始权重,包括4个节点和1个偏置,随机设置为0~0.05之间的一个值  
void intialWeight()  
{  
    cout << "初始化权重为:" << endl;  
    // 4个连结和一个偏置w0  
    for (int i = 0; i != 5; i++)  
    {  
        weight.push_back(0.05*rand() / RAND_MAX);  
        cout << weight[i] << endl;  
    }  
}  
  
//前向计算,输入时数据层的4维的训练数据,actual_output是网络当前的输出  
void cmtForward(const vector<double>& inVect)  
{  
    double dsum = weight[4];//先把偏置加上  
    for (size_t i = 0; i != inVect.size(); i++)  
    {  
        dsum += (inVect[i] * weight[i]);  
    }  
    actual_output = 1 / (1 + exp(-1 * dsum));   // S函数的非线性变换  
    //actual_output = 2 * (1 / (1 + exp(-2 * dsum))) - 1;  //T函数的非线性变换  
    //dsum > 0 ? actual_output = dsum : actual_output = 0;  //R函数的非线性变换  
}  
  
//权重调整,相当于后向计算,输入时训练数据和期望的输出  
void updataWeight(const vector<double>& inVect, const double true_output)  
{  
    double learnRate = 0.05; // 权重更新参数  
    for (size_t i = 0; i != weight.size() - 1; i++)  
    {  
        weight[i] += (learnRate*(true_output - actual_output)*actual_output*(1 - actual_output)*inVect[i]);  
    }  
    // w0单独计算  
    weight[4] += (learnRate*(true_output - actual_output)*actual_output*(1 - actual_output) * 1);  
}  
  
  
void main()  
{  
    getSamplesData(20000, 4);  //生成20000个数据,分为4类  
    intialWeight();    //初始化权重,随机生成5个0~0.05之间的数值  
  
    //执行20000次迭代  
    for (int i = 0; i < 20000; i++)  
    {  
        input = inputData[i];  
        double lable = (double)(i % 4) / 4 + 0.25;  //数据标签转换到0~1  
  
        cmtForward(input);    //前向传播计算  
        updataWeight(input, lable);   //权重调整  
  
        if (i % 111 == 0)  
        {  
            cout << "当前执行第 " << i << " 次迭代" << endl;  
            cout << "输入数据: [ " << input[0] << "," << input[1] << "," << input[2] << "," << input[3]  
                << " ]" << "   " << "网络输出:" << actual_output << endl << endl;  
        }  
    }  
    cout << "优化后的权重系数:[ " << weight[0] << "," << weight[1] << "," << weight[2] << "," <<  
        weight[3] << " ]" << "   偏置: " << weight[4] << endl;  
    system("pause");  
}  


在前向计算cmtForward函数中,使用S函数,注释掉的分别是T函数和R函数。

S函数的迭代实现结果:

T函数的迭代实现结果:

R函数的迭代实现结果:

经过迭代优化,3种激活函数对输入的数据的响应基本都映射在0.25,0.5,0.75,0.96左右,分别对应1,2,3,4分类,都可以对输入正确分类。3种激活函数迭代后分别生成3组权重系数(加偏置系数),不同激活函数优化出的权重系数不同。

原文:https://blog.csdn.net/dcrmg/article/details/76222576

参考:https://www.jianshu.com/p/6033a5d3ad4b

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI算法网奇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值