在深度学习领域,transformer可以说是在传统的神经网络的基础上发展而来,着重解决传统神经网络长距离关联、顺序处理、模型表达能力等问题。
在学习transformer之前,我想,有必要先对传统的神经网络做简要的了解。
一、神经网络基本结构
输入层: 神经网络的第一层,直接接收原始输入数据,传递到隐藏层。
隐藏层: 位于输入层和输出层之间,可以有一个或多个隐藏层,多层隐藏层逐步提取更高层次的抽象特征。
输出层: 最后一层,负责生成最终的预测结果。
下面以CNN(卷积神经网络)为例,大致说明下各层间的关系:
1.1、从原始数据到输入层
假设一张经过灰度处理的数字6、大小N * N个像素的图片,每个像素点的取值经过归一化处理(取值0-1),将N * N个像素点展开成神经网络输入层的每个神经元。
1.2、隐藏层特征提取
- 当输入层的各个节点数据传输到第一个隐藏层时,提取多个最基础的形状特征的神经元,
- 这些神经元在传输给第一个隐藏层后,第二个隐藏层提取更高层次的形状或物体结构。也就是更高层次的特征。
- 最后,第二个隐藏层的神经元通过权重计算,给每个输出层的特征节点赋值,取值最高的特征节点,就是输入图片最终匹配的预定特征。
那么,每层之间的数据传递到底是怎样的?我们以输入层到第一个隐藏层为例:
- 设n = N*N ,那么输入层就有a0-an 个神经元,每个连线代表输入层每个输入对下一层某个神经元的权重,n个输入就有n个权重(全连接的情况,全连接就是每个输入都对下一层的每个节点有影响)。
- 那么下一层的单个神经元就等于上一层所有神经元的加权和,即 : a0ω0 + a1ω1 + ... +an*ωn
- 在识别特征过程中,可能需要对不明显的特征进行过滤,这也就要求加权和需要大于某个数,这个值我们称之为偏置量,在线性表达上,偏置可以是模型更好拟合数据,可以表示不仅仅穿过原点的线性关系。 (a0ω0 + a1ω1 + ... +an*ωn ) +(-b)
- 当然,有时为了优化模型的表达,比如提高收敛速度、避免梯度消失,需要将下一层神经元的取值限制在有界范围,这时候需要通过激活函数做一些非线性的操作,比如使用SIGMOD函数,将值域限定在(0,1): σ((a0ω0 + a1ω1 + ... +an*ωn ) +(-b)) f = σ(...)为激活函数
上面仅仅是表示了第二层的一个神经元的计算,那第二层所有的神经元该如何表达:
加上偏置和激活函数表示如下:
后面隐藏层到输出层,结构也类似。通过线性表达则是:
y = W* a + b ; 其中y 为输出矩阵,W为权重矩阵,a 为输入矩阵,b为偏置矩阵
那么W和b 如何而来,当然是通过大量的数据训练而来,后面我们将逐步学习如何通过损失函数训练,
二、损失函数
以最后一个隐藏层到输出层为例:假定这个过程的权重矩阵初始化为W,偏置初始化为b ,预测值为y′ 。那么真实值y 与预测值之间的误差就可以如下表示(以均方差为例):
假设10个样本:
更一般的表示: MSE= 1 n ∑ i = 1 n ( y ′ i − y i ) 2 \frac{1}{n} ∑_{i=1}^{n} (y′_i - y_i )^2 n1∑i=1n(y′i−yi)2 或 L = 1 n ( y ′ − y ) 2 \frac{1}{n}(y′ - y)^2 n1(y′−y)2
n为样本数量
除了均方差损失函数,常用的损失函数还有交叉熵损失函数,通常用于多分类概率分布:
二分类可以看做多分类的一种特例:
三、梯度下降法与反向传播
3.1 利用损失函数求梯度
得到损失函数后,我们得根据损失函数反向推导哪个权重影响比较大,权重矩阵就重点关注影响损失函数较大的权重:
那回到损失函数MSE= 1 n ∑ i = 1 n ( y ′ i − y i ) 2 \frac{1}{n} ∑_{i=1}^{n} (y′_i - y_i )^2 n1∑i=1n(y′i−yi)2 ,则是对其关于W和b求偏导以获取梯度,梯度越大,斜率越大,对误差影响越大。
由于 y′ = W* a + b ,因此关于W和b求偏导,是个复合函数求偏导:
关于W的梯度: ∂ M S E ∂ W = ∂ M S E ∂ y ′ ∗ ∂ y ′ ∂ W = 2 n ∑ i = 1 n ( y ′ i − y i ) ∗ a T \frac{\partial MSE}{\partial W} =\frac{\partial MSE}{\partial y′} * \frac{\partial y′}{\partial W} = \frac{2}{n} ∑_{i=1}^{n} (y′_i - y_i ) * a^T ∂W∂MSE=∂y′∂MSE∗∂W∂y′=n2i=1∑n(y′i−yi)∗aT
对于单个y'(即单行W与单列a的内积,忽略偏置) 基于W求偏导,得到的应该是1行n列的矩阵,由于a是n行一列的矩阵,因此需要对a做转置处理,例如:
对 ω 0 求导: ∂ y ′ ∂ ω 0 = ∂ a 0 ∗ ω 0 + a 1 ∗ ω 1 + . . . . . . + a n ∗ ω n ω 0 = a 0 对ω0求导:\frac{\partial y′}{\partial ω0} =\frac{\partial a0*ω0 + a1*ω1 + ...... +an*ωn}{ω0} = a0 对ω0求导:∂ω0∂y′=ω0∂a0∗ω0+a1∗ω1+......+an∗ωn=a0
对 ω 1 求导: ∂ y ′ ∂ ω 1 = ∂ a 0 ∗ ω 0 + a 1 ∗ ω 1 + . . . . . . + a n ∗ ω n ω 1 = a 1 对ω1求导:\frac{\partial y′}{\partial ω1} =\frac{\partial a0*ω0 + a1*ω1 + ...... +an*ωn}{ω1} = a1 对ω1求导:∂ω1∂y′=ω1∂a0∗ω0+a1∗ω1+......+an∗ωn=a1
对 ω n 求导: ∂ y ′ ∂ ω n = ∂ a 0 ∗ ω 0 + a 1 ∗ ω 1 + . . . . . . + a n ∗ ω n ω n = a n 对ωn求导:\frac{\partial y′}{\partial ωn} =\frac{\partial a0*ω0 + a1*ω1 + ...... +an*ωn}{ωn} = an 对ωn求导:∂ωn∂y′=ωn∂a0∗ω0+a1∗ω1+......+an∗ωn=an
因此 ∂ y ′ ∂ W = [ a 0 , a 1 , . . . , a n ] = a T 因此\frac{\partial y′}{\partial W} = [ a0,a1,...,an] = a^T 因此∂W∂y′=[a0,a1,...,an]=aT多行W与多列a以此类推。
关于b的梯度:
∂ M S E ∂ b = 2 n ∑ i = 1 n ( y ′ i − y i ) \frac{\partial MSE}{\partial b} = \frac{2}{n} ∑_{i=1}^{n} (y′_i - y_i ) ∂b∂MSE=n2i=1∑n(y′i−yi)
因此根据求解的梯度,反向传播,使用梯度下降法修改权重与偏置矩阵:
权重梯度调整: W = W − η ∂ M S E ∂ W 权重梯度调整:W = W - η \frac{\partial MSE}{\partial W} 权重梯度调整:W=W−η∂W∂MSE
偏置梯度调整: b = b − η ∂ M S E ∂ b 偏置梯度调整:b = b - η \frac{\partial MSE}{\partial b} 偏置梯度调整:b=b−η∂b∂MSEη表示学习率(步长),η越大,收敛速度越快,但是越容易错过最佳值,η越小收敛速度越慢。
通过多轮训练优化,最终得到能够准确预测的权重矩阵。
看公式可能很头疼,码农肯定看代码更容易理解:
python
import numpy as np
# 定义均方差损失函数
def mean_squared_error(y_true, y_pred):
return np.mean((y_true - y_pred) ** 2)
# 定义均方差损失函数的梯度
def mse_gradient(y_true, y_pred, X):
n = len(y_true)
gradient_w = 2 * np.dot(X.T, (y_true - y_pred)) / n
gradient_b = 2 * np.mean(y_true - y_pred)
return gradient_w, gradient_b
# 示例数据
X = np.array([[1, 2], [2, 3], [3, 4], [4, 5]]) # 输入特征
y_true = np.array([2, 3, 4, 5]) # 真实值
w = np.array([0.5, 0.5]) # 初始权重
b = 0.0 # 初始偏置
# 计算预测值
y_pred = np.dot(X, w) + b
# 计算均方差损失
mse = mean_squared_error(y_true, y_pred)
print("Mean Squared loss:", mse)
# 计算梯度
gradient_w, gradient_b = mse_gradient(y_true, y_pred, X)
print("Gradient of weights:", gradient_w)
print("Gradient of bias:", gradient_b)
3.2 交叉熵损失函数
均方差相比比较好理解,但是对于概率等较小的预测值,反应不灵敏,有时不能有效反应误差,如何对较小的值如(0,1)更好的做损失分析呢,聪明的你肯定可以想到用log(x):
但从取现观察看,在x∈(0,1)时,x越小,斜率的绝对值越大,说明log(x)对越小的值越敏感,从概率考虑,也就说,概率越小的事件,越不该发生,发生了越应该被注意到,越应该调整它的权重,减少它对误差的影响。
交叉熵损失函数,就是应用了这一原理。交叉熵损失函数应用在softmax()处理后形成了概率分布的输出层,softmax处理后的输出即为预测的概率分布。这里不对交叉熵损失函数求梯度做进一步学习。
3.3优化器
在我们实际的开发过程中,可以直接使用torch提供的优化器完成反向传播梯度优化。
常见的优化器有如下SGD(随机梯度)、动量(Momentum)、RMSProp、Adam等几种,极大方便模型开发训练工作,后续有机会再认真学习优化器的原理。再第五章的示例代码中,也将简单应用优化器完成模型训练。
四、激活函数
激活函数主要非线性的操作,用来对神经元的输出限制值域,以求模型有更好的表现力,能够更好学习拟合复杂的线性关系,解决梯度消失,平滑分布,矩阵稀疏等问题。
以下是常见的一些激活函数和使用场景:
4.1 SIGMOD函数
σ(x) = 1 1 + e − x \frac{1}{1+e^{-x}} 1+e−x1
特点: 输出范围(0,1),可用于表示二分类概率,也可用于平滑梯度,但在两端附近的值梯度趋零,易导致梯度消失,因此深层网络通常采用ReLU函数。
4.2 ReLU函数
f(x)=max(0,x)
特点: 输出范围在 [0, ∞) 之间,对于正输入值保持不变,有助于缓解梯度下降;而对于负输入值输出为0。可以产生稀疏激活,对于负输入值,输出为0,减少模型的复杂度和过拟合风险,但也会导致输出为0的神经元,后续不会再被激活。因此可引入ReLU函数的变体,如:
f(x)=max(αx,x); α通常取非常小的正数,如0.01
当然还有如Tanh 函数、Softmax 函数、ELU 函数等激活函数,这里就不一一介绍了。Softmax 函数主要用来生成概率分布,在自注意力机制中也有用到,到时再单独做简要说明。