深度学习小记

Created by: Yuanpeng QU
Created time: 2025年8月12日 17:41

I. 神经网络的核心组件与训练技巧

1. 归一化层:BN 与 LN 的原理、区别与应用场景

  • 核心目的:解决“内部协变量偏移 (Internal Covariate Shift)”问题。即在训练中,由于前层网络参数不断变化,导致后层网络接收到的数据分布一直在变,拖慢收敛速度。归一化层通过将每层网络的输入强制拉回到一个稳定的分布(如均值为0,方差为1),从而加速训练。
  • Batch Normalization (BN)
    • 原理:“纵向”或“按特征”归一化。它在一个批次(mini-batch)内,对每一个特征维度计算均值和方差,并进行归一化。
    • 关键机制:引入了两个可学习的参数 γ (缩放) 和 β (平移),让网络可以自主决定是否以及在多大程度上恢复原始的分布,以保证模型的表达能力。
    • 训练 vs 推理:训练时使用当前批次的统计量,同时用滑动平均记录全局统计量;推理时则使用保存下来的全局统计量,以保证输出的确定性。
    • 应用场景:在卷积神经网络 (CNN) 和处理表格数据的多层感知机 (MLP) 中效果显著,前提是批次大小(Batch Size)不能过小。
  • Layer Normalization (LN)
    • 原理:“横向”或“按样本”归一化。它在单个样本内部,对该样本的所有特征维度计算均值和方差,并进行归一化。
    • 关键优势:其计算完全独立于批次大小,与批次内的其他样本无关。
    • 应用场景:完美适用于自然语言处理 (NLP) 领域,尤其是 RNN 和 Transformer。因为 NLP 任务的序列长度经常变化,需要 padding,BN 的效果会受到严重干扰,而 LN 则完全不受影响。在 Transformer 中,Pre-LN 的设计比 Post-LN 训练更稳定,是当前的主流。

2. 梯度问题:梯度消失与梯度爆炸

  • 本质成因:两者都是由于深层网络的链式求导法则所引发的累积效应。梯度的计算是一个长链条的连乘,如果连乘因子持续大于1,则梯度爆炸;如果持续小于1,则梯度消失。当我们想计算第一层网络参数W1的梯度时,链式求导法则如下:

    $$
    \frac{\partial L}{\partial W_1} = \frac{\partial L}{\partial a_n} \cdot \frac{\partial a_n}{\partial a_{n-1}} \cdot \frac{\partial a_{n-1}}{\partial a_{n-2}} \cdot \dots \cdot \frac{\partial a_2}{\partial a_1} \cdot \frac{\partial a_1}{\partial W_1}
    $$

    这里的每一项 ∂ai/∂ai−1 都与第 i 层的权重矩阵激活函数的导数有关。整个梯度就变成了一个非常长的乘法链

    现在,问题就出在这个“连乘”上:

    • 如果连乘的因子大部分都小于 1,那么最终的乘积就会指数级地趋近于 0。这就是梯度消失
    • 如果连乘的因子大部分都大于 1,那么最终的乘积就会指数级地变得巨大。这就是梯度爆炸
  • 梯度消失 (Gradient Vanishing)

    • 现象:靠近输入层的网络无法得到有效的梯度信号,参数更新缓慢,模型学不动。
    • 主要原因:不合适的激活函数(如 Sigmoid 在其饱和区的导数接近0)或过小的权重。
    • 解决方案“工具箱”
      1. 更换激活函数:使用 ReLU 及其变体(如 GELU, SiLU),它们在正区间的导数为1,不会造成梯度衰减。
      2. 使用残差连接 (Residual Connections):ResNet 的核心。通过“快捷方式”让梯度可以直接流向前层,将“连乘”变为“连加”,从根本上解决问题。
      3. 使用归一化层 (BN/LN):将数据拉回到激活函数的非饱和区,保证梯度的活性。
  • 梯度爆炸 (Gradient Explosion)

    • 现象:梯度变得极大,导致参数更新步子过大,损失函数剧烈震荡甚至变为 NaN,训练崩溃。
    • 主要原因:过大的权重初始化。
    • 解决方案“工具箱”
      1. 梯度裁剪 (Gradient Clipping):最直接有效的方法。给梯度设置一个范数上限,超过则按比例缩放,从而限制单次更新的最大步长。
      2. 使用归一化层 (BN/LN):同样能起到稳定激活值,间接稳定梯度的作用。
      3. 合适的权重初始化:如 Xavier 或 He 初始化,从源头上避免权重过大。

3. 初始化策略

  • 核心目的:打破对称性 (Break the Symmetry)
    • 问题:如果将同一层的所有权重初始化为相同的值(如全0或全1),那么在正向和反向传播中,这些神经元的行为将永远保持一致,它们会学到完全相同的特征。这等效于该层只有一个神经元在工作,完全浪费了网络的表达能力。
    • 解决:必须通过随机初始化来赋予每个神经元独特的初始状态,让它们有机会在训练中学习到不同的特征。
  • Transformer 为何不常用 He 初始化?
    • He 初始化的专长:专为 ReLU 网络设计,目的是维持激活值的方差稳定。
    • Transformer 的特殊性
      1. 无处不在的 LayerNorm:Transformer 中大量使用 LN,它本身就是一个强大的稳定器,在每层之后都对数据分布进行“重置”,这使得网络对权重的初始尺度不那么敏感。
      2. 投影层的需求:Transformer 中的许多线性层用于“投影”(如生成 QKV),其目标是稳定地传递信息。Xavier/Glorot 初始化同时考虑了输入和输出维度,旨在维持信号方差在流经该层时保持不变,理论上更适合这种场景。

II. 优化算法的演进之路

1. 核心思想:一阶动量与二阶动量

现代优化器的设计,都围绕着这两个核心概念,它们源于统计学中的“矩”:

  • 一阶动量 (First-Order Momentum, mt)
    • 统计意义:梯度的指数移动平均值,是对梯度分布均值的估计。
    • 物理意义:“惯性”或“速度”。
    • 作用:决定参数更新的主要方向。通过累积历史梯度,它可以平滑更新路径,抑制在“峡谷”地带的震荡,并在梯度方向一致时加速前进。
  • 二阶动量 (Second-Order Momentum, Vt)
    • 统计意义:梯度平方的指数移动平均值,是对梯度分布方差的估计。
    • 物理意义:“摩擦力”或“路况感知器”。
    • 作用:实现自适应学习率。它被放在学习率计算公式的分母位置。对于梯度变化剧烈(方差大)的参数,其有效学习率会变小,步子更稳;对于梯度一直很小(方差小)的参数,其有效学习率会变大,鼓励其更新。

优化算法的统一框架

  1. 计算梯度:计算当前参数 wt 的梯度 g_t=∇f(w_t)

  2. 更新动量:根据历史梯度计算一阶动量 m_t(梯度的均值)二阶动量 V_t(梯度平方的均值)

  3. 计算下降步长η_t=α⋅m_t/V_t

    [](data:image/svg+xml;utf8,)

  4. 更新参数w_{t+1}=w_t−η_t

这个框架的核心在于:

参数的更新方向由一阶动量 mt 决定,而更新的步长(学习率)由二阶动量 Vt 进行动态调整

。不同的优化算法,就是在这四步,特别是第 2 步上做了不同的设计。

2. SGD 家族:从“盲人摸象”到“带惯性下坡”

