EECS498 · Transformer I · 09
From Raw Sequence to Embedding
Transformer 真正开始计算时没有字符串,只有一批整数,以及一张会被 gradient 更新的向量表。这一课把两者之间每一步都走完。
30-second recall
30 秒回忆
你实现了 generate_token_dict、prepocess_input_sequence 和 positional encoding,并用 nn.Embedding 接收 arithmetic data。
- token ID 为什么不是语义数字?
- embedding table 的一行代表什么?
- 为什么没有 position 的 attention 无法可靠区分顺序?
Before We Start · 先诊断记忆
1. 输入 ID shape 为 [32,9],embedding dim 为 64,输出 shape 是?
2. 两句话中的 bank 有同一 token ID。进入第一层前的 token embedding 呢?
3. 哪个对象通常不接收 gradient?
BOS NEGATIVE 30 subtract NEGATIVE 34 EOS → BOS POSITIVE 04 EOSProblem · 模型到底接收什么?
原始字符串是人类界面;矩阵乘法需要有固定 shape 的数值 tensor。一个看似简单的“把文字变成数字”,实际包含几个不同对象:
负 或 3WI22 toy tokenizer 没有经过 UTF-8/BPE 管线:空格切词后,特殊单词保持整体,数字字符串按 digit 拆开。列出 Unicode 层次只是为了精确区分对象,不是说你当年的代码实现了现代 tokenizer。
Naive solution · 直接把字符串或数字喂进去?
把 30 当作浮点数 30,会暗中规定“30 比 3 大十倍”这一几何关系,也无法表示 BOS。把每个完整表达式分配一个 ID,则新表达式没有可复用结构。你的 digit vocabulary 采用了更小的离散积木:10 个数字和少量操作符可以组合出未见的两位数。
模型不必重新组合十位/个位,但词表会扩大,而且对未列入词表的数字没有自然分解。tokenization 是建模选择,不是无害的预处理细节。
Worked Example · 从原始文本走到 tensor
raw string
"BOS NEGATIVE 30 subtract NEGATIVE 34 EOS"
↓ split + special-token / digit rule
[BOS, NEGATIVE, 3, 0, subtract, NEGATIVE, 3, 4, EOS]
↓ vocabulary lookup
[14, 11, 3, 0, 13, 11, 3, 4, 15] [K=9]
↓ stack N examples into a batch
token_ids [N,9]
↓ row lookup in E ∈ ℝ^(16×M)
token_embeddings [N,9,M]
↓ add position-dependent vectors P
Transformer input X [N,9,M]注意 vocabulary size V=16 没有成为 activation 的最后一维。ID 11 直接选择参数表 E 的第 11 行;lookup 的结果是长度 M 的向量。
Mechanism · Embedding table 到底是什么?
- 参数:
E ∈ ℝ^(V×M)。若 V=16、M=64,就有 16×64 个可训练数。 - 查表:
X[n,i] = E[token_ids[n,i]]。 - 重复 token:同一 ID 初次查到同一行;此处尚未读取上下文。
- 反向传播:loss 对被访问行产生 gradient;optimizer 更新这些行。
token_ids[N,K] · integer indices · 非 parameterE[V,M] · float parameterE[token_ids][N,K,M] · activation∂L/∂E[V,M] · gradientembedding 起初不必“有意义”。它只是可学习坐标;训练任务持续推动某些 rows,使它们成为对预测有用的起点。语义是训练结果和后续网络共同形成的,不是 ID 自带属性。
Inline check. 若交换 vocab 中 ID 11 与 3,同时交换 embedding table 的第 11、3 行,其他所有输入 ID 也按新字典编码,模型行为怎样?
Token ID 是地址;embedding table 是会学习的记忆表;lookup 得到的是无上下文起点,不是词在这句话中的最终含义。
Your Code · 两个阶段,两种职责
Your code · 2023
win22/A5/transformers.py · prepocess_input_sequence()
for letter in input_str.split(" "):
if letter in token_dict:
out.append(token_dict[letter])
else:
for digit in letter:
out.append(token_dict[digit])token_dict 决定离散映射;这段代码不含可训练 tensor,也不会收到 gradient。
Your code · 2023
win22/A5/transformers.py · Transformer
self.emb_layer = nn.Embedding(
num_embeddings=vocab_len,
embedding_dim=emb_dim,
)
q_emb = self.emb_layer(ques_b)
q_emb_inp = q_emb + ques_posques_b [N,K] 查表后成为 q_emb [N,K,M]。相加要求 ques_pos 可 broadcast 到同一 shape;最后一维 M 没变,后续 block 因而仍使用统一接口。
Why Position? · Attention 本身没有“第几个”
考虑 cat eats fish 与 fish eats cat。若输入只包含三张 token cards,自注意力会随 cards 的排列同步重排输出——它是 permutation-equivariant,但没有独立顺序来源。模型知道集合里有 cat/eats/fish,却不能把“第一个名词”和“第三个名词”的角色固定到位置上。
位置注入做的是:
X_i = E[token_i] + P_i
E[token_i] : 这个离散符号是什么
P_i : 它处在序列的哪个位置
X_i : 后续 Q/K/V projections 可以同时利用两类信号
P_i 不是噪声。它是系统性的、随位置变化的 signal。attention 可以学习“读取前一个位置”“区分十位与个位”,因为 WQ/WK 能从混合 representation 中利用位置方向。
每个 token 都得到同一个偏移,位置之间仍不可区分;相当于给所有 cards 贴同一张“位置”标签。
“相同 token 加 position 后就有了上下文”仍不准确。position 让位置可辨;真正依赖其他 tokens 的 contextual representation,要等 attention 把别处的 values 混进来。
Deep Dive · sinusoidal、learned position、RoPE 改变了什么?
Sinusoidal absolute encoding 用确定函数生成 P;learned absolute embedding 把 P 也当参数;RoPE 不直接把一张 P 加到 X,而是在 Q/K feature pairs 上按位置旋转,使 dot product 显式包含相对位置信息。它们都在解决“内容匹配如何感知顺序/距离”,接口不同,问题相同。
Historical implementation note:repository 的 sinusoidal denominator 使用了会把指数取整的表达式;saved Notebook check 与该历史实现一致。今天调试时应先打印多个 dimension 的频率,确认它们没有意外相同,而不是默默把历史代码改写成标准版本。
谁被训练?什么时候改变?
| 对象 | 是否 parameter | 何时改变 |
|---|---|---|
| toy split / vocabulary mapping | 否 | 人工固定 |
| token IDs | 否 | 随输入变化 |
| embedding table E | 是 | optimizer step |
| lookup activation X | 否(中间值) | 随输入与 E 变化 |
| contextual hidden state | 否(中间值) | 随整句与模型参数变化 |
Deep Checks · 不只认名词
V=16、M=64、batch=[32,9]。参数与 activation 各是什么 shape?
E=[16,64],lookup activation [32,9,64]。V 属于表的行数,不留在每个 token vector 中。
同一 digit 3 出现两次:哪一步相同,哪一步可能不同?
基础 row lookup 相同;位置相加后不同;经过 attention 后还会因读取的邻居不同而进一步分化。
为什么不能对 token IDs 直接做普通大小关系运算?
ID 是可任意置换的类别地址。把 11 减 3 没有稳定语义;可学习几何位于 embedding rows 中。
若把 sequence 整体重排且不提供 position,self-attention 输出会怎样?
输出按同样 permutation 重排;网络没有一个外部信号判断哪个排列是原句顺序。
Transfer Question
一个模型用 byte-level tokenizer。字符 é 可能成为两个 UTF-8 bytes,对应两个 token IDs。它进入 Transformer 时是一条“字符”还是两个 positions?embedding table 的行数由什么决定?
核对思路
若 tokenizer 保留为两个 byte tokens,就是两个 positions;表的行数由 tokenizer vocabulary 决定,而不是 Unicode 字符总数或句子长度。
闭卷解释
- 不使用“向量化”这个模糊词,解释 token ID 如何变成 embedding。
- 为什么 embedding 是 parameter,而 tokenizer rule 通常不是?
- 用
cat eats fish解释 position signal 的必要性。 - 为什么 embedding 不等于 contextual representation?
Then I implemented: token dictionary → digit split → nn.Embedding → position add.
Now I understand: tokenizer 固定离散地址,embedding 学习每个地址的连续起点,position 提供顺序坐标;上下文要到下一课的信息路由后才出现。
Connection · 下一课为什么必然出现?
现在每个位置已有 [M] 向量,但 slept 的向量仍没有数值上包含 animal 的信息。下一课要构造一个输入相关的矩阵,让位置 i 真的读取位置 j。
Based on your implementation
- EECS498 WI22 · A5 · Transformers.ipynb
- transformers.py · two_digit_op.json