LLM从0到1 | 从输入维度变换理解Transformer:以Encoder为例

向量数据库大模型机器学习
  1. 简介 =====

本文将从维度变换的视角,详细剖析BERT中输入序列的处理流程——从原始文本输入到Encoder层输出的全过程。通过追踪这些维度的变化,希望有助于更好地理解BERT模型(即Transformer的Encoder部分)的内部运作机制。#AI入门 #Transformer详解 #LLM #LLM从0到1 #原理

更多AI相关欢迎关注微信公众号"小窗幽记机器学习":

  1. 前置说明 =======

以bert-base-uncased模型为例,进行step by step解析:

  • 输入序列长度n (例如,512 是 BERT 的典型最大长度)
  • 批大小batch_size (例如,32)
  • 词表大小vocab_size (例如,30522 对于 BERT-base)
  • embedding_sized_modeld (例如,768 对于 BERT-base,在config.json中的名字是"hidden_size",所以这3个名字都是指同一个意思)
  • 注意力头数h (例如,12 对于 BERT-base)
  • 每个头的维度d_k = d_v = d_head (例如, d_head = d / h = 768 / 12 = 64 对于 BERT-base)
  • 前馈网络隐藏层维度d_ff (例如, 4 * d = 3072 对于 bert-base-uncased该值存于config.json的"intermediate_size"字段)

picture.image

picture.image

补充说明: 之前有小伙伴问我 embedding_sized_model是什么关系,这里做一些补充说明。 其实 embedding_size = d_model,只是称谓不同而已。

  • embedding_size(也称为 d_model): 指的是你将每个输入token转换成的连续向量表示的** 维度大小** 。例如,你有一个包含 30000 个单词的词表, embedding_size=512 意味着每个单词会被表示成一个 512 维的实数向量。
  • d_model:在 Transformer 架构(BERT 基于此)中, d_model 是模型** 内部表示的统一维度** 。它定义了模型在处理序列时,每个时间步(每个token位置)的特征向量的长度。它是 Transformer 块(编码器/解码器层)设计的关键参数。
  • 关系 :在标准的 Transformer 和 BERT 实现中, embedding_size 被设定为等于 d_model 。也就是说,词嵌入向量被直接映射到模型内部工作所需的维度 d_model 上。这是模型设计的一个关键点,它确保了:
  • 可加性 :词嵌入(Word Embedding)、位置嵌入(Positional Embedding)、段嵌入(Segment Embedding)都是 d_model 维的,可以直接相加。
  • 一致性 :所有后续线性变换(如 Q、K、V 投影)、层归一化(LayerNorm)、前馈网络(FFN)的输入和输出在序列长度 n 的每个位置上都保持 d_model 维的特征向量。
步骤0:输入准备
  • 输入数据 :一批句子。每个句子被分词、添加特殊token(如 [CLS] , [SEP] )并填充/截断到长度 n
  • 初始维度(batch_size, n) 。这是一个整数张量,每个元素是词表 vocab_size 中的token ID。
步骤1:嵌入层
  • 词嵌入 (Token Embedding)
  • 操作:将每个token ID 映射到一个 d 维向量。
  • 输入维度: (batch_size, n)
  • 输出维度: (batch_size, n, d) 。现在每个token位置都有一个 d 维向量表示其语义。
  • 位置嵌入 (Positional Embedding)
  • 操作:生成表示每个位置(1 到 n )的 d 维向量。可以是学习的或固定的(如正弦/余弦)。
  • 输出维度: (n, d) 或直接广播到 (batch_size, n, d)
  • 段嵌入 (Segment Embedding - BERT特有)
  • 操作:对于句子对任务(如问答、自然语言推理),区分句子 A 和句子 B。每个句子类型(A 或 B)有一个 d 维向量。
  • 输入:段 ID 张量 (batch_size, n) (通常是 0 或 1)。
  • 输出维度: (batch_size, n, d)
  • 嵌入求和
  • 操作:将 词嵌入

位置嵌入 + 段嵌入 (如果使用)进行 逐元素相加

  • 为什么能相加? 因为三者维度都是 (batch_size, n, d)
  • 输出维度: (batch_size, n, d) 。这是进入 Transformer 编码器层的初始表示 X
步骤2:Transformer 编码器层
  • 输入到第一层X ,维度 (batch_size, n, d)