这个家族的特点是使用全局统一的学习率

  • SGD (随机梯度下降)
    • 做法:只看当前一步的梯度方向,然后走一小步。
    • SGD 没有动量 (Momentum) 的概念。可以理解为:一阶动量mt就是当前梯度gt,二阶动量Vt不使用,或者说恒为1。
    • 所以按照上面的公式3代入SGD的下降步长为η_t=α·gt,更新参数则为w_{t+1}=w_t−α·gt
    • 问题:非常“短视”,容易在曲折的路径上震荡,收敛缓慢。
  • SGDM (SGD with Momentum)
    • 改进:引入了一阶动量,像一个有惯性的小球,累积过去的速度。

      $$
      m_t = \beta_1 \cdot m_{t-1} + (1 - \beta_1) \cdot g_t
      $$

    • mt:t时刻的动量,代表了历史梯度的累积方向。

    • beta1: 动量系数,通常取 0.9。这个值意味着,当前的更新方向主要由历史方向决定 (90%),只受当前梯度的一点点影响 (10%)。

    • 二阶动量Vt仍然不使用。

    • 代入公式3,得到SGDM下降步长为η_t=α·mt,更新参数则为w_{t+1}=w_t−α·mt

    • 效果:极大地缓解了震荡,加速了收敛。

    • 新问题:惯性是“盲目”的,可能会因为速度太快而冲过最优解。

  • NAG (Nesterov Accelerated Gradient)
    • 改进:引入了“预判”机制。它会先“探头”看一下按照当前动量走一步后会到达什么位置,然后用那个“未来”位置的梯度来修正当前的方向。
    • 效果:更“聪明”,能有效防止冲过头,收敛通常更稳定。

3. 自适应家族:为每个参数定制学习率

这个家族解决了 SGD 家族“一刀切”学习率的问题。

  • AdaGrad

    AdaGrad 第一个提出了这个革命性的想法。

    • 核心动机
      一个参数过去的梯度历史,可以指导它未来的更新步伐。

      • 如果一个参数的梯度一直很大,说明它可能处于一个“陡峭”的区域,我们应该小心翼翼,用一个较小的学习率
      • 如果一个参数的梯度一直很小(甚至是0,例如稀疏特征),说明它很少被更新。一旦它有机会更新,我们应该给它一个较大的学习率,让它能“迎头赶上”。
    • 实现机制
      AdaGrad 为每一个参数 w(i) 维护一个历史梯度平方的累加器 Vt(i)。

      1. 累积梯度平方和:在每一步 t,计算当前梯度 gt(i),并将其平方累加到 Vt(i) 中。

        $$
        V_t^{(i)} = V_{t-1}^{(i)} + (g_t^{(i)})^2
        $$

      2. 更新参数:在更新时,将全局学习率 α 除以 根号Vt(i)。ϵ 是一个极小值防止分母为零

        [](data:image/svg+xml;utf8,)

        [](data:image/svg+xml;utf8,)

        $$
        w_{t+1}^{(i)} = w_t^{(i)} - \frac{\alpha}{\sqrt{V_t^{(i)} + \epsilon}} \cdot g_t^{(i)}
        $$

      [](data:image/svg+xml;utf8,)

      [](data:image/svg+xml;utf8,)

      • 优点
        • 在处理稀疏数据(如搜广推中的海量ID类特征)时表现极其出色。对于那些不常出现的特征,其 Vt 累积很慢,因此能保持较大的学习率,保证了有效的学习。
      • 致命缺陷
        • 由于 (gt(i))2 永远是正数,累加器 Vt(i) 是严格单调递增的。

        • 随着训练的进行,Vt 会变得越来越大,导致分母 Vt(i) 也无限增大。

          [](data:image/svg+xml;utf8,)

        • 这使得有效学习率 α/根号Vt(i) 会持续衰减,并最终趋近于零。这会导致模型在训练后期“学习乏力”,过早地停止更新,即使它还没有达到最优状态。

          [](data:image/svg+xml;utf8,)

  • RMSProp

    • 改进:将 AdaGrad 的“累加和”改为了指数移动平均(即二阶动量)
      1. 更新二阶动量:不再是简单相加,而是加权平均。

        $$
        V_t^{(i)} = \beta_2 \cdot V_{t-1}^{(i)} + (1 - \beta_2) \cdot (g_t^{(i)})^2
        $$

        其中,衰减率 β2 是一个接近 1 的值(如0.999),它决定了历史信息的“遗忘速度”。

      2. 更新参数:更新规则的形式与 AdaGrad 完全相同。

        [](data:image/svg+xml;utf8,)

    [](data:image/svg+xml;utf8,)

    • 优点
      • 由于 Vt 是一个移动平均值,它不再会无限增长。如果最近的梯度变小了, Vt 也会随之减小,从而让学习率有机会“回升”。这使得 RMSProp 非常鲁棒,在各种任务上都表现良好。
    • 潜在不足
      • 它只解决了自适应学习率的问题(二阶动量),但没有集成 SGD with Momentum 那样的“惯性”(一阶动量)。
  • Adam (Adaptive Moment Estimation)

    • 改进集大成者。既然一阶动量(如 Momentum)能帮助我们确定更好的更新方向,而二阶动量(如 RMSProp)能为每个参数找到合适的学习率,为什么不把它们都用上呢?它将 SGDM 的一阶动量(控制方向)和 RMSProp 的二阶动量(控制自适应学习率)完美结合。

    • 实现机制
      Adam 为每个参数维护了两个移动平均值:

      1. 一阶动量 mt (梯度的均值)

        $$
        m_t^{(i)} = \beta_1 \cdot m_{t-1}^{(i)} + (1 - \beta_1) \cdot g_t^{(i)}
        $$

      2. 二阶动量 Vt (梯度平方的均值)

        $$
        V_t^{(i)} = \beta_2 \cdot V_{t-1}^{(i)} + (1 - \beta_2) \cdot (g_t^{(i)})^2
        $$

    • 独有设计:偏差修正 (Bias Correction)

      • 问题:mt 和 Vt 都是从 0 开始初始化的。在训练初期,由于 β1 和 β2 都很接近 1,会导致 mt 和 Vt 的值系统性地偏向于 0

      • 修正:为了解决这个冷启动问题,Adam 在使用它们之前,先对其进行修正:

        $$
        \hat{m}_t^{(i)} = \frac{m_t^{(i)}}{1 - \beta_1^t}
        $$

        $$
        \hat{V}_t^{(i)} = \frac{V_t^{(i)}}{1 - \beta_2^t}
        $$

        在训练初期(t 很小),分母会显著地放大 mt 和 Vt 的值,以纠正偏差。随着训练的进行(t 变大),分母会趋近于 1,修正作用随之消失。

    • 最终更新规则

      $$
      w_{t+1}^{(i)} = w_t^{(i)} - \frac{\alpha}{\sqrt{\hat{V}_t^{(i)} + \epsilon}} \cdot \hat{m}_t^{(i)}
      $$

      [](data:image/svg+xml;utf8,)

      这里,参数更新的方向由修正后的一阶动量 m^t 提供,而每一步的学习率由修正后的二阶动量 V^t 进行自适应地、逐参数地调节。

    总结:这条演进之路清晰地展示了研究者们如何不断发现问题并解决问题:
    AdaGrad 提出了自适应思想但有硬伤 -> RMSProp 修复了硬伤但不够全面 -> Adam 融合了 Momentum 和 RMSProp 的优点并加入了偏差修正,最终成为了一个极其强大和通用的优化器。

