草庐IT

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

armcvai 2023-03-28 原文

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

一,激活函数概述

1.1,前言

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

从机器学习的角度来看,神经网络其实就是一个非线性模型,其基本组成单元为具有非线性激活函数的神经元,通过大量神经元之间的连接,使得多层神经网络成为一种高度非线性的模型。神经元之间的连接权重就是需要学习的参数,其可以在机器学习的框架下通过梯度下降方法来进行学习。

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

1.2,激活函数定义

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

假设一个神经元接收 \(D\) 个输入 \(x_1, x_2,⋯, x_D\),令向量 \(x = [x_1;x_2;⋯;x_?]\) 来表示这组输入,并用净输入(Net Input) \(z \in \mathbb{R}\) 表示一个神经元所获得的输入信号 \(x\) 的加权和:

\[z = \sum_{d=1}^{D} w_{d}x_{d} + b = w^\top x + b \]

其中 \(w = [w_1;w_2;⋯;w_?]\in \mathbb{R}^D\)\(D\) 维的权重矩阵,\(b \in \mathbb{R}\) 是偏置向量。

以上公式其实就是带有偏置项的线性变换(类似于放射变换),本质上还是属于线形模型。为了转换成非线性模型,我们在净输入 \(z\) 后添加一个非线性函数 \(f\)(即激活函数)。

\[a = f(z) \]

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

1.3,激活函数性质

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

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

二,Sigmoid 型函数(挤压型激活函数)

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

相关数学知识: 对于函数 \(f(x)\),若 \(x \to −\infty\) 时,其导数 \({f}'\to 0\),则称其为左饱和。若 \(x \to +\infty\) 时,其导数 \({f}'\to 0\),则称其为右饱和。当同时满足左、右饱和时,就称为两端饱和。

2.1,Logistic(sigmoid)函数

对于一个定义域在 \(\mathbb{R}\) 中的输入,sigmoid 函数将输入变换为区间 (0, 1) 上的输出(sigmoid 函数常记作 \(\sigma(x)\)):

\[\sigma(x) = \frac{1}{1 + exp(-x)} \]

sigmoid 函数的导数公式如下所示:

\[\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 函数在隐藏层中已经较少使用,原因是 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 函数,公式如下所示:

\[\text{tanh}(x) = 2\sigma(2x) - 1 = \frac{2}{1 + e^{-2x}} - 1 \]

利用基本导数公式,可得 Tanh 函数的导数公式(推导过程省略):

\[\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(-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 函数及其导数曲线图

可以看出 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) = \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 的部分调整为 \(\gamma \cdot x\), 其中 \(\gamma\) 常设置为 0.01 或 0.001 数量级的较小正数。这种新型的激活函数被称作带泄露的 ReLULeaky ReLU)。

\[\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\) 也作为一个网络中可学习的变量融入模型的整体训练过程。对于第 \(i\) 个神经元,PReLU 的 定义为:

\[\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 定义如下:

\[\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. \]

\(\gamma ≥ 0\) 是一个超参数,决定 \(x ≤ 0\) 时的饱和曲线,并调整输出均值在 0 附近。

详情可以参考原论文:《Fast and Accurate Deep Network Learning by Exponential Linear Units (ELUs)》

4,Softplus 函数: Softplus 函数其导数刚好是 Logistic 函数.Softplus 函数虽然也具有单侧抑制、宽 兴奋边界的特性,却没有稀疏激活性。Softplus 定义为:

\[\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)激活 函数,定义为

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

其中 \(\sigma(\cdot)\) 为 Logistic 函数,\(\beta\) 为可学习的参数或一个固定超参数。\(\sigma(\cdot) \in (0, 1)\) 可以看作一种软性的门控机制。当 \(\sigma(\beta x)\) 接近于 1 时,门处于“开”状态,激活函数的输出近似于 \(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. https://github.com/borgwang/tinynn/blob/master/tinynn/core/layer.py

有关神经网络基础部件-激活函数详解的更多相关文章

  1. ruby - 在没有 sass 引擎的情况下使用 sass 颜色函数 - 2

    我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re

  2. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

    我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

  3. ruby - 在 Ruby 中有条件地定义函数 - 2

    我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin

  4. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  5. ruby - 在 Ruby 中按名称传递函数 - 2

    如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只

  6. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  7. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  8. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  9. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  10. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

随机推荐