一字一诗
LESSON 08 / 26

给汉字一组可学习的数字

这一课完成什么

把一个汉字编号变成一组可以学习的数字。第 4 课编号只告诉我们字表中的位置;现在为每个位置准备一行浮点数,供后面的计算使用。这张表在代码里叫 Embedding,中文常译作“嵌入”。

把一个编号,换成一整行数字

字向量就是字表中可学习的一行数字。它起初是随机的,在训练中逐渐变化;不要把某一格直接命名为“春天程度”。

把一个编号,换成一整行数字
用文字逐步读这张图
  1. 输入编号:春 → 1;编号不是含义
  2. 可学习的表:每字对应一行;每行有 C 个数字
  3. 查找第 1 行:[0.2, −0.1, 0.4];教学数值
  4. 同字再出现:仍然查第 1 行;得到相同字向量
  5. 进入后续计算:结合位置与前文;得到不同上下文
  6. 反向更新:用到的数字参与学习;表不再保持初始值
两个“春”的字向量相同,最终表示也相同吗?

不一定。加上不同位置、汇总不同前文以后,表示就会不同。

查表,而不是给编号赋予大小含义

假设字表有 6 个字,每个字用 4 个数表示,那么表的尺寸是 [6,4],共有 24 个可学习参数。输入编号 [1,2,1],就取第 1、2、1 行,输出三行、每行四项。

python
embedding = torch.nn.Embedding(6, 4)
ids = torch.tensor([1, 2, 1])
vectors = embedding(ids)

在同一次模型状态下,两个编号 1 会取到完全相同的行。下一课加上位置表示后,同一个字处于不同位置的最终输入才会不同。

数字最初意味着什么

开始时这些数随机初始化,没有已经学会的古诗含义。训练通过第 7 课的误差反传更新它们。一个字在许多相似上下文中出现,可能被学到与这些上下文有关的规律;但不能把第一维直接称为“山水”,第二维称为“思乡”。向量坐标通常没有这种人工指定的解释。

bash
./poet lesson 8

先看三行数值,确认第一行和第三行相同。再把中间编号也改成 1,三行都会相同。改变表示宽度会改变每行可用的数字数量,也增加模型参数,后面注意力头的尺寸要同步兼容。

在正式模型里找到它

PoetryGPTcharacters 是汉字表,positions 是位置表。汉字表尺寸为“正式字表大小 × 表示宽度”。输入编号 [B,T] 查表后成为 [B,T,C],正好接上第 5 课的数字形状。

python
self.characters = nn.Embedding(config.vocab_size, config.width)
hidden = self.characters(tokens)

这个项目让输出层与输入字表共用参数:self.output.weight = self.characters.weight。这样每个字的一行数字既参与输入表示,也参与最终候选字打分,减少参数数量。共用的是同一份参数,不是训练前复制一次后各自更新。统计参数时不要把它重复算两次。

验收与排错

小练习与答案

字表 6000 个字,宽度 128,这张表有多少参数?换成宽度 256 呢?

查看答案

分别是 768,000 和 1,536,000。宽度翻倍使这张表参数翻倍,但整个 GPT 中还有宽度乘宽度的矩阵,因此总模型参数和计算开销不会都只翻倍。可以在第 26 课比较完整模型的参数统计。

动手补全一小段

先运行上面的完整实验,再复制本课起始文件为自己的练习。starter 有意留空 solve 函数;补全后运行文件,底部检查会告诉你是否符合本课要求。卡住时打开参考答案,比较每一步。

bash
cp lessons/08/starter.py lessons/08/my_exercise.py
# 编辑 my_exercise.py 中的 solve 函数,然后运行:
.venv/bin/python lessons/08/my_exercise.py
# 对照完整答案:
.venv/bin/python lessons/08/solution.py

下载起始代码 · 下载参考答案

展开本课补全练习的完整参考答案
python
"""第 08 课补全练习:按编号查行。修改 solve,保持下方检查不变。"""
import math, io, argparse
import torch
from torch import nn
from torch.nn import functional as F
torch.set_num_threads(2)
torch.manual_seed(26)

def solve(table, ids):
    return table[ids]

table=torch.arange(12).view(3,4)
y=solve(table,torch.tensor([1,2,1]))
assert y.shape==(3,4) and torch.equal(y[0],y[2])
print("本课补全练习通过。")
展开本课完整、可独立运行的实验代码
python
"""本课独立实验;在仓库根目录执行 .venv/bin/python lessons/08/experiment.py。"""
import sys, json, math
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
import torch
from torch import nn
from torch.nn import functional as F
from poetry_gpt.common import ROOT, DEFAULT_DATA, read_json, read_jsonl
from poetry_gpt.model import ModelConfig, PoetryGPT, CausalAttention, Block
from poetry_gpt.data import Tokenizer, SPECIAL, clean_record, keywords_for
from poetry_gpt.labs import show
torch.set_num_threads(2)
torch.manual_seed(26)

embedding = nn.Embedding(6, 4)
ids = torch.tensor([1, 2, 1])
show('查表后的三行', embedding(ids).tolist())
show('相同字编号得到相同向量', torch.equal(embedding(ids)[0], embedding(ids)[2]))
下载本课实验