欢迎光临
我们一直在努力

AI筑基录——激活函数篇

前言

接上期损失函数函数的内容,我们这一期介绍的是激活函数;专栏 AI筑基录 前面几期比较偏理论内容,相对来说会比较枯燥,但是这个是我们学习中一定要克服的,后面我们会给大家比较有趣的实战作业;没有看我们上一期的内容的,可以看看上期的内容,内容连贯起来,会更加深刻;上一期的链接我放到下面,方便大家观看学习

AI筑基录——损失函数篇-CSDN博客

什么是激活函数?

为了解决这一个问题,我们先引入一个背景——为什么我们要用激活函数,假设不使用激活函数会有什么代价?明白这个就自然知道什么是激活函数,以及对应的作用!

我们看看最简单神经网络,也就是当层数等于1的时候的网络:

                                                        \\mathbf{y} = W\\mathbf{x} + \\mathbf{b}

其实,W 和 b 是我们权重,可以通过方向传播进行动态调整,x 是模型的输入,y 是模型的输出;我们可以通过观察出来实际上这个就是一次函数的表达式

接下来我们做一个操作,就是迭代多层,我们看看最后的表达式结果是什么?

第一层神经网络:

                                                ​​​​​​​        h = W_1x + b_1

第二层神经网络:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        y = W_2h + b_2

我们把第一层的表达式带入到第二层网络,并且整理一下:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​         y = W_2W_1x + W_2b_1 + b_2

换元一下把式子变得简洁一点:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        y = W'x + b'

实际上这一个形式,和我们前面的形式一样的,也就是说无论我们添加多少层网络,最后的公式本质上还是一个线性的表达式;那么会有小伙伴问了,那线性表达式会怎么样呢?带来什么后果?

在现实生活中,我们的世界的关系并不是单纯的线性关系,比如说识别一只小猫,我们考虑的点会有很多,比如说从轮廓,毛发,眼睛,耳朵等等。这一些东西一定不是线性关系就可以完全拟合的;

我们说一个比较简单的例子,在线性分类中,模型只能使用一条直线、一个平面或者一个超平面划分类别,那么二维情况下,它只能画一条直线,直线一边属于类别 A,另一边属于类别 B;如果数据本身无法被一条直线分开,模型无论训练多久都解决不了;

那么就是说,单纯堆叠线性层是无法解决非线性的问题的,听到这里,聪明的你就应该意识到,我们的激活函数就是专门解决这个问题的——提高模型的非线性表达能力

既然是提升模型的非线性的能力,那么激活函数应该是一个非线性函数,因此激活函数也有另外一个名字,非线性激活函数;那具体激活函数大概是做什么内容呢?

我们在每一个线性层后面加入激活函数:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        h = \\sigma(W_1x + b_1)

把这个结果带入第二层展开:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        y = W_2\\sigma(W_1x + b_1) + b_2

由于中间存在非线性函数 σ,整个表达式不能再简单地合并成线性的表达式;这时,多层网络才真正拥有了比单层线性模型更强的表达能力,能使网络能够拟合曲线、复杂边界以及更加抽象的特征关系

假设你能够看到这里,我相信你已经理解了什么是激活函数和为什么要用激活函数,那很自然我们下一步介绍的就是常见的激活函数有什么?

常见的激活函数\\sigma(x) = \\frac{1}{1+e^{-x}}

为了把这一篇文章尽量写出自己的风格,我打算用层层递进的形式来告诉大家常见的激活函数有什么,以及对应比较细的点

阶跃函数

这一个函数的安排,可以更加深刻的理解“激活”这一个词语的含义

其实最早的感知机通常使用阶跃函数:

                                                  f(x)= \\begin{cases} 1, & x>0 \\\\ 0, & x \\leq 0 \\end{cases}

这个就很像一个开关,函数表达的意思就是:输入大于 0,神经元被“激活”,输出 1;输入小于等于 0,神经元不激活,输出 0

从这个角度来看,“激活函数”这个名字也就比较容易理解了:它在判断一个神经元是否应该把信号继续传递下去

但是有一个致命的缺点,它在绝大多数位置的导数都是 0,并且在x = 0的位置不可导,神经网络依靠梯度下降和反向传播更新参数的时候,如果梯度一直为 0,模型就无法知道参数应该朝哪个方向修改,因此我们需要一个既能像开关一样控制信息,又能够计算梯度的函数;

Sigmoid 函数

为了解决前者的痛点,Sigmoid 出现了,其表达式:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \\sigma(x) = \\frac{1}{1+e^{-x}}​​​​​​​

观察这个函数,可以发现值域处于 0~1 之间,因此它很适合表示概率。比如在二分类任务中,模型可以输出:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        p = \\sigma(z)

