激活函数的理论部分可以参考这篇文章
从这篇文章里摘抄了几个知识点:
1. 为什么要用激活函数?
如果不用激活函数,每一层输出都是上层输入的线性函数,无论神经网络有多少层,输出都是输入的线性组合,这种情况就是最原始的感知机(Perceptron)。没有激活函数的每层都相当于矩阵相乘。就算你叠加了若干层之后,无非还是个矩阵相乘罢了。
如果使用的话,激活函数给神经元引入了非线性因素,使得神经网络可以任意逼近任何非线性函数,这样神经网络就可以应用到众多的非线性模型中。
2. 激活函数为什么是非线性的?
如果使用线性激活函数,那么输入跟输出之间的关系为线性的,无论神经网络有多少层都是线性组合。
使用非线性激活函数是为了增加神经网络模型的非线性因素,以便使网络更加强大,增加它的能力,使它可以学习复杂的事物,复杂的表单数据,以及表示输入输出之间非线性的复杂的任意函数映射。
输出层可能会使用线性激活函数,但在隐含层都使用非线性激活函数。
3.常用的激活函数:sigmoid,Tanh,ReLU,Leaky ReLU,PReLU,ELU,Maxout
自己写代码,实现其中几个激活函数:
首先导入依赖库
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
sigmoid 激活函数
优点:
- Sigmoid函数的输出在(0,1)之间,输出范围有限,优化稳定,可以用作输出层。
- 连续函数,便于求导。
缺点:
sigmoid函数在变量取绝对值非常大的正值或负值时会出现饱和现象,意味着函数会变得很平,并且对输入的微小改变会变得不敏感。在反向传播时,当梯度接近于0,权重基本不会更新,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。
sigmoid函数的输出不是0均值的,会导致后层的神经元的输入是非0均值的信号,这会对梯度产生影响。
计算复杂度高,因为sigmoid函数是指数形式。
公式如下:
f ( x ) = 1 1 + e − x f(x) = \frac{1}{1+e^{-x}} f(x)=1+e−x1
def sigmoid(x:torch.Tensor):
return 1/ (1 + torch.pow(torch.e, -x))
绘制 sigmoid 函数的图像
x = torch.linspace(-10, 10, 100)
y = sigmoid(x)
plt.plot(x, y)
Tanh 函数
函数公式如下:
f ( x ) = e x − e − x e x + e − x f(x)=\frac{e^x - e^{-x}}{e^x + e^{-x}} f(x)=ex+e−xex−e−x
def tanh(x:torch.Tensor) -> torch.Tensor:
a = torch.pow(torch.e, x) - torch.pow(torch.e, -x)
b = torch.pow(torch.e, x) + torch.pow(torch.e, -x)
return a/b
绘制函数图像:
x = torch.linspace(-10, 10, 100)
y = tanh(x)
plt.plot(x, y)
由图像可知,tanh函数是sigmoid函数向下平移和收缩后的结果。
Tanh 函数的优缺点:
Tanh函数是 0 均值的,因此实际应用中 Tanh 会比 sigmoid 更好。但是仍然存在梯度饱和与exp计算的问题。
ReLU函数
整流线性单元(Rectified linear unit,ReLU)是现代神经网络中最常用的激活函数,大多数前馈神经网络默认使用的激活函数。
f ( x ) = m a x ( 0 , x ) f(x) = max(0, x) f(x)=max(0,x)
优点:
- 使用ReLU的SGD算法的收敛速度比 sigmoid 和 tanh 快。
- 在x>0区域上,不会出现梯度饱和、梯度消失的问题。
- 计算复杂度低,不需要进行指数运算,只要一个阈值就可以得到激活值。
缺点:
- ReLU的输出不是0均值的。
- Dead ReLU Problem(神经元坏死现象):ReLU在负数区域被kill的现象叫做dead relu。ReLU在训练的时很“脆弱”。在x<0时,梯度为0。这个神经元及之后的神经元梯度永远为0,不再对任何数据有所响应,导致相应参数永远不会被更新。
产生这种现象的两个原因:参数初始化问题;learning rate太高导致在训练过程中参数更新太大。
解决方法:采用Xavier初始化方法,以及避免将learning rate设置太大或使用adagrad等自动调节learning rate的算法。