本文剖析了激活函数关于神经网络的必要性,同时讲解了几种常见的激活函数的原理,并给出相关公式、代码和示例图。

一,激活函数概述

1.1,前语

人工神经元(Artificial Neuron),简称神经元(Neuron),是构成神经网络的根本单元,其主要是模拟生物神经元的结构和特性,接纳一组输入信号并产生输出。生物神经元与人工神经元的比照图如下所示。

神经网络基础部件-激活函数详解

机器学习的视点来看,神经网络其实便是一个非线性模型,其根本组成单元为具有非线性激活函数的神经元,经过大量神经元之间的连接,使得多层神经网络成为一种高度非线性的模型。神经元之间的连接权重便是需求学习的参数,其能够在机器学习的结构下经过梯度下降方法来进行学习。

深度学习一般指的是深度神经网络模型,泛指网络层数在三层或许三层以上的神经网络结构。

1.2,激活函数界说

激活函数(也称“非线性映射函数”),是深度卷积神经网络模型中必不行少的网络层。

假定一个神经元接纳 DD 个输入 x1,x2,⋯,xDx_1, x_2,⋯, x_D,令向量 x=[x1;x2;⋯;x]x = [x_1;x_2;⋯;x_] 来表明这组输入,并用净输入(Net Input) z∈Rz \in \mathbb{R} 表明一个神经元所取得的输入信号 xx 的加权和:

z=∑d=1Dwdxd+b=w⊤x+bz = \sum_{d=1}^{D} w_{d}x_{d} + b = w^\top x + b

其间 w=[w1;w2;⋯;w]∈RDw = [w_1;w_2;⋯;w_]\in \mathbb{R}^DDD 维的权重矩阵,b∈Rb \in \mathbb{R} 是偏置向量。

以上公式其实便是带有偏置项的线性改换(类似于放射改换),本质上仍是属于线形模型。为了转换成非线性模型,咱们在净输入 zz 后添加一个非线性函数 ff(即激活函数)。

a=f(z)a = f(z)

由此,典型的神经元结构如下所示:

神经网络基础部件-激活函数详解

1.3,激活函数性质

为了增强网络的表明能力和学习能力,激活函数需求具有以下几点性质:

  1. 接连并可导(允许少数点上不行导)的非线性函数。可导的激活函数 能够直接使用数值优化的方法来学习网络参数。
  2. 激活函数及其导函数要尽可能的简略,有利于进步网络核算功率。
  3. 激活函数的导函数的值域要在一个适宜的区间内,不能太大也不能太小,否则会影响练习的功率和稳定性.

二,Sigmoid 型函数(揉捏型激活函数)

Sigmoid 型函数是指一类 S 型曲线函数,为两头饱满函数。常用的 Sigmoid 型函数有 Logistic 函数和 Tanh 函数。

相关数学知识: 关于函数 f(x)f(x),若 x→−∞x \to −\infty 时,其导数 f′→0{f}’\to 0,则称其为左饱满。若 x→+∞x \to +\infty 时,其导数 f′→0{f}’\to 0,则称其为右饱满。当同时满意左、右饱满时,就称为两头饱满。

2.1,Logistic(sigmoid)函数

关于一个界说域在 R\mathbb{R} 中的输入,sigmoid 函数将输入改换为区间 (0, 1) 上的输出。因此,sigmoid 通常称为揉捏函数(squashing function): 它将规模 (-inf, inf) 中的任意输入压缩到区间 (0, 1) 中的某个值:

(x)=11+exp(−x)\sigma(x) = \frac{1}{1 + exp(-x)}

sigmoid 函数常记作 (x)\sigma(x)。它的导数公式如下所示:

ddxsigmoid(x)=exp(−x)(1+exp(−x))2=sigmoid(x)(1−sigmoid(x))\frac{\mathrm{d} }{\mathrm{d} x}\text{sigmoid}(x) = \frac{exp(-x)}{(1+exp(-x))^2} = \text{sigmoid}(x)(1 – \text{sigmoid}(x))

sigmoid 函数及其导数曲线如下所示:

神经网络基础部件-激活函数详解

留意,当输入为 0 时,sigmoid 函数的导数达到最大值 0.25; 而输入在任一方向上越远离 0 点时,导数越挨近 0

现在 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(x)=2(2x)−1=21+e−2x−1\text{tanh}(x) = 2\sigma(2x) – 1 = \frac{2}{1 + e^{-2x}} – 1

使用根本导数公式,可得 Tanh 函数的导数公式(推导进程省掉):

