一、BP神经网络
1.1 为何要引出BP神经网络
- 逻辑回归对于如今越来越复杂的任务效果越来越差,主要是难以处理线性不可分的数据,LR处理线性不可分,一般是特征变换和特征组合,将低维空间线性不可分的数据在高维空间中线性可分
- 改良方式有几种,本质上都是对原始输入特征做文章。但都是针对特定场景设计。如果实际场景中特征组合在设计之外,模型无能为力
- MLP网络中,每个节点都接收前一层所有节点的信号的加权和,累加后进行激活,再传入下一层的节点。这个过程和动物神经元细胞传递信号的过程类似,所以叫神经网络,各节点称为神经元。
- 每层神经元个数称为神经网络的宽度,层数称为神经网络的深度。所以多层神经网络称为深度神经网络DNN。
- 神经元数据过多会造成过拟合和运算量过大。层中神经元过多,相当于该层转换后的特征维度过高,会造成维数灾难。
- DNN仍然使用交叉熵损失函数。
1.3 神经网络的多分类
- 机器学习模型相对简单,参数较少。可以通过训练N个二分类模型来完成多分类。而深度学习中,模型参数较多,训练多个模型不实际。而且多分类任务的前层特征变换是一致的,没必要训练多个模型。一般是输出层采用softmax函数来完成。
- softmax做多分类只适用于类别互斥且和为1的情况。如果和不为1,可以加一个其它类。不互斥时不能保证分母能归一化。
1.4 二分类使用softmax还是sigmoid好?
- softmax等于分别学习w1和w2,而sigmoid等于学这两个的差值就行了。sigmoid是softmax在二分类上的特例。二分类时sigmoid更好。因为我们只关注w1和w2的差值,但是不关心其具体的值。
- softmax的运算量很大,因为要考虑别的概率值。一般只在神经网络最后一层用(多分类时)。中间层神经元各算各的,不需要考虑别的w数值,所以中间层不需要softmax函数。
- softmax函数分子:通过指数函数,将实数输出映射到零到正无穷。softmax函数分母:将所有结果相加,进行归一化。
- softmax和sigmoid一样有饱和区,x变化几乎不会引起softmax输出的变化,而且饱和区导数几乎为0,无法有效学习。所以需要合适的初始化,控制前层输出的值域。
- 两个函数的代码实现如下图
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1.0/(1+np.exp(-x))
sigmoid_inputs = np.arange(-10,10)
sigmoid_outputs=sigmoid(sigmoid_inputs)
print("Sigmoid Function Input :: {}".format(sigmoid_inputs))
print("Sigmoid Function Output :: {}".format(sigmoid_outputs))
plt.plot(sigmoid_inputs,sigmoid_outputs)
plt.xlabel("Sigmoid Inputs")
plt.ylabel("Sigmoid Outputs")
plt.show()def softmax(x):
orig_shape=x.shape
if len(x.shape)>1:
#Matrix
#shift max whithin each row
constant_shift=np.max(x,axis=1).reshape(1,-1)
x-=constant_shift
x=np.exp(x)
normlize=np.sum(x,axis=1).reshape(1,-1)
x/=normlize
else:
#vector
constant_shift=np.max(x)
x-=constant_shift
x=np.exp(x)
normlize=np.sum(x)
x/=normlize
assert x.shape==orig_shape
return x
softmax_inputs = np.arange(-10,10)
softmax_outputs=softmax(softmax_inputs)
print("Sigmoid Function Input :: {}".format(softmax_inputs))
print("Sigmoid Function Output :: {}".format(softmax_outputs))
# 画图像
plt.plot(softmax_inputs,softmax_outputs)
plt.xlabel("Softmax Inputs")
plt.ylabel("Softmax Outputs")
plt.show()1.5 梯度下降和链式求导
1.6度量学习
二、矩阵求导术
线性代数参考《线性代数一(基本概念)》2.1 标量对向量求导
例如标量z对向量X求导,就是看X各元素变化对z的影响。所以结果是z对X各元素求导,结果也是一个同尺寸向量。2.2 向量对向量求导
列向量对行向量求导,结果是一个矩阵,方便后面进行链式求导中的导数连乘。</font>(向量既可以写成列的形式,也可以写成行的形式。列向量可以直接对列向量求导,但是维数太多不便于操作,而且没法进行连乘,一般没人这么干。) - m×1维列向量$y$对n×1维列向量$x$求导,等于$y$的每个元素$y_{i}$分别对$x$求导,得到m×1维导数。而导数每个元素都是标量对向量求导,结果是n×1维列向量。所以最终结果是mn×1维的列向量。行向量亦然。
- 神经网络中一般都用列向量。,如果列向量$y$对列向量$x$求导,结果太长不便于计算。一般是$y$对$x$的转置求导,即$\frac{\partial y}{\partial x^{T}}$,最后结果是一个m×n的矩阵。
2.3 标量对矩阵的矩阵
向量可以看成某一维为1的矩阵(行为1或列为1),同理,标量对m×n维矩阵求导,是对矩阵中每个元素求导,结果也是一个m×n维的结果。2.4 向量求导及链式法则
对于
展开之后有:所以$\frac{\partial x{3}}{\partial x{2}^{T}}=W$,$\frac{\partial x{3}}{\partial W}=x{2}^{T}$ - 即之前列向量对列向量转置求导,推出结果是mn的矩阵,但是各元素的具体值不知道。这里直接求出,具体值为矩阵W
- 求导要一直盯着尺寸看。
公式1——标量对向量求导链式法则:
向量$x{1}…x{n}$为神经网络各层的输入,最终输出结果是标量z。存在依赖关系:$x{1}\rightarrow x{2}\rightarrow x{3}…\rightarrow x{n}\rightarrow z$,则有:
- 假如$x{n}、x{n-1}、x_{n-2}$分别是m、n、k维的列向量,则上式右边第一项分别是m×n和n×k的矩阵,这两个矩阵才有相乘的可能,结果是m×k的矩阵。所以必须是列向量对列向量的转置求导
- 假如$x{1}$是h维列向量,则括号内最终结果是m×h维矩阵,$\frac{\partial z}{\partial x{n}}$结果是m维列向量,无法直接相乘,所以括号内的矩阵必须转置。
公式2——标量对矩阵求导链式法则:
W为矩阵,$x$和$y$是向量,有$y=Wx$。且标量$z=f(y)$,则:
参照上面写的:$\frac{\partial x{3}}{\partial W}=x{2}^{T}$
2.5 BP反向传播
Loss对任意层$W^k$求导有:
对于一个L层的神经网络,j表示L层第j维数据(也就是第j个神经元)
第一项$\frac{\partial Loss}{\partial d{j}^{L}}=y{j}’-y_{j}$,是一个标量。(即loss对最后一层某个神经元导数为一个标量,推导见P119)
又因为:
后一项是标量对矩阵求导,根据公式2有:
将$d_{j}^{L}$看做是由向量$d^{L}$映射成标量
上式右边第一项是标量对向量求导,根据公式1有:
依次求三项导数有:
将以上结果联合起来就是:
第m层激活函数得导数有:
展开后为:
其实是省去了上标层数m,其中第m层共有M个神经元。相当于向量d数乘之后得到向量a,每个元素按位操作。$a{1}=f(d{1}+w{0}^{1})$,$a{1}$只和$d_{1}$有关,和向量d其它元素无关,对d其它分量结果为0。
列向量对行向量求导,结果是一个矩阵。所以有:
- sigmoid函数:$sigmoid(d)=\frac{1}{1+e^{-d}}$导数为:
$f’=f(1-f)=0.25-(f-0.5)^2$,后一项非负,当f=0.5时有最大值0.25。所以其值域为(0,0.25) softmax函数及其导数,参考《Softmax函数及其导数》
relu函数:$f(x)=max(0,x)$。其导数w为:
- Tanh函数导数为:
Tanh值域(-1,1),导数值域(0,1)。三、神经网络调优
海量的数据和强大的算力为深度学习的发展提供了条件。但是也带来一些新的问题:
- 零均值输出:例如tanh,零均值输出,有正有负。W各个维度更新方向可以不同,避免单向更新学习慢的问题(各维度只能同增或者同减,走Z字路线。比如loss极小值在左下方,只能先左后下)这一点softmax、sigmoid、relu都不满足,都是非负的
- 适当的线性。激活函数对输入的变化不宜过于激烈,否则输入轻微变化造成输出剧烈变化,稳定性不好。softmax、sigmoid、relu、tanh都有近似线性的区域可以满足
- 导数不宜过大或过小,否则梯度消失或者爆炸。这也是relu成为神经网络首选的原因。tanh导数是0-1,比sigmoid好一点。
- 导数的单调性。避免导数有正有负造成学习震荡,例如三角函数
- 有值域的控制。避免多次激活后输出太大。例如$y=x^2$
- 没有超参数,计算简单。Maxout、PReLu函数有超参数,设置本身依赖经验。而且ReLu计算足够简单
3.2 Relu激活函数及其变体
参考《从ReLU到GELU,一文概览神经网络的激活函数》
线性整流函数Relu:
- d≥0时,f(d)=d,导数为1,不存在梯度消失或者梯度爆炸,也不存在饱和区
- d<0时,f(d)=0,梯度为0 ,神经元死亡
梯度消失和激活函数饱和困扰业界多年,直到RELU的出现。
神经元真死和假死:
真死:
- 无论w和x各维度如何变化,恒有$d{j}^{(l)}=w{j}^{(l)}a^{(l-1)}$≤0,神经元死亡。(l表示神经网络层数,j表示某层中神经元节点j)。$a^{(l-1)}≥0$为上一层神经网络的输出。
- 所以是$w{j}^{(l)}$各元素都是很大的负数时,恒有d≤0。这是参数初始化错误或者lr过大导致权重$w{j}^{(l)}$更新过大造成的。因此用Relu做激活函数,lr不宜过大,权重初始化要合理
假死:饱和形式之一
- 恰巧造成$w{j}^{(l)}a{j}^{(l-1)}≤0$,重新训练时有可能会恢复正常。神经元大多数时是假死。
- 适当假死可以提升神经网络效果:
- 如果没有神经元假死,$a_{j}^{(l-1)}≥0$,f(d)=d,Relu作为激活函数就是纯线性的,失去意义。
- 假死神经元就是对某些输入特征不进行输出,达到特征选择的作用(门控决策)
LRelu: d<0时,f(d)=k,k为超参数,在0-1之间
PRelu: d<0时,f(d)=k,k是可学习的参数。
Maxout:$w{j}^{(l)}(1)$到$w{j}^{(l)}(n)$有多个参数,分别和上一层输出相乘,即每个神经元节点j有多个输入,最终输出选最大值。(P133-134)
3.3 高斯误差线性单元激活函数gelu
3.3.1 GELU概述
BERT、RoBERTa、ALBERT 等目前业内顶尖的 NLP 模型都使用了这种激活函数。另外,在 OpenAI 声名远播的无监督预训练模型 GPT-2 中,研究人员在所有编码器模块中都使用了 GELU 激活函数。在计算机视觉、自然语言处理和自动语音识别等任务上,使用 GELU 激活函数比使用ReLU 或 ELU 效果更好。
随着网络深度的不断增加,利用 Sigmoid 激活函数来训练被证实不如非平滑、低概率性的 ReLU 有效(Nair & Hinton, 2010),因为 ReLU 基于输入信号做出门控决策。
深度学习中为了解决过拟合,会随机正则化(如在隐层中加入噪声)或采用 dropout 机制。这两个选择是和激活函数割裂的。非线性和 dropout 共同决定了神经元的输出,而随机正则化在执行时与输入无关。
由此提出高斯误差线性单元(Gaussian Error Linear Unit,GELU)。GELU 与随机正则化有关,因为它是自适应 Dropout 的修正预期(Ba & Frey, 2013)</font >。这表明神经元输出的概率性更高。
3.3.2 GELU数学表示
Dropout、ReLU 等机制都希望将「不重要」的激活信息规整为零。即对于输入的值,我们根据它的情况乘上 1 或 0。</font >或者说,对输入x乘上一个伯努利分布 Bernoulli(Φ(x)),其中Φ(x) = P(X ≤ x)。(x服从于标准正态分布 N(0, 1))
对于一部分Φ(x),它直接乘以输入 x,而对于另一部分 (1 − Φ(x)),它们需要归零。随着 x 的降低,它被归零的概率会升高。对于 ReLU 来说,这个界限就是 0。
我们经常希望神经网络具有确定性决策,这种想法催生了 GELU 激活函数的诞生。具体来说可以表示为:Φ(x) × Ix + (1 − Φ(x)) × 0x = xΦ(x)。可以理解为,不太严格地说,上面这个表达式可以按当前输入 x 比其它输入大多少来缩放 x。</font >
高斯概率分布函数通常根据损失函数计算,因此研究者定义高斯误差线性单元(GELU)为:
上面这个函数是无法直接计算的,因此可以通过另外的方法来逼近这样的激活函数,研究者得出来的表达式为:
或:
其中 σ() 是标准的 sigmoid 函数
当 x 大于 0 时,输出为 x;但 x=0 到 x=1 的区间除外,这时曲线更偏向于 y 轴。
没能找到该函数的导数,所以我使用了 WolframAlpha 来微分这个函数。结果如下:
微分的GELU函数:
GELU 的近似实现方式有两种,借助 tanh() 和借助σ()。我们在 GPT-2 的官方代码中也发现,更多研究者采用了 tanh() 的实现方式尽管它看起来要比 xσ(1.702x) 复杂很多。
# GPT-2 的 GELU 实现 |
3.4 Xavier权重初始化
参考《苏神文章解析(6篇)》
《浅谈Transformer的初始化、参数化与标准化》
《从几何视角来理解模型参数的初始化策略》
- 如果两个神经元参数完全相同,则梯度也相同,更新幅度一致,最后输出也相同。看上去是两个神经元,但其实相当于就一个,是互相冗余的。所以 初始化W矩阵时要破坏其对称性,独立均匀分布的随机初始化就可以做到。(正态分布采样容易集中在均值附近,可能造成权重对称)均匀分布有两个参数$\mu/ \sigma$ ,均值和方差。
- 真实场景中,特征都是客观事实,一般都是>0。如果W矩阵各元素都是>0,则relu函数不起作用。都<0,神经元都死了。所以 W矩阵各元素需要有正有负,所以一般均匀分布选择均值为0。$\sigma$ 越大,w采样到大值的可能性越大,即$W{i,j}^{l}\propto \sigma {i,j}^{l}$
- 从均值为0、方差为1/m的随机分布中独立重复采样,这就是Xavier初始化(无激活函数时)relu做激活函数时,方差为2/m
- NTK参数化:均值为0、方差为1的随机分布来初始化,但是将输出结果除以$\sqrt{m}$。利用NTK参数化后,所有参数都可以用方差为1的分布初始化,这意味着每个参数的量级大致都是相同的O(1)级别,让我们更平等地处理每一个参数,于是我们可以设置较大的学习率。
- 对于$d{j}^{(l)}=\sum{i=1}^{M(l-1)}w{i,j}^{(l)}a{i}^{(l-1)}$,M(l-1)表示上一层神经元个数。$d{j}^{(l)}$不宜过大,$W{i,j}^{l}\propto \frac{1}{M^{l-1}},\sigma _{i,j}^{l}\propto \frac{1}{M^{l-1}}$</font >。否则relu前向传播时,输出会层层放大而溢出。softmax、sigmoid、tanh进入饱和区,导数基本为0。
其它推导见P137