4. 高级优化器:面向工业界的实战选择

  • AdamW (Adam with Decoupled Weight Decay)

    要深刻理解 AdamW,我们必须先厘清一个经常被混淆的概念:L2 正则化 (L2 Regularization)权重衰减 (Weight Decay)

    • 在 SGD 这样的简单优化器中,这两者在数学上是等价的。
    • 但在 Adam 这样的自适应优化器中,它们的效果截然不同,而这个差异正是 AdamW 诞生的原因。

    1. 问题:Adam + L2 正则化错在哪里?

    • 标准 L2 正则化的做法:将 L2 惩罚项 λ/2 ∥w∥2 加入到损失函数中。这会导致反向传播时,梯度中增加了一项 λw。

      $$
      g_{total}=g_{loss}+λw
      $$

    • Adam 的处理方式:Adam 会将这个包含了正则项的完整梯度 gtotal,送入它的一阶和二阶动量计算中。我们重点看二阶动量 Vt:

      $$
      V_t = \beta_2 V_{t-1} + (1 - \beta_2)(g_{loss} + \lambda w)^2
      $$

      最终的参数更新步长大致为:

      $$
      \Delta w \propto \frac{\alpha}{\sqrt{V_t}} \cdot m_t
      $$

      [](data:image/svg+xml;utf8,)

    • 矛盾出现

      • L2 正则化的初衷是:权重 w 越大,惩罚(衰减)就应该越厉害,以防止过拟合。

      • 但在 Adam 中,对于那些本身梯度很大(gloss 很大)或者权重很大(λw 很大)的参数,它们的二阶动量 Vt 也会变得很大。

      • 这导致分母 Vt 变大,反而削弱了对这些重要参数的学习率和正则化效果。

        [](data:image/svg+xml;utf8,)

      • 结论:Adam 的自适应学习率机制与 L2 正则项发生了“耦合”,扭曲了 L2 正则化原本的意图,使其效果大打折扣。

    2. AdamW 的解决方案:解耦 (Decouple)

    AdamW 的思想是返璞归真,让正则化和梯度优化“分道扬镳”。

    • 第一步:纯粹的梯度优化

      • 在计算一阶动量 mt 和二阶动量 Vt 时,只使用原始的损失梯度 gloss。这样,mt 和 Vt 就纯粹地反映了损失函数本身的几何特性。

      • 计算出不含正则项的“Adam 更新量”:

        $$
        \text{Adam_update} = \frac{\alpha}{\sqrt{\hat{V}_t} + \epsilon} \cdot \hat{m}_t
        $$

        [](data:image/svg+xml;utf8,)

    • 第二步:独立的权重衰减

      • 在参数更新的最后,直接减去一个与权重大小成正比的衰减项

      • 最终更新规则:

        $$
        w_{t+1} = w_t - \text{Adam_update} - \alpha \cdot \lambda’ \cdot w_t
        $$

        (这里的 λ′ 是权重衰减系数,注意这个衰减项没有被 Vt 除!)

        [](data:image/svg+xml;utf8,)

    • 效果与应用

      • AdamW 的权重衰减效果更稳定、更符合直觉。它能带来更好的泛化性能和更低的训练损失。
      • 由于其出色的表现,AdamW 已成为训练 TransformerBERTGPT 等大型语言模型的事实标准,是现代深度学习工具箱中的必备选项。
  • FTRL (Follow-The-Regularized-Leader)

    FTRL 的设计哲学和应用场景与 Adam 完全不同,它是在线学习 (Online Learning)广告点击率 (CTR) 预估领域的王者。

    1. 核心动机与场景

    • 场景:在推荐和广告系统中,特征空间是亿级别、甚至百亿级别的,并且是极度稀疏的。例如,一个用户的特征由他的ID、他正在看的商品ID、广告ID等组成,这些ID绝大部分都不会在下一个样本中出现。
    • 需求
      1. 模型必须非常小,以便于快速部署和线上低延迟推理。
      2. 模型必须能高效地处理稀疏特征。
    • 核心目标:训练出一个高度稀疏的模型,即模型的大部分权重都等于零

    2. 实现机制:带 L1 正则化的“懒惰”更新

    FTRL 的核心在于它独特的、逐坐标的更新法则,其中巧妙地融入了 L1 正则化,以强制产生稀疏性。

    我们可以这样理解它的更新步骤:

    1. 累积信息:和 AdaGrad 类似,FTRL 为每个参数 w(i) 维护两个累加器:
      • zt(i): 历史梯度之和。
      • Vt(i): 历史梯度平方之和(用于自适应学习率)。
    2. “懒惰”的更新决策(稀疏性的来源)
      在决定是否更新 w(i) 之前,FTRL会先做一个判断:
      • 如果累积的梯度信号 ∣zt(i)∣ 还不足以“战胜”L1 正则化的惩罚力度 λ1,即 ∣zt(i)∣<λ1:
        • 决策:FTRL 认为这个特征的信号太弱,不值得为其分配非零权重。于是,直接将该参数设为 0
      • 如果累积的梯度信号足够强,即 ∣zt(i)∣≥λ1:
        • 决策:此时才认为该特征是有效的,并根据“超出” L1 惩罚的那部分梯度,以及自适应学习率,计算出一个非零的权重值。
    • 效果与应用
      • 极致稀疏:通过这种“阈值过滤”机制,FTRL 能将大量不重要、信号不强的特征权重直接剪枝为零,产出的模型非常小巧。
      • 工业标准:在处理大规模类别特征的 LR、FM 这类模型上,FTRL 是当之无愧的工业标准优化器,广泛应用于各大互联网公司的广告和推荐系统的排序阶段,尤其是 Wide & Deep 模型中的 Wide 部分

    AdamW vs. FTRL 面试总结

    对比维度 AdamW FTRL
    核心优势 优秀的泛化能力,稳定的正则化效果 产出高度稀疏的模型,内存占用小,推理速度快
    处理对象 稠密模型,如深度神经网络、Transformer 稀疏模型,如带海量ID特征的线性模型(LR/FM)
    应用场景 训练大语言模型、计算机视觉、推荐系统的Deep部分 广告/推荐系统的CTR预估、在线学习、推荐系统的Wide部分
    一句话总结 训练复杂深度模型的“瑞士军刀” 处理海量稀疏特征、追求极致效率的“手术刀”

5. 学习率调度:掌握训练的“节奏”

  • 问题:单一的、固定的学习率无法适应整个训练过程的需求。
  • 常用策略Warmup + Decay
    • Warmup (热身):在训练初期,使用一个很小的学习率,并逐步线性增加到预设的目标值。这可以防止模型在初始权重混乱时因步子太大而“崩溃”。
    • Decay (衰减):在 Warmup 结束后,让学习率随着训练的进行而逐渐降低。这有助于模型在接近最优解时,能以更小的步长进行精细探索,从而收敛得更好。
    • 组合推荐“线性 Warmup + 余弦退火 (Cosine Annealing) Decay” 是一个非常鲁棒且流行的现代训练策略。

III. 文本表示的进化

1. 统计时代:TF-IDF — “以数取胜”的关键词大师

