手撕经典算法 #4 经典函数篇
本文最后更新于:2025年3月19日 晚上
本文对深度学习中经典的函数进行了简单的实现和注释。包括:
- 损失函数(MSE、CE、BCE、KL、Focal)
- 激活函数(Sigmoid、Tanh、ReLU、Leaky ReLU、ELU、Swish、GeLU、SwiGLU、Softmax)
- 指标计算(PPL、ROUGE、BLEU)
损失函数
MSE Loss
均方误差(Mean Squared Error,MSE)衡量预测值与真实值的平方差均值,是回归任务中最常用的损失函数:
\[ L = \frac{1}{N}\sum_{i=1}^N (y_i - \hat{y}_i)^2 \]
其中 \(y_i\) 为真实值,\(\hat{y}_i\) 为预测值。其梯度计算为 \(\frac{\partial L}{\partial \hat{y}_i} = \frac{2}{n}(\hat{y}_i - y_i)\),具有凸函数的良好优化特性,可导且处处平滑,适合梯度下降。
代码如下:
1 |
|
CE Loss
交叉熵(Cross Entropy)衡量两个概率分布间的差异,常用于多分类任务。给定真实分布 \(P\) 和预测分布 \(Q\):
\[ H(P, Q) = -\sum_{i=1}^N P(x_i) \log Q(x_i) \]
在分类任务中,真实标签常采用 one-hot 编码,公式简化为:
\[ L = -\frac{1}{N}\sum_{i=1}^N \sum_{i=1}^C y_i \log \hat{y}_i \]
其中 \(C\) 为类别总数,\(\hat{y}_i\) 需经过 Softmax 归一化。
代码如下:
1 |
|
BCE Loss
二元交叉熵(Binary Cross Entropy)处理的是二分类问题,其归一化的方式从 Softmax 替换为 Sigmoid,并且每个类别的概率独立计算(不像交叉熵仅计算真实类别的损失): \[ L = -\frac{1}{N}\sum_{i=1}^N \left[ y_i \cdot \log(\sigma(x_i)) + (1-y_i) \cdot \log(1-\sigma(x_i)) \right] \] 此外,BCE 也可以用于多分类多标签任务,此时需要将每个类别看作为 0 或 1 的二分类问题。
代码如下:
1 |
|
KL 散度
KL 散度(Kullback-Leibler Divergence)衡量两个概率分布 \(P\) 和 \(Q\) 的差异程度:
\[ D_{KL}(P \parallel Q) = \sum_{i=1}^N P(x_i) \log \frac{P(x_i)}{Q(x_i)} \]
其性质包括:
- 非对称性:\(D_{KL}(P \parallel Q) \neq D_{KL}(Q \parallel P)\)
- 非负性:\(D_{KL} \geq 0\),当且仅当 P=Q 时等于 0
- 与交叉熵的关系:\(D_{KL}(P \parallel Q) = H(P, Q) - H(P)\)
代码如下:
1 |
|
Focal 损失
通过调节难易样本权重解决数据倾斜问题,适用于长尾分布场景的二分类问题:
\[ FL = -\alpha_t (1-p_t)^\gamma \log(p_t) \]
其中:
\(p_t\) 是模型对真实类别的预测概率: \[ p_t = \begin{cases} p & \text{正样本} \\ 1-p & \text{负样本} \end{cases} \]
\(\alpha \in [0,1]\):类别平衡因子,通常为稀有类别分配更高权重(如 \(\alpha=0.25\))。
\(\gamma \geq 0\): 困难样本聚焦参数,调整难易样本的权重比例(通常 $ $ )。
代码如下:
1 |
|
激活函数
Sigmoid
输出区间 (0,1),符合概率分布特性,常用于二分类输出层。表达式为: \[ \sigma(x) = \frac{1}{1+e^{-x}} \] 导数: \[ \sigma'(x) = \sigma(x)(1-\sigma(x)) \] 缺点:
- 输入较大或较小时候梯度接近于 0,容易导致梯度消失(且导数最大值为 0.25,更新效率不高);
- 函数输出不是以 0 为中心的,梯度可能就会向特定方向移动,从而降低权重更新的效率;
- 执行指数运算,计算机运行得较慢,比较消耗计算资源。
代码如下:
1 |
|
Tanh
输出区间 \((-1,1)\),以零为重心,缓解梯度偏移问题,表达式为: \[ \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} \] 导数: \[ \tanh'(x) = 1 - \tanh^2(x) \] 缺点:
- 仍然存在梯度饱和的问题:但 \(x\) 进入饱和区(saturation region)时,其导数值趋近于零,最终无法有效更新网络参数;
- 依然进行的是指数运算,比较消耗计算资源。
代码如下:
1 |
|
ReLU
表达式为: \[ \text{ReLU}(x) = \max(0, x) \] 导数: \[ \text{ReLU}'(x) = \begin{cases} 1 & x > 0 \\ 0 & x \leq 0 \end{cases} \] 优点:
- ReLU 解决了梯度消失的问题,当输入值为正时,神经元不会饱和(梯度始终为 1);
- 由于 ReLU 线性、非饱和的性质,在 SGD 中能够快速收敛;
- 计算复杂度低,不需要进行指数运算。
缺点:
- Dead ReLU 问题:负区间梯度归零,不再对任何数据有所响应,导致相应参数永远不会被更新;
- 与 Sigmoid 一样,其输出不是以 0 为中心的。
代码如下:
1 |
|
Leaky ReLU
缓解神经元死亡问题(负区间保留小梯度 \(\alpha\),常见取值为 \(0.01-0.3\)),表达式为: \[ \text{LeakyReLU}(x) = \begin{cases} x & x > 0 \\ \alpha x & x \leq 0 \end{cases} \quad (\alpha \in (0,1)) \]
导数: \[ \text{LeakyReLU}'(x) = \begin{cases} 1 & x > 0 \\ \alpha & x \leq 0 \end{cases} \] 代码如下:
1 |
|
ELU
ELU 采用比 ReLU 更平滑的过渡,保持负区间微小梯度(\(\alpha\) 控制信息保留程度)解决神经元死亡问题。最重要的是,可以控制激活函数的输出均值接近于零(假设输入分布为标准正态输入),使正常梯度更接近于单位自然梯度,从而加快学习速度。表达式为: \[ \text{ELU}(x) = \begin{cases} x & x > 0 \\ \alpha(e^x - 1) & x \leq 0 \end{cases} \]
导数: \[ \text{ELU}'(x) = \begin{cases} 1 & x > 0 \\ \text{ELU}(x) + \alpha & x \leq 0 \end{cases} \] 缺点:
- ELU 在较小的输入下会饱和至负值,从而减少前向传播的变异和信息;
- 计算的时需要计算指数,计算效率低。
代码如下:
1 |
|
Swish
Google Brain (2017) 提出的自门控的智能激活,结合了线性与非线性特性的激活函数: \[ \text{Swish}(x) = x \cdot \sigma(\beta x) \] 导数: \[ \text{Swish}'(x) = \text{Swish}(x) + \sigma(\beta x)(1 - \text{Swish}(x)) \] 优点: - 自适应门控机制(通过 sigmoid 调整 \(\beta\),Swish 可以模拟不同形状); - 处处平滑可微,在全体实数域上连续可导(优于 ReLU 系列,\(x=0\) 处不可导); - 在深层网络中表现优于 ReLU(Google 实验证明)。
代码如下:
1 |
|
GeLU
高斯误差线性单元(Gaussian Error Linear Unit)是一种结合了高斯分布特性的激活函数,旨在通过概率建模的方式平滑地调整神经元的激活状态,被 BERT、GPT 采用。其数学表达式为:
\[ \text{GeLU}(x) = x \cdot \Phi(x) \]
其中,\(\Phi(x)\) 是标准高斯分布的累积分布函数(CDF)。为了高效计算,常采用近似公式: \[ \text{GeLU}(x) \approx 0.5x\left(1 + \tanh\left(\sqrt{\frac{2}{\pi}}(x + 0.044715x^3)\right)\right) \]
优点:
- 跟 Swish 长得非常像,都是平滑版的 ReLU(保留非线性同时可微分);
- 通过概率权重调整激活强度,避免 ReLU 的神经元死亡现象(负值完全被抑制)。
代码如下:
1 |
|
SwiGLU
SwiGLU 是一种结合了 Swish 激活函数和门控线性单元(GLU)的复合激活函数,近年来被广泛应用于大型语言模型(LLM)如 LLaMA、PaLM 等。
GLU 的原始形式为: \[ \text{GLU}(x) = \sigma(W x + b) \otimes (Vx) \] 其中 \(\otimes\) 是逐元素乘法,\(\sigma\) 是 Sigmoid 函数,用于门控信息流。而 SwiGLU 将 GLU 中的 Sigmoid 替换为 Swish: \[ \text{SwiGLU}(x) = \text{Swish}(W x + b) \otimes (Vx) \]
在实际实现中,SwiGLU 通常被整合到前馈网络(FFN)中,传统的 FFN 可以记为: \[ \text{FFN}_\text{ReLU} = (\text{ReLU}(W_1 x+b))W_2 + c \] 而 SwiGLU 通过 \(W_1x\) 完成升维操作的同时,还会用 \(Vx\) 完成门控操作,最后再用 \(W_2\) 降维: \[ \text{FFN}_\text{SwiGLU} = (\text{Swish}(W_1 x + b) \otimes (Vx))W_2 + c \] 优点:
- Swish 的连续梯度缓解了梯度消失问题,而门控机制进一步平衡了信息流,使深层网络训练更稳定;
- 实验表明其计算效率优于 GeLU,且下游任务表现更好。
Llama 中的代码如下:
1 |
|
Softmax
基本表达式为: \[ \text{Softmax}(x_i) = \frac{e^{x_i}}{\sum_{j=1}^n e^{x_j}} \] 在实际使用中,为了消除指数爆炸风险(原始值超过 20 时可能发生浮点溢出),通常会等价变形为: \[ \text{Softmax}(x_i) = \frac{e^{x_i - \text{max}(x)}}{\sum_{j=1}^n e^{x_j - \text{max}(x)}} \]
1 |
|
指标计算
PPL
困惑度(Perplexity)是语言模型的核心评估指标,用于衡量模型对测试数据的预测能力。其本质是交叉熵的指数形式,可理解为模型在预测时面临的平均「选择困境」。 \[ PPL = \exp\left(-\frac{1}{N}\sum_{i=1}^N \log p(w_i|w_{<i})\right) \]
其中 \(N\) 为测试集词数,\(p(w_i|w_{<i})\) 是模型预测当前词的概率。PPL 值越低,说明模型预测越准确。
1 |
|
ROUGE指标
自动摘要任务的黄金标准,主要变体:
- ROUGE-N:基于 n-gram 重叠的召回率
- ROUGE-L:基于最长公共子序列(LCS)
\[ ROUGE{\text-L} = \frac{(1+\beta^2)R_{\text{lcs}}P_{\text{lcs}}}{R_{\text{lcs}}+\beta^2 P_{\text{lcs}}} \]
其中 \(\beta\) 控制召回率权重,通常设为 1.2。
1 |
|
BLEU分数
机器翻译经典指标,基于修正 n-gram 精度和长度惩罚:
\[ BLEU = BP \cdot \exp\left(\sum_{n=1}^N w_n \log p_n\right) \]
其中 BP(Brevity Penalty)惩罚过短输出:
\[ BP = \begin{cases} 1 & \text{if } c > r \\ e^{(1-r/c)} & \text{otherwise} \end{cases} \]
1 |
|