就可以解释为该样本属于正类的概率,我们可以看看这个函数的导数:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​   \\sigma'(x) = \\sigma(x)\\big(1-\\sigma(x)\\big)

我们可以发现,这个公式导数可以直接用函数本身计算,但是有一个缺点,当 x 很大或者很小的时候,导数的值都趋近于 0,也就是说,在函数两端,输出虽然还在变化,但梯度已经非常接近 0,这个区域被称为饱和区

如果神经网络层数很深,反向传播时需要连续相乘很多层的导数,许多小于 1 的数不断相乘,梯度会越来越小,最终前面的网络层几乎无法得到更新,这就是梯度消失

所以说一般这个激活函数不会用于深度比较深的网络

Tanh 函数

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \\tanh(x)=\\frac{e^x-e^{-x}}{e^x+e^{-x}}

观察这个函数,可以发现值域是在 -1~1 之间的,这意味着它的输出既可以是正数,也可以是负数,也就是以 0 为中心点;它的导数是:

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​          \\tanh'(x) = 1-\\tanh^2(x)

它并没有彻底解决 Sigmoid 的问题。当输入的绝对值很大时导数趋近于 0 的情况,但是通常比 Sigmoid 更适合作为隐藏层

ReLU 函数

          ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​           \\mathrm{ReLU}(x)= \\begin{cases} x, & x>0 \\\\ 0, & x \\leq 0 \\end{cases}

函数表达的意思很简单:当输入大于 0 时,ReLU 直接输出输入本身;当输入小于等于 0 时,直接输出 0,看起来就是把负数部分给截掉,但是这一个小小的操作确实带来了很巨大的作用:

我们看看导数部分,在 x > 1 的部分,导数的值恒等于 1,这就带来了一个优势,反向传播时,梯度不会像 Sigmoid 那样在正数区域不断缩小

这个计算不涉及指数函数的计算,只需要判断输入是否大于 0 ,计算成本很低

当输入小于 0 时,输出直接变为 0。也就是说,一次前向传播中,部分神经元不会被激活,很符合我们的大脑特性,不是每一个神经元我们都会用到的;

优点说完了,我们来看看 ReLU 函数的缺点:

因为当输入是负数的时候,导数是 0 ,也就是说如果一个神经元因为参数更新,导致它对所有输入的结果都小于 0,那么它的输出始终是 0,梯度也始终为 0。这样它的参数就很难再次更新回来,这样的情况也被称为神经元死亡,为了解决这个问题,人们引入了 Leaky ReLU

Leaky ReLU 函数

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​                  f(x)= \\begin{cases} x, & x>0 \\\\ \\alpha x, & x \\leq 0 \\end{cases}

Leaky ReLU 在负数区域不会直接输出 0,而是保留一个很小的斜率,其中 α 通常是一个比较小的正数,因此求导后梯度虽然比较小,但并没有完全消失,这也就是这个函数的目的:防止神经元进入负数区域之后彻底失去更新能力

还有一个变体叫 PReLU,它不是人工固定 α\\alphaα,而是让模型自己学习负数区域的斜率

GELU 函数

这个函数的公式会比较复杂,我们大概有一个印象就行了:

               ​​​​​​​             \\mathrm{GELU}(x) \\approx \\frac{x}{2}\\left[1+\\tanh\\left(\\sqrt{\\frac{2}{\\pi}}\\big(x+0.044715x^3\\big)\\right)\\right]

这个函数表示的是,输入越大,通过的比例越高,输入较小时,不是直接归零,而是进行平滑抑制,这个就不会想 ReLU 在 x = 0 处突然转折;这个函数经常用在 Transformer 模型

SiLU 函数

        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​          \\mathrm{SiLU}(x) = x\\sigma(x)

我们可以从公式上理解:输入 x 先经过 Sigmoid 得到一个 0∼1 的权重,再用这个权重控制 x 自身有多少可以通过;

SiLU 和 GELU 都属于平滑激活函数,在现代卷积网络和生成模型中也比较常见

总结

虽然激活函数的代码基本上都是一行就可以解决的,但是地位我们从前面的介绍可以看出还是很重要的;我们要熟悉什么样的网络可以选择什么样的激活函数,以及对应的函数图像大概要有点印象;而且激活函数还是可以和很多的东西打配合的,比如说参数初始化,归一化,残差连接等一系列内容,后面我们的专栏也会持续的更新对应的内容!

这期文章也是把很多激活函数都介绍了一下,里面涉及梯度的问题,我们下期文章可以专门出一期梯度相关的内容, AI筑基录 的专栏会慢慢更新完,带大家有体系的理解神经网络的由来!制作不易,喜欢博主文章的可以点赞收藏关注,这些都是我持续更新的动力!

赞(0)
未经允许不得转载:171主机测评 » AI筑基录——激活函数篇
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址