在深度学习普及之前,我们用巧妙的统计学方法来衡量词的重要性。其中,TF-IDF 是最经典、最有效的代表。

  • 核心思想:一个词的重要性,由它在**本文档内的频率(TF)和在整个语料库中的稀有度(IDF)**共同决定。
  • TF (Term Frequency, 词频)
    • 是什么:一个词在本篇文章中出现的频率。
    • 计算TF = (某词在文章中出现的次数) / (文章总词数)
    • 直觉:在一篇关于“自动驾驶”的论文中,“传感器”这个词的 TF 值会很高。但这有个问题:像“的”、“是”这种常用词的 TF 值也会很高。
  • IDF (Inverse Document Frequency, 逆文档频率)
    • 是什么:对 TF 值进行修正的“惩罚”项,用来衡量一个词的“信息量”或“稀有度”。
    • 计算IDF = log(总文档数 / (包含该词的文档数 + 1))
    • 直觉:像“的”、“是”这种词,几乎所有文档都包含,分母会很大,因此它们的 IDF 值会极其接近 0。而“自动驾驶”这种专业术语,只在少数文档中出现,其 IDF 值就会很高。
  • 最终形态:TF-IDF
    • 计算TF-IDF = TF × IDF
    • 效果:只有那些在本文档中频繁出现(高TF),但在别的文档中很少见(高IDF)的词,才能获得很高的 TF-IDF 分数。这使得它成为一个极其出色的关键词提取算法。
    • 局限性
      • 语义缺失:它完全不理解词语的含义。在 TF-IDF 的世界里,“国王”和“女王”是两个毫无关系的词。
      • 稀疏性:它为每个文档生成一个维度等于整个词典大小的向量,其中绝大部分都是零,非常稀疏。

2. 静态向量时代:Word2Vec — “物以类聚”的语义先锋

为了解决 TF-IDF 无法理解语义的问题,神经网络方法应运而生,其核心思想是分布式假设 (Distributional Hypothesis):“一个词的含义,由其上下文决定”。

  • 核心思想
    不再统计词频,而是通过一个神经网络,为词典中的每一个词学习一个稠密的、低维的向量(即 Embedding)。在这个向量空间中,意思相近的词,其向量距离也相近。
  • 两种经典训练范式
    1. CBOW (Continuous Bag-of-Words)上下文 -> 中心词
      • 任务:“完形填空”。模型看到 [深度, __, 网络],然后去预测中间的词是“学习”。
      • 特点:训练速度快,对高频词效果好。
    2. Skip-gram中心词 -> 上下文
      • 任务:“头脑风暴”。模型看到“学习”,然后去预测它周围可能出现的词是 [深度, __, 网络]
      • 特点:训练速度慢一些,但对低频词和生僻词的学习效果更好。
  • 在推荐系统中的应用:Item2Vec
    • 这是一个绝妙的类比:将用户的行为序列(如点击、购买)看作一个“句子”,将商品ID看作“词语”
    • 通过 Skip-gram 模型训练这些“句子”,我们就能得到每个商品的 Embedding。
    • 效果:经常被一同购买或浏览的商品(如“手机壳”和“手机膜”),它们的 Embedding 就会非常接近。这是构建“相关推荐”、“猜你喜欢”等模块的强大基石。
  • 致命缺陷:无法解决多义词 (Polysemy)
    • Word2Vec 为每个词生成一个全局唯一、固定不变的静态向量。
    • 经典例子
      1. 我把钱存进了银行 (bank)。 (金融机构)
      2. 我坐在河岸 (bank) 上。 (地理位置)
    • 对于 Word2Vec,这两个 bank 的向量是一模一样的。它无法根据上下文动态地调整词的表示,这是其最大的局限性。

3. 动态向量时代:BERT — “千人千面”的语境大师

BERT 的出现,标志着 NLP 进入了一个新纪元,其核心是提供上下文相关的动态词向量 (Contextualized Word Embeddings)

  • 核心思想:一个词没有固定的含义,它的意义完全由其所在的上下文语境所定义。
  • 如何实现?
    1. 强大的骨架:Transformer Encoder。我们已经深入讨论过,其核心的自注意力机制,允许输入序列中的每一个词都能“看到”并与所有其他词进行信息交互。
    2. 动态计算:BERT 不会预先存好每个词的向量。当一个句子输入后,句子中的每个词的初始 Embedding 会流经 BERT 的多层(如12层)Transformer。在每一层,每个词的向量都会根据整个句子的信息被重新计算和优化。
    3. 最终输出:从 BERT 的最后一层输出的,才是这个词在当前特定语境下的最终向量表示。
  • 解决多义词问题
    • 当处理 “I went to the bank“ 时,bank 的向量会因为它看到了 went to 等信息,而被塑造成带有“金融”含义的向量。
    • 当处理 “The river bank“ 时,bank 的向量会因为它看到了 river,而被塑造成带有“地理”含义的向量。
    • 这两个 bank 的最终输出向量在向量空间中会相距很远,从而完美解决了多义词问题。

总结:从 TF-IDF 的词频统计,到 Word2Vec 的静态语义向量,再到 BERT 的动态上下文向量,我们看到机器对文本的理解,从表层统计,发展到固定语义,最终达到了深度、动态的语境理解。这条进化路径是所有现代搜广推应用的基础。

IV. Transformer 与 BERT 模型深度剖析

1. Transformer 基石:自注意力机制 (Self-Attention)

[](data:image/svg+xml;utf8,)

[](data:image/svg+xml;utf8,)

自注意力机制是 Transformer 的心脏。它的核心思想是:一个序列中的每个元素,其新的表示,应该由序列中所有元素根据相关性进行加权求和得到。

1.1 QKV 矩阵的角色分工与生成方式

为了实现上述思想,自注意力机制为每个输入向量 x 创造了三个不同的身份,这个过程是通过三个独立、可学习的线性变换矩阵 WQ,WK,WV 完成的:

  • Query (Q, 查询): Q=X⋅WQ
    • 角色:代表当前词为了更好地理解自己,主动发出的一个“查询”或“问题”。它在问:“为了理解我,我应该关注序列中的哪些信息?”
  • Key (K, 键): K=X⋅WK
    • 角色:代表序列中每个词所拥有的一个“索引”或“标签”,用来被别人查询。它在说:“这是我的身份标签,你们可以根据这个标签来判断我是否与你们相关。”
  • Value (V, 值): V=X⋅WV
    • 角色:代表序列中每个词实际蕴含的**“内容”或“信息”**。它在说:“如果你们关注我,这就是我能提供给你们的丰富信息。”

计算流程:

  1. 匹配打分:用每个词的 Query 向量,去和所有词的 Key 向量进行点积运算 (QKT),得到一个注意力分数矩阵。这个分数代表了“查询”与“索引”之间的匹配程度。
  2. 加权求和:将分数通过 Softmax 归一化为权重,然后用这个权重去对所有词的 Value 向量进行加权求和。

Encoder vs Decoder 中的区别:

  • Encoder 中 (Self-Attention):Q, K, V 全部来自于上一层 Encoder 的输出。它是在对输入序列自身进行信息重组,目的是为了深刻理解输入。
  • Decoder 中 (包含两种 Attention)
    1. Masked Self-Attention: Q, K, V 全部来自于上一层 Decoder 的输出。目的是让已生成的部分序列理解自身,为生成下一个词做准备。
    2. Cross-Attention: Q 来自于 Decoder (上一步 Masked Self-Attention 的输出),而 K 和 V 来自于 Encoder 的最终输出。这是让 Decoder 在生成新内容时,能够查询和参考完整的原始输入信息的关键桥梁。

1.2 位置编码 (Positional Encoding): 弥补顺序信息的缺失

  • 问题:自注意力机制本身是“无序”的,它平等地看待序列中的每一个词,无法感知它们的先后顺序。
  • 解决方案:在模型的输入层,将每个词的 Embedding 与一个代表其绝对位置的“位置向量”相加,强行把顺序信息注入模型。
  • 两种主流方式
    1. 固定式 (Sinusoidal PE):原始 Transformer 使用的方式。它通过不同频率的 sincos 函数生成一个固定的、不参与训练的位置向量。其优点是能够处理比训练时更长的序列,并且其周期性使得模型能轻易学习到相对位置关系。
    2. 可学习式 (Learnable PE):BERT 等模型使用的方式。它创建一个位置 Embedding 矩阵(就像一个词表一样),让模型在训练过程中自己去学习每个位置最合适的向量表示。优点是更灵活、更能适应特定任务,但通常有最大长度限制。