步骤 2.1:多头自注意力(Multi-Head Self-Attention)

  1. 线性投影 (生成 Q, K, V)
  • 操作:对输入 X 进行三个不同的线性变换(三个独立的权重矩阵 W_Q , W_K , W_V ,每个形状为 (d, d_head * h) = (d, d) ),分别生成查询(Query)、键(Key)、值(Value)向量。
  • 输入维度: (batch_size, n, d)
  • 输出维度: (batch_size, n, d) 。注意:虽然投影到了 d 维,但 d = h * d_head物理上 它是一个 d 维向量, 逻辑上 我们将其视为 hd_head 维的向量。
  • 拆分成多头
  • 操作:将上一步得到的 Q , K , V 张量从 (batch_size, n, d) 重塑 (reshape)(batch_size, n, h, d_head) 。然后 交换维度 (transpose)(batch_size, h, n, d_head) 以便并行计算每个头的注意力。
  • 输入维度: (batch_size, n, d) (Q, K, V 各自)
  • 输出维度: (batch_size, h, n, d_head) (Q, K, V 各自)。现在我们有 h 个独立的注意力头,每个头处理 nd_head 维的向量。
  • 缩放点积注意力(Scaled Dot-Product Attention)
  • Q_i @ K_i^T : 维度 (batch_size, n, n) (注意力分数矩阵)

  • softmax(...) : 维度 (batch_size, n, n) (注意力权重矩阵)

  • softmax(...) @ V_i : 维度 (batch_size, n, d_head) (该头的输出)

  • Q_i : (batch_size, h, n, d_head) -> 取头 i(batch_size, n, d_head)

  • K_i : (batch_size, h, n, d_head) -> 取头 i(batch_size, n, d_head)

  • V_i : (batch_size, h, n, d_head) -> 取头 i(batch_size, n, d_head)

  • 操作:对每个头 i 独立计算: Attention(Q_i, K_i, V_i) = softmax( (Q_i @ K_i^T) / sqrt(d_head) ) @ V_i

  • 输入维度:

  • 计算过程:

  • 输出维度 (每个头): (batch_size, n, d_head)

  • 合并多头(Concatenate Heads)
  • 操作:将所有 h 个头的输出(每个 (batch_size, n, d_head)连接 (concat) 在一起。将维度从 (batch_size, n, h, d_head) (在合并头之前需要先堆叠) 重塑回 (batch_size, n, d) ,因为 h * d_head = d
  • 输入维度 (所有头): (batch_size, h, n, d_head) (注意:步骤3的输出是每个头 (batch_size, n, d_head) ,需要先交换维度回 (batch_size, n, h, d_head) 或直接堆叠)。
  • 输出维度: (batch_size, n, d) 。这是多头注意力机制的组合输出。
  • 输出投影 (线性层)
  • 操作:将合并后的多头输出通过一个线性层 W_O (形状 (d, d) ) 进行投影。
  • 输入维度: (batch_size, n, d)
  • 输出维度: (batch_size, n, d) 。这是自注意力子层的最终输出 Z
  • 残差连接与层归一化 (Add & LayerNorm)
  • Z : (batch_size, n, d)

  • X : (batch_size, n, d)

  • 操作: LayerNorm(Z + X)X 是自注意力子层的输入)

  • 输入维度:

  • 输出维度: (batch_size, n, d) 。层归一化在每个token位置上独立进行,不改变维度。残差连接要求 ZX 维度相同(都是 d 维),因此成立。输出记为 Y

步骤 2.2:前馈神经网络 (Position-wise Feed-Forward Network - FFN)

  1. 第一个线性层 (扩展维度)
  • 操作:将 Y 的每个token位置的 d 维向量独立地通过一个线性层(权重 W1 ,形状 (d, d_ff) ,偏置 b1 )投影到更高维度 d_ff 。通常 d_ff = 4 * d
  • 输入维度: (batch_size, n, d)
  • 输出维度: (batch_size, n, d_ff) 。注意:该变换是 逐位置 (position-wise) 的,每个位置的 d 维向量被独立映射到 d_ff 维。
  • 激活函数 (如 GELU/ReLU)
  • 操作:对 d_ff 维向量逐元素应用激活函数。
  • 输入维度: (batch_size, n, d_ff)
  • 输出维度: (batch_size, n, d_ff)
  • 第二个线性层(降回模型维度)
  • 操作:将激活后的 d_ff 维向量通过另一个线性层(权重 W2 ,形状 (d_ff, d) ,偏置 b2 )投影回 d 维。
  • 输入维度: (batch_size, n, d_ff)
  • 输出维度: (batch_size, n, d) 。这是 FFN 子层的输出 F
  • 残差连接与层归一化 (Add & LayerNorm)
  • F : (batch_size, n, d)

  • Y : (batch_size, n, d)

  • 操作: LayerNorm(F + Y)Y 是 FFN 子层的输入,即自注意力子层的输出)

  • 输入维度:

  • 输出维度: (batch_size, n, d) 。这是该 Transformer 编码器层的最终输出。

