神经网络基础部件-激活函数详解
本文分析了激活函数对于神经网络的必要性,同时讲解了几种常见的激活函数的原理,并给出相关公式、代码和示例图。
一,激活函数概述
1.1,前言
人工神经元(Artificial Neuron),简称神经元(Neuron),是构成神经网络的基本单元,其主要是模拟生物神经元的结构和特性,接收一组输入信号并产生输出。生物神经元与人工神经元的对比图如下所示。
从机器学习的角度来看,神经网络其实就是一个非线性模型,其基本组成单元为具有非线性激活函数的神经元,通过大量神经元之间的连接,使得多层神经网络成为一种高度非线性的模型。神经元之间的连接权重就是需要学习的参数,其可以在机器学习的框架下通过梯度下降方法来进行学习。
深度学习一般指的是深度神经网络模型,泛指网络层数在三层或者三层以上的神经网络结构。
1.2,激活函数定义
激活函数(也称“非线性映射函数”),是深度卷积神经网络模型中必不可少的网络层。
假设一个神经元接收
其中
以上公式其实就是带有偏置项的线性变换(类似于放射变换),本质上还是属于线形模型。为了转换成非线性模型,我们在净输入
由此,典型的神经元结构如下所示:
1.3,激活函数性质
为了增强网络的表示能力和学习能力,激活函数需要具备以下几点性质:
- 连续并可导(允许少数点上不可导)的非线性函数。可导的激活函数 可以直接利用数值优化的方法来学习网络参数。
- 激活函数及其导函数要尽可能的简单,有利于提高网络计算效率。
- 激活函数的导函数的值域要在一个合适的区间内,不能太大也不能太小,否则会影响训练的效率和稳定性.
二,Sigmoid 型函数(挤压型激活函数)
Sigmoid 型函数是指一类 S 型曲线函数,为两端饱和函数。常用的 Sigmoid 型函数有 Logistic 函数和 Tanh 函数。
相关数学知识: 对于函数
,若 时,其导数 ,则称其为左饱和。若 时,其导数 ,则称其为右饱和。当同时满足左、右饱和时,就称为两端饱和。
2.1,Logistic(sigmoid)函数
对于一个定义域在 sigmoid
函数将输入变换为区间 (0, 1)
上的输出(sigmoid 函数常记作
sigmoid 函数的导数公式如下所示:
sigmoid 函数及其导数曲线如下所示:
注意,当输入为 0 时,sigmoid 函数的导数达到最大值 0.25; 而输入在任一方向上越远离 0 点时,导数越接近 0
,即当sigmoid 函数的输入很大或是很小时,它的梯度都会消失。
目前 sigmoid
函数在隐藏层中已经较少使用,原因是 sigmoid
的软饱和性,使得深度神经网络在过去的二三十年里一直难以有效的训练,如今其被更简单、更容易训练的 ReLU
等激活函数所替代。
当我们想要输出二分类或多分类、多标签问题的概率时,sigmoid
可用作模型最后一层的激活函数。下表总结了常见问题类型的最后一层激活和损失函数。
问题类型 | 最后一层激活 | 损失函数 |
---|---|---|
二分类问题(binary) | sigmoid |
sigmoid + nn.BCELoss (): 模型最后一层需要经过 torch.sigmoid 函数 |
多分类、单标签问题(Multiclass) | softmax |
nn.CrossEntropyLoss() : 无需手动做 softmax |
多分类、多标签问题(Multilabel) | sigmoid |
sigmoid + nn.BCELoss() : 模型最后一层需要经过 sigmoid 函数 |
nn.BCEWithLogitsLoss()
函数等效于sigmoid + nn.BCELoss
。
2.2,Tanh 函数
Tanh
(双曲正切)函数也是一种 Sigmoid 型函数,可以看作放大并平移的 Sigmoid
函数,公式如下所示:
利用基本导数公式,可得 Tanh 函数的导数公式(推导过程省略):
Logistic 和 Tanh 两种激活函数的实现及可视化代码(复制可直接运行)如下所示:
# example plot for the sigmoid activation function import numpy as np from matplotlib import pyplot import matplotlib.pyplot as plt # sigmoid activation function def sigmoid(x): """1.0 / (1.0 + exp(-x)) """ return 1.0 / (1.0 + np.exp(-x)) def tanh(x): """2 * sigmoid(2*x) - 1 (e^x – e^-x) / (e^x + e^-x) """ # return (exp(x) - exp(-x)) / (exp(x) + exp(-x)) return 2 * sigmoid(2*x) - 1 def relu(x): return max(0.0, x) def gradient_relu(x): """1 * (x > 0)""" if x < 0.0: return 0 else: return 1 def gradient_sigmoid(x): """sigmoid(x)(1−sigmoid(x)) """ a = sigmoid(x) b = 1 - a return a*b def gradient_tanh(x): return 1 - tanh(x)**2 # 1, define input data inputs = [x for x in range(-6, 7)] # 2, calculate outputs outputs = [sigmoid(x) for x in inputs] outputs2 = [tanh(x) for x in inputs] # 3, plot sigmoid and tanh function curve plt.figure(dpi=100) # dpi 设置 plt.style.use('ggplot') # 主题设置 plt.plot(inputs, outputs, label='sigmoid') plt.plot(inputs, outputs2, label='tanh') plt.xlabel("x") # 设置 x 轴标签 plt.ylabel("y") plt.title('sigmoid and tanh') # 折线图标题 plt.legend() plt.show()
程序运行后得到的 Sigmoid 和 Tanh 函数曲线如下图所示:
改变下函数输出,同样可得到 Tanh 函数及其导数曲线图
可以看出 Sigmoid
和 Tanh
函数在输入很大或是很小的时候,输出都几乎平滑且梯度很小趋近于 0,不利于权重更新;不同的是 Tanh
函数的输出区间是在 (-1,1)
之间,而且整个函数是以 0 为中心的,即他本身是零均值的,也就是说,在前向传播过程中,输入数据的均值并不会发生改变,这就使他在很多应用中效果能比 Sigmoid 优异一些。
Tanh 函数优缺点总结:
- 具有 Sigmoid 的所有优点。
exp
指数计算代价大。梯度消失问题仍然存在。
Tanh
函数及其导数曲线如下所示:
Tanh 和 Logistic 函数的导数很类似,都有以下特点:
- 当输入接近 0 时,导数接近最大值 1。
- 输入在任一方向上越远离0点,导数越接近0。
三,ReLU 函数及其变体(半线性激活函数)
3.1,ReLU 函数
ReLU
(Rectified Linear Unit,修正线性单元),是目前深度神经网络中最经常使用的激活函数,它保留了类似 step 那样的生物学神经元机制: 输入超过阈值才会激发。公式如下所示:
以上公式通俗理解就是,ReLU
函数仅保留正元素并丢弃所有负元素。注意: 虽然在 0
点不能求导,但是并不影响其在以梯度为主的反向传播算法中发挥有效作用。
1,优点:
ReLU
激活函数计算简单;- 具有很好的稀疏性,大约 50% 的神经元会处于激活状态。
- 函数在 x > 0 时导数为 1 的性质(左饱和函数),在一定程度上缓解了神经网络的梯度消失问题,加速梯度下降的收敛速度。
相关生物知识: 人脑中在同一时刻大概只有 1% ∼ 4% 的神经元处于活跃状态。
2,缺点:
- ReLU 函数的输出是非零中心化的,给后一层的神经网络引入偏置偏移,会影响梯度下降的效率。
- ReLU 神经元在训练时比较容易“死亡”。如果神经元参数值在一次不恰当的更新后,其值小于 0,那么这个神经元自身参数的梯度永远都会是 0,在以后的训练过程中永远不能被激活,这种现象被称作“死区”。
ReLU 激活函数的代码定义如下:
# pytorch 框架对应函数: nn.ReLU(inplace=True) class ReLU(object): def func(self, x): return np.maximum(x, 0.0) def derivative(self, x): """简单写法: return x > 0.0""" da = np.array([1 if x > 0 else 0 for x in a]) return da
ReLU 激活函数及其函数梯度图如下所示:
ReLU
激活函数的更多内容,请参考原论文 Rectified Linear Units Improve Restricted Boltzmann Machines
3.2,Leaky ReLU/PReLU/ELU/Softplus 函数
1,Leaky ReLU
函数: 为了缓解“死区”现象,研究者将 ReLU 函数中 x < 0 的部分调整为 Leaky ReLU
)。
详情可以参考原论文:《Rectifier Nonlinearities Improve Neural Network Acoustic Models》
2,PReLU
函数: 为了解决 Leaky ReLU 中超参数 PReLU
)。参数化 ReLU 直接将 PReLU
的 定义为:
详情可以参考原论文:《Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification》
3,ELU
函数: 2016 年,Clevert 等人提出的 ELU
(Exponential Linear Units) 在小于零的部分采用了负指数形式。ELU
有很多优点,一方面作为非饱和激活函数,它在所有点上都是连续的和可微的,所以不会遇到梯度爆炸或消失的问题;另一方面,与其他线性非饱和激活函数(如 ReLU 及其变体)相比,它有着更快的训练时间和更高的准确性。
但是,与 ReLU 及其变体相比,其指数操作也增加了计算量,即模型推理时 ELU
的性能会比 ReLU
及其变体慢。 ELU
定义如下:
0
附近。
详情可以参考原论文:《Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)》
4,Softplus
函数: Softplus 函数其导数刚好是 Logistic 函数.Softplus 函数虽然也具有单侧抑制、宽 兴奋边界的特性,却没有稀疏激活性。Softplus
定义为:
对
Softplus
有兴趣的可以阅读这篇论文: 《Deep Sparse Rectifier Neural Networks》。
注意: ReLU 函数变体有很多,但是实际模型当中使用最多的还是 ReLU
函数本身。
ReLU、Leaky ReLU、ELU 以及 Softplus 函数示意图如下图所示:
四,Swish 函数
Swish
函数[Ramachandran et al., 2017] 是一种自门控(Self-Gated)激活 函数,定义为
其中 1
时,门处于“开”状态,激活函数的输出近似于 0
时,门的状态为“关”,激活函数的输出近似于 0
。
Swish
函数代码定义如下:
# Swish https://arxiv.org/pdf/1905.02244.pdf class Swish(nn.Module): #Swish激活函数 @staticmethod def forward(x, beta = 1): # 此处beta默认定为1 return x * torch.sigmoid(beta*x)
结合前面的画曲线代码,可得 Swish 函数的示例图:
Swish 函数可以看作线性函数和 ReLU 函数之间的非线性插值函数,其程度由参数
五,激活函数总结
常用的激活函数包括 ReLU
函数、sigmoid
函数和 tanh
函数。其标准代码总结如下(Pytorch 框架中会更复杂)
from math import exp class Sigmoid(object): def func(self, x): return 1.0 / (1.0 + np.exp(-x)) def derivative(self, x): return self.func(x) * (1.0 - self.func(x)) class Tanh(object): def func(self, x): return np.tanh(x) def derivative(self, x): return 1.0 - self.func(x) ** 2 class ReLU(object): def func(self, x): return np.maximum(x, 0.0) def derivative(self, x): return x > 0.0 class LeakyReLU(object): def __init__(self, alpha=0.2): super().__init__() self.alpha = alpha def func(self, x): return np.array([x if x > 0 else self.alpha * x for x in z]) def derivative(self, x): dx = np.array([1 if x > 0 else self.alpha for x in a]) return dx class Softplus(object): def func(self, x): return np.log(1 + np.exp(z)) def derivative(self, x): return 1.0 / (1.0 + np.exp(-x))
下表汇总比较了几个激活函数的属性:
激活函数的在线可视化移步 Visualising Activation Functions in Neural Networks。
参考资料
版权声明 ©
本文作者:嵌入式视觉
本文链接:https://www.cnblogs.com/armcvai/p/17041421.html
版权声明:本文为「嵌入式视觉」的原创文章,首发于 github ,遵循 CC BY-NC-ND 4.0 版权协议,著作权归作者所有,转载请注明出处!
鼓励博主:如果您觉得文章对您有所帮助,可以点击文章右下角【推荐】一下。您的鼓励就是博主最大的动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性