ddxtanh(x)=1−tanh2(x)\frac{\mathrm{d} }{\mathrm{d} x} \text{tanh}(x) = 1 – \text{tanh}^{2}(x)

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(-8, 9)]
# 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 函数曲线。如下图所示:

神经网络基础部件-激活函数详解

以上代码的基础上,改下 plt.plot 函数的输入数据,同样可得到 Tanh 函数及其导数曲线图:

神经网络基础部件-激活函数详解

能够看出 SigmoidTanh 函数在输入很大或是很小的时分,输出都几乎滑润且梯度很小趋近于 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(x)=max(0,x)={xx≥00x<0ReLU(x) = max(0, x) = \left \lbrace \begin{matrix} x & x\geq 0 \\ 0 & x< 0 \end{matrix}\right.

以上公式浅显了解便是,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 的部分调整为 ⋅x\gamma \cdot x, 其间 \gamma 常设置为 0.01 或 0.001 数量级的较小正数。这种新式的激活函数被称作带走漏的 ReLULeaky ReLU)。

LeakyReLU(x)=max(0,)+min(0,x)={xx≥0⋅xx<0\text{Leaky ReLU}(x) = max(0, ) + \gamma\ min(0, x) = \left \lbrace \begin{matrix} x & x\geq 0 \\ \gamma \cdot x & x< 0 \end{matrix}\right.

概况能够参阅原论文:《Rectifier Nonlinearities Improve Neural Network Acoustic Models》

2,PReLU 函数: 为了处理 Leaky ReLU 中超参数 \gamma 不易设定的问题,有研究者提出了参数化 ReLU(Parametric ReLU,PReLU)。参数化 ReLU 直接将 \gamma 也作为一个网络中可学习的变量融入模型的整体练习进程。关于第 ii 个神经元,PReLU 的 界说为:

LeakyReLU(x)=max(0,)+imin(0,x)={xx≥0i⋅xx<0\text{Leaky ReLU}(x) = max(0, ) + \gamma_{i}\ min(0, x) = \left\lbrace\begin{matrix} x & x\geq 0 \\ \gamma_{i} \cdot x & x< 0 \end{matrix}\right.

概况能够参阅原论文:《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 界说如下:

LeakyReLU(x)=max(0,)+min(0,(exp(x)−1)={xx≥0(exp(x)−1)x<0\text{Leaky ReLU}(x) = max(0, ) + min(0, \gamma(exp(x) – 1) = \left\lbrace\begin{matrix} x & x\geq 0 \\ \gamma(exp(x) – 1) & x< 0 \end{matrix}\right.

≥0\gamma ≥ 0 是一个超参数,决议 x≤0x ≤ 0 时的饱满曲线,并调整输出均值在 0 邻近。

概况能够参阅原论文:《Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)》

4,Softplus 函数: Softplus 函数其导数刚好是 Logistic 函数.Softplus 函数尽管也具有单侧抑制、宽 振奋鸿沟的特性,却没有稀少激活性。Softplus 界说为:

Softplus(x)=log(1+exp(x))\text{Softplus}(x) = log(1 + exp(x))

Softplus 有兴趣的能够阅览这篇论文: 《Deep Sparse Rectifier Neural Networks》。

留意: ReLU 函数变体有许多,可是实践模型傍边使用最多的仍是 ReLU 函数本身

ReLU、Leaky ReLU、ELU 以及 Softplus 函数示意图如下图所示:

神经网络基础部件-激活函数详解

四,Swish 函数

Swish 函数[Ramachandran et al., 2017] 是一种自门控(Self-Gated)激活 函数,界说为

swish(x)=x(x)\text{swish}(x) = x\sigma(\beta x)

其间 (⋅)\sigma(\cdot) 为 Logistic 函数,\beta 为可学习的参数或一个固定超参数。(⋅)∈(0,1)\sigma(\cdot) \in (0, 1) 能够看作一种软性的门控机制。当 (x)\sigma(\beta x) 挨近于 1 时,门处于“开”状况,激活函数的输出近似于 xx 本身;当 (x)\sigma(\beta x) 挨近于 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 函数之间的非线性插值函数,其程度由参数 \beta 控制

五,激活函数总结

常用的激活函数包括 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。

参阅资料

  1. Pytorch分类问题中的交叉熵丢失函数使用
  2. 《解析卷积神经网络-第8章》
  3. 《神经网络与深度学习-第4章》
  4. How to Choose an Activation Function for Deep Learning
  5. 深度学习中的激活函数汇总
  6. Visualising Activation Functions in Neural Networks
  7. AI-EDU: 揉捏型激活函数
  8. github.com/borgwang/ti…