步骤 3:重复编码器层
  • 将上一步的输出 (batch_size, n, d) 作为下一个 Transformer 编码器层的输入 X
  • 重复步骤 2.1 和 2.2 L 次(BERT-base L=12)。
步骤 4:编码器输出
  • 经过 L 层编码器后,最终输出维度仍然是: (batch_size, n, d)
  • 这个输出包含了输入序列中每个token位置 ( n 个位置) 的上下文感知的 d 维向量表示。
  • 对于句子级任务(如分类),通常取第一个位置( [CLS] token)的输出向量 (batch_size, d) 作为整个序列的表示,送入任务特定的输出层(如线性分类器)。
  • 对于token级任务(如命名实体识别),每个位置的输出向量 (batch_size, n, d) 都可以被送入任务特定的输出层(如线性分类器作用于每个位置)。
关键维度变化总结表

| 步骤 | 操作 | 输入维度 | 输出维度 | 说明 | | --- | --- | --- | --- | --- | | 输入 |

| (batch_size, n) |

| token ID | | 1. 词嵌入 | Token Embedding | (batch_size, n) | (batch_size, n, d) | d = embedding_size = d_model | | 1. 位置嵌入 | Positional Embedding |

| (batch_size, n, d) |

| | 1. 段嵌入 (可选) | Segment Embedding | (batch_size, n) | (batch_size, n, d) |

| | 1. 嵌入求和 | Sum | (batch_size, n, d)

x 3 | (batch_size, n, d) |

| | 2.1.1 Q/K/V 投影 | Linear (W_Q, W_K, W_V) | (batch_size, n, d) | (batch_size, n, d) | d = h * d_head | | 2.1.2 拆分成多头 | Reshape/Transpose | (batch_size, n, d) | (batch_size, h, n, d_head) | 逻辑上分离头 | | 2.1.3 注意力 (每头) | softmax(Q_i K_i^T / sqrt(d_k)) V_i | (batch_size, n, d_head) | (batch_size, n, d_head) | 计算在 n x n 注意力矩阵上 | | 2.1.4 合并多头 | Concat/Reshape | (batch_size, h, n, d_head) | (batch_size, n, d) | d = h * d_head | | 2.1.5 输出投影 | Linear (W_O) | (batch_size, n, d) | (batch_size, n, d) |

| | 2.1.6 Add & Norm | LayerNorm(Z + X) | Z: (b, n, d), X: (b, n, d) | (batch_size, n, d) |

| | 2.2.1 FFN 第一层 | Linear (W1) + Activation | (batch_size, n, d) | (batch_size, n, d_ff) | d_ff

通常为 4 * d | | 2.2.2 FFN 第二层 | Linear (W2) | (batch_size, n, d_ff) | (batch_size, n, d) | 投影回 d_model | | 2.2.3 Add & Norm | LayerNorm(F + Y) | F: (b, n, d), Y: (b, n, d) | (batch_size, n, d) | 该编码器层最终输出 | | 3. 编码器输出 (L层后) |

| ... (重复 L 次) ... | (batch_size, n, d) |

|

要点回顾:
  1. 序列长度 n 保持不变 :所有操作(嵌入、自注意力、FFN)都是在序列的每个位置 (batch_size, n, ...) 上独立或交互地进行的,但不会改变序列长度 n
  2. 多头注意力的维度变化 :通过将 d_model 拆分成 hd_head 的头 ( d = h * d_head),在计算注意力时降低了复杂度( d_head 较小),并通过合并操作无缝地回到 d_model 维。
  3. FFN 的维度膨胀 :FFN 先将特征从 d 维扩展到 d_ff(通常是 4*d)以增加模型容量,然后再压缩回 d_model 维,以匹配残差连接的要求和下一层的输入。
  4. 残差连接 :依赖于输入 X 和子层输出 Z/ F维度严格相同 ( d_model 维),这是 embedding_size = d_model 设计的关键原因之一。
  5. 层归一化 :在序列的每个位置上 ( n 个位置) 独立地对 d_model 维向量进行归一化,不改变维度。

通过这个流程,BERT 模型能够将输入的离散token序列 (batch_size, n) 逐步转化为富含上下文信息的连续向量表示 (batch_size, n, d),其中每个位置的 d 维向量都编码了整个输入序列的相关信息。

更多AI相关欢迎关注微信公众号"小窗幽记机器学习":

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论