1.3 注意力掩码 (Attention Mask): 控制信息的流动

  • 原理:在计算好的注意力分数矩阵上,在送入 Softmax 之前,将需要被屏蔽的位置的分数设置为一个巨大的负数(如 -1e9)。这样,经过 Softmax 后,这些位置的注意力权重就会变成 0。
  • 两种核心掩码
    1. Padding Mask (填充掩码)
      • 应用场景:Encoder 和 Decoder。
      • 目的:在处理一个批次的变长序列时,我们需要用 <pad> 标记将短序列补齐。此掩码的作用就是告诉模型,在计算注意力时,完全忽略这些无意义的 <pad> 标记
    2. Look-ahead Mask (前瞻/因果掩码)
      • 应用场景仅用于 Decoder
      • 目的:在生成任务中,为了保证模型的自回归特性,在预测第 i 个词时,绝对不能让它“偷看”到第 i 个词之后的信息
      • 实现:通过一个上三角矩阵,将所有“未来”位置的注意力分数全部屏蔽掉。

1.4 注意力缩放因子根号 dk 的作用

[](data:image/svg+xml;utf8,)

  • 问题:在计算 Q 和 K 的点积时,如果向量维度 dk 很大(例如 512),点积的结果的方差也会变得很大。

  • 危害:过大的点积结果会把 Softmax 函数推向其饱和区,导致梯度变得极小,从而引发梯度消失,使训练过程不稳定甚至失败。

  • 解决方案:将计算出的点积分数除以根号 dk

    [](data:image/svg+xml;utf8,)

  • 数学原理:原论文作者证明,假设 Q 和 K 的元素是均值为 0、方差为 1 的独立随机变量,那么它们的点积结果的方差就是 dk。因此,除以 根号dk 这一步,恰好能将点积结果的方差重新归一化为 1,从而保证了送入 Softmax 的数值在一个合理的、梯度稳定的范围内。这是一个至关重要的稳定化技巧

2. BERT 模型解析

[](data:image/svg+xml;utf8,)

BERT (Bidirectional Encoder Representations from Transformers) 的发布是 NLP 领域的里程碑事件。它的核心贡献在于,通过巧妙的预训练任务,第一次实现了深度的、无监督的双向语言表示学习。

2.1 预训练任务:MLM 与 NSP 的原理、缺陷及改进

BERT 的强大能力,源于它在海量文本上进行的两项“自我修炼”任务。

  • MLM (Masked Language Model / 掩码语言模型)
    • 原理:这是 BERT 的核心创新。它采用“完形填空”的方式进行训练。
      1. 随机选取输入序列中 15% 的 Token。
      2. 在这 15% 的 Token 中,80% 的情况用一个特殊的 [MASK] 标记替换掉,10% 的情况用一个随机的词替换,10% 的情况保持原样。
      3. 模型的目标,是仅根据未被遮盖的上下文,准确预测出这些被遮盖位置的原始 Token 是什么
    • 意义(为什么是双向的?):与只能看到左侧文本的 GPT 不同,MLM 任务天然地强迫模型必须同时利用被遮盖位置的左侧和右侧的全部上下文信息才能做出正确的预测。这使得 BERT 能够学习到真正意义上的深层双向语境表示
    • 缺陷与改进
      • 独立性假设问题:BERT 在预测多个 [MASK] 时是相互独立的,无法利用被预测词之间的关系。
      • 改进 1: WWM (Whole Word Masking):对策是,如果一个词被 WordPiece 分割成多个子词(如 playing -> ['play', '##ing']),那么就将这个完整单词对应的所有子词一同进行 Mask。这增加了任务难度,也更符合语言逻辑。
      • 改进 2: SpanBERT:更进一步,不再 Mask 零散的词,而是 Mask 一个连续的文本片段 (Span)。模型需要利用片段两端边界的信息来预测整个片段的内容,这促使模型学习更强的短语级语义关系。
  • NSP (Next Sentence Prediction / 下一句预测)
    • 原理:这是一个二分类任务,用于学习句子间的关系
      1. 模型接收一对句子 (A, B)。
      2. 50% 的情况下,B 是 A 在原文中真实的下一句(正样本)。
      3. 50% 的情况下,B 是从其他文档中随机抽取的一个句子(负样本)。
      4. 模型需要预测 B 是否是 A 的下一句。
    • 缺陷:后来的研究发现,这个任务存在“捷径”。由于负样本来自完全不同的文档,模型很可能仅通过判断两个句子的主题是否相关就能做出正确判断,而没有真正学到更深层次的逻辑连贯性
    • 改进
      • RoBERTa 发现此任务弊大于利,直接将其废除,仅使用 MLM 在更长的连续文本上训练,效果反而更好。
      • ALBERT 提出了 SOP (Sentence Order Prediction) 任务。它取同一文档中的两个连续句子,50% 保持原序,50% 调换顺序。这排除了主题相关性的干扰,强迫模型去学习更细致的语篇连贯性

2.2 BERT 的输入构成:三位一体的 Embedding

为了让模型同时理解“词义”、“位置”和“句间关系”,BERT 的每个输入 Token 的最终向量表示,是由三种 Embedding 按元素相加而成的。

  • Token Embeddings (词元嵌入):最基础的部分,代表了每个词或子词(WordPiece)本身的语义。
  • Segment Embeddings (分段嵌入):用于区分两个不同的输入句子。所有属于第一句话的 Token,其 Segment Embedding 都是同一个向量(通常是 EA);所有属于第二句话的 Token,其 Segment Embedding 都是另一个向量(EB)。这是为 NSP 任务量身设计的。
  • Position Embeddings (位置嵌入):我们讨论过的可学习的位置向量。每个位置(0, 1, 2, …, 511)都有一个独立的、在训练中不断优化的向量,来为模型提供顺序信息。

最终输入 = Token Embedding + Segment Embedding + Position Embedding

2.3 [CLS] 标记:序列表示的“代言人”

  • 是什么:一个特殊的标记,永远被放在输入序列的最开头
  • 工作原理
    [CLS] 本身没有语义,但因为它和其他所有词一样,参与了 BERT 所有 Encoder 层的全局自注意力计算,所以在经过 12 层的信息充分“搅拌”和“融合”后,[CLS] 标记在最后一层对应的输出向量,就成了一个包含了整个输入序列信息的“聚合表示”。它像一个指定的“会议总结人”,最终产出了代表会议精神的总结报告。
  • 意义与应用
    • 提供固定维度的句子表示:无论输入句子多长,我们都可以从 [CLS] 位置得到一个固定维度(如 768 维)的向量来代表整个句子。
    • 简化下游任务微调:在进行句子级别的分类任务时(如情感分析、文本分类),我们只需要:
      1. 取出 BERT 最后一层的 [CLS] 输出向量。
      2. 将这个向量接入一个简单的、小型的分类器(如一个全连接层)。
      3. 进行微调。此时,分类任务的梯度会通过 [CLS] 向量反向传播到整个 BERT 模型,等于在告诉模型:“请调整你所有的参数,把对我的任务最有用的信息,都浓缩到 [CLS] 这个向量里来!”

通过这种方式,[CLS] 成为了连接强大的预训练模型和各种具体下游任务的标准、高效的桥梁。

