博客
文章
深度学习:Basics·第 2 / 12 篇

激活函数:让模型自己学特征

逻辑回归分不开的数据,传统做法是人工加特征。隐藏层把这件事交给模型,激活函数则保证学出来的特征不会退化成原始输入的线性组合。

deep-learningneural-networkactivation-functionrelugelu

上一篇结尾留下了单个神经元的两条边界:它只能画出线性决策边界;它只能使用我们交给它的输入。这两条其实是同一个问题的两面。数据在原始输入上线性不可分时,机器学习的常规做法是改输入,也就是做特征工程。神经网络换了一个做法:加一层隐藏层,让模型自己学出新的输入。

这一篇讲清楚两件事。第一,隐藏层要学出有用的特征,层与层之间为什么必须有一个非线性函数。第二,这个函数为什么通常选 ReLU 这一类曲线,而很少直接用阶跃函数或 Sigmoid。

一个逻辑回归处理不了的规则

舒适温度区间

只取一个输入:房间温度 xx(摄氏度)。标签是房间是否舒适。

温度 xx101016162121232328283434
舒适否否是是否否

假设规则是 18 到 24 度之间算舒适:

y={1,18≤x≤240,其他y= \begin{cases} 1, & 18\le x\le 24\\ 0, & \text{其他} \end{cases}

这类「过低和过高都不行」的规则在实际数据里很常见,比如药物剂量、商品定价。

线性分数只能切一刀

逻辑回归计算 z=wx+bz=wx+b,在 z≥0z\ge 0 时预测为类别 11。w>0w>0 时,预测为 11 的区域是 x≥−b/wx\ge -b/w;w<0w<0 时是 x≤−b/wx\le -b/w。两种情况都只有一个端点,而目标区间需要两个。

一条直线追不上一段区间
x 21.0
目标(舒适 = 1) 1.000 线性分数 z 0.150
21.0
目标在 18–24 ℃ 之间为 1,两侧都为 0。线性分数 z = wx + b 单调,z ≥ 0 的一侧永远是一条射线,不可能只覆盖中间那一段。

外层的 Sigmoid 帮不上忙。它是单调函数,只改变分数的尺度,不改变分数的大小顺序。问题出在 zz 关于 xx 单调,而目标不单调。

熟悉的解法:加一个特征

把 x2x^2 放进输入

熟悉逻辑回归的话,第一反应多半是加一个二次项。把输入从 xx 换成 (x,x2)(x, x^2),模型变成

z=w1x+w2x2+bz=w_1x+w_2x^2+b

取 w1=42w_1=42、w2=−1w_2=-1、b=−432b=-432:

z=−x2+42x−432=−(x−18)(x−24)z=-x^2+42x-432=-(x-18)(x-24)

这是一条开口向下的抛物线,只在 18≤x≤2418\le x\le 24 时非负,目标区间就被写出来了。

模型本身没有变。它对参数 w1w_1、w2w_2、bb 仍然是线性的,训练方法也和上一篇完全一样。变的是输入:x2x^2 不是 xx 的线性函数,它给了模型一个新坐标,在这个坐标下答案变得线性可分。

这个办法的代价

x2x^2 是我们选的。选它之前,我们已经知道目标的形状是「中间高、两边低」。

一维时这不难。输入一多就难了:温度和湿度两个变量,二次特征有 x12x_1^2、x22x_2^2、x1x2x_1x_2 三项;dd 个变量有约 d2/2d^2/2 项。一张 224×224224\times224 的彩色图像有 150,528150{,}528 个输入,二次项超过 101010^{10} 个,而且我们并不知道「是不是猫」应该用二次特征来描述。

所以问题变成:能不能把「从 xx 算出新特征」这一步也交给模型,用数据训练出来?

让模型自己学特征

用一层线性变换提取特征

最直接的尝试是在输入和逻辑回归之间插一层可训练的变换:

h=W1x+b1,z=w2⊤h+b2\boldsymbol{h}=\boldsymbol{W}_1\boldsymbol{x}+\boldsymbol{b}_1, \qquad z=\boldsymbol{w}_2^\top\boldsymbol{h}+b_2

h\boldsymbol{h} 就是想让模型学出来的特征,W1\boldsymbol{W}_1、b1\boldsymbol{b}_1 和输出层参数一起训练。

线性层会合并成一层

把第一式代入第二式:

z=w2⊤(W1x+b1)+b2=(w2⊤W1)x+(w2⊤b1+b2)z=\boldsymbol{w}_2^\top(\boldsymbol{W}_1\boldsymbol{x}+\boldsymbol{b}_1)+b_2 =(\boldsymbol{w}_2^\top\boldsymbol{W}_1)\boldsymbol{x}+(\boldsymbol{w}_2^\top\boldsymbol{b}_1+b_2)

结果仍然是 w⊤x+b\boldsymbol{w}^\top\boldsymbol{x}+b 的形式,和原来的逻辑回归没有区别。叠多少层都一样,因为线性(严格说是仿射)变换的复合还是仿射变换。

对照上一节就能看出原因。x2x^2 有用,是因为它不是 xx 的线性函数;而 W1x+b1\boldsymbol{W}_1\boldsymbol{x}+\boldsymbol{b}_1 的每个分量都是 x\boldsymbol{x} 的线性函数,输出层能从中得到的信息,直接用 x\boldsymbol{x} 也能得到。

在中间加一个非线性函数

修正方法是对每个分量套一个非线性函数 ϕ\phi:

h=ϕ(W1x+b1)\boldsymbol{h}=\phi(\boldsymbol{W}_1\boldsymbol{x}+\boldsymbol{b}_1)

ϕ\phi 逐元素作用,这就是激活函数。只要 ϕ\phi 不是 αz+β\alpha z+\beta 的形式,上面的合并就不再成立,h\boldsymbol{h} 才可能成为 x2x^2 那样的新特征。

注意这里说的是隐藏层的激活函数。上一篇输出层的 Sigmoid 负责把分数转成概率,是另一件事。

ReLU 作为可学习的特征

两个 ReLU 单元

最常用的激活函数是

ReLU⁡(z)=max⁡(0,z)\operatorname{ReLU}(z)=\max(0,z)

它在 z<0z<0 时输出 00,在 z>0z>0 时原样输出,只在原点有一个折点。在隐藏层放两个单元:

h1=ReLU⁡(x−22),h2=ReLU⁡(20−x)h_1=\operatorname{ReLU}(x-22), \qquad h_2=\operatorname{ReLU}(20-x)

h1h_1 是温度高出 22 度的部分,h2h_2 是温度低于 20 度的部分。输出层取

z=2−h1−h2z=2-h_1-h_2
xxh1h_1h2h_2zz预测
16160044−2-2否
1818002200是(边界)
2121000022是
2424220000是(边界)
30308800−6-6否

分段看:x≥22x\ge22 时 z=24−xz=24-x;x≤20x\le20 时 z=x−18z=x-18;中间 z=2z=2。所以 z≥0z\ge0 恰好对应 18≤x≤2418\le x\le24。

两次弯折围出一段区间
x 21.0
分数 z 2.000 h₁ = 高出 22 多少 0.000 h₂ = 低于 20 多少 0.000
21.0
h₁ 只在 x > 22 时打开,h₂ 只在 x < 20 时打开。分数 z = 2 − h₁ − h₂ 在 18 ℃ 和 24 ℃ 各穿过 0 一次,正好围出目标区间。

关键在 ReLU 有一侧输出恒为 00。温度偏低时 h1=0h_1=0,只有 h2h_2 影响结果;偏高时反过来。如果把两个 ReLU 换成线性单元,它们的和仍是 xx 的线性函数,这张表就算不出来。

和 x2x^2 对照

(h1,h2)(h_1, h_2) 和 x2x^2 的作用相同:原始输入上线性不可分的问题,在新坐标下变成线性可分,输出层只需要做加权求和。

手工特征与 ReLU 特征
x 21.0
x² 特征:−(x−18)(x−24)/4.5 2.000 ReLU 特征:2 − h₁ − h₂ 2.000
21.0
两条分数都在 18 ℃ 和 24 ℃ 穿过 0,判出的区间相同。二次曲线的形状是人事先选定的;两个折点的位置由参数决定,可以被训练调整。

区别在于形状由谁决定。x2x^2 的形状在训练前就固定了。ReLU⁡(wx+b)\operatorname{ReLU}(wx+b) 的折点在 x=−b/wx=-b/w,打开的方向由 ww 的符号决定,而 ww、bb 都是参数。上面的 22 和 20 是手写的;在真实训练中,梯度下降会调整它们,数据决定折点落在哪里。

这就是隐藏层所说的「学习表示」:特征的形式(分段线性)是结构给定的,具体的特征由数据训练出来。

单元越多,折点越多

一个 ReLU 单元贡献一个折点。kk 个单元的线性组合是最多 k+1k+1 段的分段线性函数,段数足够多时,可以逼近区间上的任意连续函数。Cybenko(1989)和 Hornik 等人(1989)对 Sigmoid 类激活证明了这一点,通常称为万能逼近定理,后来的结果也覆盖了 ReLU。

这个定理只说明这样的参数存在。它没有说明需要多少单元,也没有说明梯度下降能不能找到这些参数。前一个问题是下一篇的主题,后一个问题要等到反向传播。

为什么常用这几条曲线

只看表达能力,任何非线性函数都可以,阶跃函数也行。实际选择由训练决定:梯度下降需要激活函数的导数,而且导数不能太小。上一篇的参数更新里,梯度要乘上 Sigmoid 的导数;多层网络中,每经过一层就要乘一次该层激活函数的导数。

阶跃函数:没有梯度

感知机使用的阶跃函数在 z≥0z\ge0 时输出 11,否则输出 00。它在 z≠0z\ne0 处导数为 00,在 z=0z=0 处不可导。参数稍微改变,输出通常完全不变,梯度下降没法用。

从硬门槛到光滑门槛
z 0.0
Sigmoid 0.500 阶跃函数 1.000
0.0
阶跃函数在门槛两侧分别是 0 和 1,中间没有过渡;Sigmoid 把同一道门槛摊成连续斜坡,输出可以随输入一点点变化。

1986 年 Rumelhart、Hinton 和 Williams 的 Learning representations by back-propagating errors 把阈值单元换成 Sigmoid,误差才能逐层传回隐藏层,隐藏层也才开始学习表示。

Sigmoid:导数最大只有 0.25

Sigmoid 的导数是 σ′(z)=σ(z)(1−σ(z))\sigma'(z)=\sigma(z)(1-\sigma(z)),在 z=0z=0 处取到最大值 0.250.25,∣z∣|z| 较大时接近 00。

曲线中间最敏感
z 0.0 →0.250
0.0
导数 σ′(z)=σ(z)(1−σ(z)) 在 z=0 时达到最大值 0.25;两端饱和时,局部梯度接近 0。

一层时影响不大。十层网络的梯度要连乘十个这样的因子,在权重不大的情况下,前面几层的梯度会变得非常小,几乎不更新。这称为梯度消失。Glorot 与 Bengio(2010)的实验分析了 Sigmoid 在深层网络中的这个问题。

ReLU:正半轴导数为 1

ReLU 的导数在 z>0z>0 时为 11,在 z<0z<0 时为 00。只要单元处于打开状态,梯度经过它时不会缩小。Glorot、Bordes 与 Bengio(2011)的 Deep Sparse Rectifier Neural Networks 之后,ReLU 成为深层网络隐藏层的默认选择。它的计算也很便宜,只需要一次比较。

代价在负半轴:如果某个单元对所有训练样本都有 z<0z<0,它的梯度始终为 00,参数不再更新。这种现象叫 dying ReLU。

Tanh 和 GELU

另外两个常见的选择:

  • Tanh:tanh⁡(z)=2σ(2z)−1\tanh(z)=2\sigma(2z)-1,输出范围 (−1,1)(-1,1),以 00 为中心,原点处导数为 11。两端依然饱和,常见于 RNN 等需要有界状态的结构。
  • GELU:GELU⁡(z)=z Φ(z)\operatorname{GELU}(z)=z\,\Phi(z),Φ\Phi 是标准正态分布的 CDF(Hendrycks & Gimpel, 2016)。可以把它看作平滑版的 ReLU,在 00 附近没有硬折点,负输入会保留一小部分。BERT、GPT-2 等 Transformer 模型使用它。
四种常见激活函数
z 0.0
Sigmoid 0.500 Tanh 0.000 ReLU 0.000 GELU 0.000
0.0
四条曲线都在原点附近改变斜率,但对负输入、输出范围和两端是否压平作出了不同选择。注意它们并不占用同一段纵轴。

实际选择时,普通 MLP 和 CNN 先用 ReLU,Transformer 沿用 GELU,一般就够了。

运行对照实验

下面的代码不做训练,只把三种模型在整个温度范围上算一遍:原始输入上的逻辑回归、加入 x2x^2 的逻辑回归、两个 ReLU 单元。点击「运行」后 Python 在浏览器里执行,第一次需要下载 Pyodide、NumPy 与 Matplotlib。

Output

运行代码后,文本与图表会显示在这里。

后两种模型的判断结果相同。区别只在于新特征是人写的,还是由参数决定、可以训练的。

小结与下一篇

这一篇的论证是:

  1. 逻辑回归在原始输入上只能切一刀,舒适区间需要两刀。
  2. 传统解法是人工加 x2x^2 这样的非线性特征,但高维输入下很难手工设计。
  3. 用线性层来学特征会失败,因为线性层会合并成一层。
  4. 在线性层之后加入激活函数,隐藏层就能学出非线性特征;ReLU 的每个单元提供一个位置可训练的折点。
  5. 激活函数的具体形状由训练决定:需要导数,并且导数不能太快趋向 00。

这里只用到一层隐藏层。根据万能逼近定理,一层足够宽就能逼近任意连续函数,那为什么实际网络要做得很深?下一篇讨论深度。