3. Transformer & BERT 的局限与优化

3.1 注意力机制效率优化

这部分主要关注两个场景:一是推理 (Inference) 时的显存和速度优化,二是训练 (Training) 时的计算效率优化。

从 MHA 到 GQA 和 MQA 的演进:优化推理中的 KV Cache

在进行自回归生成(即一个词一个词地生成文本)时,为了避免重复计算,模型需要将过去所有 token 的 Key (K) 和 Value (V) 缓存起来,这被称为 KV Cache。随着生成文本越来越长,KV Cache 会变得非常巨大,成为推理时的显存瓶颈。

  • MHA (Multi-Head Attention):标准的多头注意力。
    • 机制:假设有 32 个头,那么就有 32 组独立的 Q, K, V 投影矩阵。
    • 问题:在推理时,需要缓存 32 组独立的 K 和 V。这导致 KV Cache 非常庞大,不仅消耗大量显存,而且在生成每个新 token 时,从显存中读取巨大的 K 和 V 矩阵也非常耗时。
  • MQA (Multi-Query Attention):一个极致的优化方案。
    • 核心思想:“所有头真的需要各自独立的 K 和 V 吗?也许它们可以共享?”
    • 机制:仍然保留 32 个独立的 Query 头,但让所有这 32 个头共享同一套 Key 和 Value 投影
    • 效果:KV Cache 的大小被急剧压缩为原来的 1/32!这极大地降低了推理时的显存占用,并显著提升了生成速度。
    • 代价:牺牲了一定的模型性能和容量,因为所有头被迫从同一个“知识库”中提取信息。
  • GQA (Grouped-Query Attention):一个介于 MHA 和 MQA 之间的“黄金平衡点”。
    • 机制:将 32 个 Query 头分成若干组,比如 4 组,每组 8 个头。在同一组内的 8 个头,共享同一套 Key 和 Value。这样,我们就从 MHA 的 32 组 K/V,和 MQA 的 1 组 K/V,变成了 4 组 K/V。
    • 效果:它获得了 MQA 大部分的加速和显存节省效果,同时比 MQA 保留了更好的模型质量。GQA 是当前最先进的开源大模型(如 Llama 2/3)采用的主流方案。

FlashAttention 的 I/O 感知优化原理:重塑训练效率

FlashAttention 是一项革命性的算法层面的优化,它在**不改变任何数学结果(即精确注意力)**的前提下,极大地提升了训练和长文本推理的速度。

  • 核心洞察:标准注意力的瓶颈不在于浮点数计算(FLOPs),而在于内存的读写(I/O)。在计算过程中,需要生成一个巨大的 N×N 的中间矩阵(QKT 的结果),并反复地在 GPU 核心的高速缓存(SRAM)和 GPU 的主显存(HBM)之间进行读写。HBM 带宽远低于 SRAM 的计算速度,这造成了大量的等待时间。
  • 举例:对于一个不算太长的序列,比如 N = 16k (16384),如果用半精度(2字节)存储,这个矩阵的大小就是 16384 * 16384 * 2 bytes ≈ 512 MB
  • 瓶颈:这个 512MB 的矩阵,对于 GPU 核心旁边的高速缓存 (SRAM, 只有几 MB) 来说太巨大了,根本放不下。因此,GPU 必须将这个巨大的矩阵写入速度慢得多的主显存 (HBM)。在整个计算过程中,GPU 核心不得不频繁地暂停计算,去等待这个大矩阵从 HBM 中读出或写入。计算单元大部分时间都在“等”,而不是在“算”。这就是所谓的 I/O 瓶颈,其读写次数是 O(N2) 的。
  • FlashAttention 的解决方案
    1. 分块计算 (Tiling):它不再试图一次性生成整个 N×N 的大矩阵。它将输入的 Q, K, V 矩阵切分成一个个更小的“块 (Tile)”,小到可以完全放进高速的 SRAM 中。
    2. 算子融合 (Kernel Fusion):FlashAttention 将注意力的多个计算步骤(矩阵乘法、Mask、Softmax、与V相乘等)融合成了单个 GPU 计算核 (Kernel)。数据块一旦从慢速的 HBM 载入到高速的 SRAM 后,就会在这个“小作坊”里完成所有的计算,得到最终结果,然后再写回 HBM。
    3. 执行流程
      • 将一小块 Q 和一小块 K 从慢速 HBM 加载进高速 SRAM。
      • 在 SRAM 内部,完成这部分的 QK^T 计算、Mask、Softmax 和与 V 相乘的全部操作。
      • 只将最终计算好的那一小块输出结果写回到慢速 HBM 中。
      • 关键:那个巨大的 N×N 中间矩阵,自始至终从未被完整地创建和写入到慢速 HBM 中。它只在高速 SRAM 的“小作坊”里昙花一现。
    4. 在线 Softmax:它使用了一种巧妙的数值稳定技巧,可以在不看到全局的情况下,分块地计算出完全正确的 Softmax 结果。
  • 效果:通过最大化高效率的片上计算,最小化低效率的内存读写,FlashAttention 将注意力的实际复杂度从 O(N2) 降低到了接近线性的 O(N),使得在数万长度的序列上训练 Transformer 成为可能。

3.2 长文本处理策略

当序列长度超出了模型本身或硬件的极限时,我们需要一些工程策略来处理。

  • 截断/分块 (Truncation/Segmentation)
    • 做法:最简单粗暴的方法。直接将长文本切成固定长度(如 512)的块,分别输入模型,最后再对结果进行聚合(如取平均)。
    • 缺点:完全丢失了块与块之间的上下文信息。
  • 滑动窗口 (Sliding Window)
    • 做法:同样是分块,但在块与块之间设置重叠区域 (Overlap)。例如,第一块是 [0..512],第二块可以是 [384..896]
    • 优点:通过重叠部分,模型能够感知到一部分跨块的上下文,效果优于简单截断。
  • 稀疏注意力模型 (Sparse Attention Models)
    • 做法:从模型架构层面进行修改,不再让每个 token 都关注所有其他 token。
    • 例子Longformer 模型使用了局部窗口注意力(每个 token 只关注自己附近的一小块区域)和全局注意力(少数几个重要的 token 可以被所有 token 关注)的组合。
    • 注意:这类方法是标准注意力的一种近似,会损失部分信息,而 FlashAttention 是对标准注意力的精确加速

3.3 QLoRA 能用 FlashAttention 吗?

答案是:不但能,而且是“天作之合”!

在实际应用中,将 QLoRA 和 FlashAttention 结合使用,是当前在有限资源下最高效地微调长文本大语言模型黄金标准方案

它们之所以能完美配合,是因为它们解决了两个正交的、互补的问题。

  • QLoRA 的职责:解决“静态存储”问题
    • 目标:降低模型权重参数 (Weights) 的存储体积。
    • 手段:通过 4-bit 量化,将一个原本需要 140GB 显存的 70B 模型压缩到约 35GB,使其能够**“装进”** 单张 GPU。
    • 关注点:模型文件本身的大小。
  • FlashAttention 的职责:解决“动态计算”问题
    • 目标:降低模型在前向/反向传播过程中,中间激活值 (Activations) 产生的内存和 I/O 开销。
    • 手段:通过 I/O 感知的算法,避免生成巨大的中间矩阵。
    • 关注点:计算过程中的效率和临时内存占用。

它们如何协同工作?

设想一下用 QLoRA + FlashAttention 微调一个长文本任务的完整流程:

  1. 模型加载:首先,QLoRA 发挥作用。一个巨大的、被量化成 4-bit 的基础模型和微小的、16-bit 的 LoRA 适配器被加载到 GPU 显存中。(解决了静态存储问题)
  2. 前向传播开始:当数据输入到模型的某个注意力层时:
    a. QLoRA 将当前计算需要的一小部分 4-bit 权重即时反量化成 16-bit。
    b. 用这些 16-bit 的权重去计算出 16-bit 的 Q, K, V 矩阵。
  3. 注意力计算:此时,FlashAttention 接管工作。
    a. 它接收 16-bit 的 Q, K, V 矩阵。
    b. 开始它的“分块+融合”计算流程,高效地算出注意力输出,全程避免了生成那个巨大的 N x N 矩阵(解决了动态计算问题)
  4. 反向传播:梯度计算的过程也同样受益于 FlashAttention 的优化。

总结:QLoRA 解决了**“模型太大放不进显卡”的问题,而 FlashAttention 解决了“序列太长导致计算过程爆炸”**的问题。两者联手,使得我们可以在单张消费级或专业级 GPU 上,高效地微调一个百亿级别参数的大模型来处理数万长度的超长文本,这在以前是完全无法想象的。

V. 大语言模型(LLM)的训练与应用实践

1. 参数高效微调 (PEFT)

  • 核心动机
    对一个拥有数十亿甚至上百亿参数的大语言模型进行全量微调(即更新所有参数),其代价是极其高昂的。不仅需要海量的计算资源,更需要巨大的 GPU 显存来存储模型参数、梯度、以及像 Adam 这样的优化器所产生的额外状态。PEFT 的目标就是,在尽可能少地改动原模型的前提下,达到接近全量微调的效果
  • 基本思路
    冻结大部分原始模型的参数,只向模型中注入一小部分可训练的“适配器”模块,在微调时,只更新这些新增的、极少数的参数。

1.1 LoRA 的核心原理:低秩适应、冻结参数与“旁路”训练

LoRA (Low-Rank Adaptation) 是目前最成功、应用最广泛的 PEFT 方法之一。

  • 核心洞察 (Low-Rank Hypothesis)
    一个预训练好的大模型在适配下游新任务时,其参数的“改变量” ΔW 是具有“低内在秩”的。通俗地说,这个巨大的改动矩阵,可以被近似地分解为两个非常“瘦长”的小矩阵 B⋅A 的乘积。
  • 实现机制:注入“旁路”
    1. 冻结原始权重:将 Transformer 中需要微调的层(通常是 QKV 投影层和 MLP 层)的原始权重矩阵 W 完全冻结,使其在训练中不被更新。

    2. 创建低秩旁路:在原始权重旁边,并联一个新的、由两个低秩矩阵 A 和 B 组成的“旁路”。

      • 矩阵 A 的维度是 d x r(d是输入维度,r是极小的秩,如8)。
      • 矩阵 B 的维度是 r x k(k是输出维度)。
      • 这两个小矩阵 A 和 B 是可训练的
    3. 合并计算:对于输入 x,最终的输出是“主路”和“旁路”输出的加和。

      h=Wx+B(Ax)=(W+BA)x

    4. 训练过程:在训练时,只有矩阵 A 和 B 的参数会被梯度更新。因为 r 非常小,所以需要训练的参数量只有全量微调的 0.1% ~ 1%,极大地降低了计算和存储开销。

  • 优点
    • 高效:训练速度快,显存占用小。
    • 易于部署:对于不同的任务,只需要保存各自训练好的、非常小巧的 LoRA 权重(A和B),而共享同一个巨大的基础模型。

1.2 QLoRA 的核心原理:4-bit 量化、块缩放与即时反量化

QLoRA 将 LoRA 的效率推向了极致,解决了在微调时加载整个基础模型的显存瓶颈问题。

  • 核心思想
    既然基础模型 W 在 LoRA 训练中是被冻结的,我们是否可以用一种精度更低、更省空间的方式来存储它呢?答案就是量化 (Quantization)
  • 关键技术
    1. 4-bit 量化 (NF4)
      • 做法:QLoRA 将原来用 16-bit (FP16/BF16) 存储的基础模型权重,压缩成用一种新的、信息论最优的 4-bit NormalFloat (NF4) 数据类型来存储。
      • 效果:仅此一项,就将基础模型的显存占用降低为原来的 1/4
    2. 块自适应缩放 (Block-wise Scaling)
      • 做法:为了提高量化精度,模型权重被分成很多小块(如256个元素一组)。每一块都独立计算一个缩放因子
      • 效果:这使得量化过程可以自适应地处理不同数值范围的权重,极大地减小了量化误差。
    3. 即时反量化 (On-the-fly Dequantization)
      • 工作方式:在模型进行前向传播时,计算需要用到哪一小块权重,就即时地将这一块 4-bit 的数据反量化回 16-bit 的计算精度。计算完成后,这个 16-bit 的临时数据立刻被丢弃,显存中只保留 4-bit 的版本。
      • 效果:这保证了计算的精度,同时维持了极低的常驻显存占用。

2. 解码与生成策略:控制模型的“创造力”

LLM 在生成文本时,本质上是在每一步都计算出一个覆盖整个词汇表的概率分布。解码策略就是我们从这个概率分布中挑选下一个词的方法。

  • Greedy Search (贪心搜索)
    • 做法:最简单直接的方法,每一步都选择当前概率最高的那个词。
    • 问题:虽然安全,但极其容易导致生成内容枯燥、重复,甚至陷入“我我我我……”这样的死循环。它只顾眼前最优,可能会错失全局更优的句子。
  • Top-k Sampling (Top-k 采样)
    • 做法:为了增加多样性,我们不再只看第一名,而是划定一个范围。
      1. 找出概率最高的 k 个候选词。
      2. 在这 k 个词内部,按照它们自身的概率分布进行随机采样
    • 效果:通过引入可控的随机性,生成的内容变得更生动、更多样。k 是一个超参数,k 越大,随机性越强;k 越小,生成结果越稳定。
  • Top-p (Nucleus) Sampling (Top-p / 核心采样)
    • 做法:这是目前更受青睐的一种方法,它比 Top-k 更智能、更自适应。
      1. 它不固定候选词的数量 k,而是设定一个累积概率阈值 p(例如 0.95)。
      2. 从概率最高的词开始逐个累加,直到这部分词的总概率超过 p 为止,形成一个“核心候选集 (Nucleus)”。
      3. 在这个动态大小的核心候选集中进行随机采样。
    • 优点
      • 当模型对下一个词非常**“确定”**时(例如,“法国的首都是” -> “巴”的概率极高),核心集会很小,保证了事实的准确性。
      • 当模型非常**“不确定”**时(例如,写故事的开头),核心集会很大,允许模型进行更有创造性的探索。

2. Decoder-Only 架构成为主流的原因

近年来,我们看到像 GPT、Llama、PaLM 等顶级模型,都纷纷采用了 Decoder-only 架构,而不是像 T5、BART 那样的 Encoder-Decoder 架构。

  • 原因一:任务的统一与架构的简洁
    • Encoder-Decoder 架构天然为**“源-目标”**式的转换任务(如翻译、摘要)而生,它需要一个 Encoder 先完整理解源文本。
    • Decoder-only 架构的本质是自回归 (Autoregressive),即“根据上文预测下文”。事实证明,几乎所有的自然语言任务都可以被统一地、巧妙地转化为这种**“提示工程/文本补全 (Prompting/Completion)”**的范式。无论是问答、分类还是生成,都可以通过设计不同的 Prompt 来让模型“续写”出答案。这种“万物皆可续写”的模式,使得更简洁的 Decoder-only 架构足以应对绝大多数场景,工程上更具优势。
  • 原因二:预训练任务的强大与通用
    • Decoder-only 模型的核心预训练任务极其简单且强大:“预测下一个词 (Next Token Prediction)”
    • 当这个简单的任务在一个接近“无限”的、高质量的互联网语料上进行训练时,模型为了降低预测的困惑度,被迫学习到了关于语法、事实、逻辑、风格等极其丰富的世界知识。
    • 这个单一、通用的预训练目标,被证明是创造一个强大“通才”模型的极佳路径。

3. 知识蒸馏技术(以 DistilBERT 为例)

  • 核心目的:进行模型压缩。将一个庞大、精确但笨重的“教师模型”(如 BERT),其所学到的知识,“蒸馏”到一个更小、更快的“学生模型”(如 DistilBERT)中,以方便实际部署。
  • 核心思想:学习“软标签 (Soft Labels)”
    • 硬标签:是数据集中非黑即白的标准答案,例如一张图片,标签是 [0, 0, 1, 0],代表“猫”。
    • 软标签:是“教师模型”经过思考后,输出的带有不确定性的概率分布。例如,教师模型可能认为图片是 [狗:0.05, 虎:0.04, 猫:0.90, 兔:0.01]
    • 蒸馏的精髓:学生模型不仅要学习硬标签(学会做对题),更要学习教师模型的软标签(学会老师的“解题思路”)。软标签中蕴含的类别间的相似性等知识,是硬标签无法提供给学生模型的宝贵信息。
  • DistilBERT 的损失函数
    为了让学生全方位地模仿老师,DistilBERT 在训练时会同时优化三个目标:
    1. 监督学习损失 (L_CE):学生模型的预测结果与真实硬标签之间的损失。确保学生能做对题。
    2. 蒸馏损失 (L_Distill):学生模型的软标签与教师模型的软标签之间的差异(通常用 KL 散度衡量)。这是模仿老师“思路”的核心。
    3. 特征对齐损失 (L_Cos):鼓励学生模型的中间层隐状态向量与教师模型的隐状态向量在方向上保持一致(用余弦相似度衡量)。这是在模仿老师“思考的中间过程”。

通过这种方式,DistilBERT 在参数量减少 40%、速度提升 60% 的情况下,依然能保留 BERT 约 97% 的性能。

3. LLM 训练中的显存占用分析

GPU 显存是训练大模型最宝贵的资源。一份完整的“显存账单”主要包括以下四个部分:

  1. 模型参数 (Model Parameters)
    • 是什么:模型自身的权重(weights)和偏置(biases)。
    • 特点:这是“静态”成本,一旦模型架构和精度(如 FP16/BF16)确定,这部分大小就固定了。
    • 例子:一个 7B(70亿)参数的模型,用半精度(2字节/参数)存储,需要 7B × 2 = 14 GB 显存。
  2. 优化器状态 (Optimizer States)
    • 是什么:优化器为每个可训练参数存储的额外信息。
    • 特点:这是最容易被忽略的“隐藏”成本。对于 AdamW 优化器,它需要为每个参数存储一阶动量二阶动量,这相当于将参数的显存需求乘以了 2
    • 例子:对于上述 7B 模型进行全量微调,优化器状态就需要 14 GB × 2 = 28 GB 显存。
    • 启示:这也是为什么 LoRA/QLoRA 如此高效的原因之一,因为它们只训练极少数的适配器参数,所以优化器状态的显存开销也极小。
  3. 梯度 (Gradients)
    • 是什么:反向传播时为每个可训练参数计算出的梯度值。
    • 特点:其大小与可训练参数完全一致。例如,全量微调 7B 模型,梯度也需要 14GB 显存。
  4. 激活值 (Activations)
    • 是什么:模型在前向传播过程中,每一层网络计算产生的中间输出。这些输出需要被存储下来,以供反向传播时使用。
    • 特点:这是“动态”成本,其大小与批次大小 (Batch Size)序列长度 (Sequence Length)模型层数等因素成正比。这是我们在显存不足时,最常用来“开刀”进行调整的部分。

PyTorch 中多损失函数的 backward 方法

  • 场景:在多任务学习、知识蒸馏等场景中,我们常常需要同时优化多个损失函数。
  • 错误的做法:对每个 loss 单独调用 loss.backward()
    • 原因:PyTorch 在默认情况下,执行完一次 .backward() 后,为了释放内存,会立即销毁计算图。当第二次调用 .backward() 时,就会因找不到计算图而报错。
  • 不推荐的做法loss1.backward(retain_graph=True)loss2.backward()
    • 原因:这会强制保留计算图,不仅消耗大量额外显存,而且对于简单的多任务场景,这种做法在梯度累加的逻辑上也是不正确的。
  • 正确且标准的方法:先求和,再反传
    • 做法:将所有损失函数按照一定的权重(权重也可以是1)相加,形成一个最终的总损失,然后对这个总损失调用一次 .backward()。Python

      total_loss = w1 * loss1 + w2 * loss2 total_loss.backward()

    • 原理:基于微积分的链式法则,梯度的加法等价于加法的梯度 ∇(L1+L2) = ∇L1 + ∇L2。这种方法在数学上完全等价,且计算效率最高。

model.eval() 的作用及其对现代 LLM 的影响

  • model.eval() 的两大作用
    1. 关闭 Dropout:将模型中所有的 Dropout 层的丢弃概率设为 0。这是为了在推理时使用模型的全部能力,并得到一个确定的输出。
    2. 切换 BatchNorm (BN):将模型中所有的 BN 层切换到评估模式。此时,BN 不再使用当前批次的均值和方差,而是使用在整个训练集上学习到的、固定的全局统计量。
  • 对现代 LLM 的影响:基本无效
    • 这是一个非常好的面试“陷阱”题。虽然 model.eval() 在 ResNet 等 CV 模型中至关重要,但对于当前主流的、基于 Transformer Decoder 的大语言模型(如 Llama, GPT系列),调用它几乎没有任何作用
    • 原因
      1. 这些模型不使用 BatchNorm,而是使用 LayerNorm。而 LayerNorm 在训练和推理时的行为完全一样,不受 eval() 模式的影响。
      2. 这些模型在训练时,也普遍不使用或很少使用 Dropout。它们更依赖于海量数据和其他正则化手段。
    • 结论:知道这一点,能体现出你对模型架构具体实现的深入了解。

神经网络在特定领域(如风控)的挑战

  • 1. 可解释性 (Interpretability):神经网络是一个“黑盒”,其决策过程难以向业务方或监管机构解释。而在风控领域,决策的“可解释性”是硬性要求。
  • 2. 对稀疏噪声数据的敏感性 (Sensitivity to Sparse & Noisy Data):风控数据通常是高维稀疏的,且可能包含噪声或异常点。神经网络强大的拟合能力有时反而会学到这些噪声,导致模型鲁棒性差。
  • 3. 难以融合专家规则 (Difficulty Integrating Expert Rules):风控领域高度依赖专家经验(Feature Engineering)。相比于能清晰体现“IF-THEN”规则的树模型(如 GBDT),神经网络更像一个“大力出奇迹”的模型,难以将专家规则直观地融入其中。
  • 业界常用解决方案:GBDT + NN:结合 GBDT 强大的特征交叉与筛选能力,和神经网络强大的非线性拟合能力。先用 GBDT 对原始特征进行处理,生成一组新的、更有信息量的特征,再将这些新特征输入神经网络进行最终的预测。