示例:从两句话到三维表示
本例衔接基础篇F01—F06,用两句话和一张小型参数表,串联编号、组批、形状与查表。先计算小规模表示,再解释[2,128,4096]。所有数字都可以手算。
本章目录
Embedding是查一行,不是把编号转二进制
一组浮点数怎样替代一个Token编号?
词表编号像目录的页码;Embedding像打开那一页看到的数值卡。页码2并不会通过“转成0010”得到语义。模型取到的是参数表里事先存好的小数和负数,后续层再根据上下文加工它。
输入与适用条件:完全沿用F05正文6×4小数参数表。图示的维度是特征宽度,不是二进制位数。
1 / 5 · 为“我”取第1行
窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。
编号1定位Embedding表的第1行,得到[0.2, -0.1, 0.7, 0.4]。这一行用来表示本例Token“我”,不会因为同批还有其他句子而改为另一行。
E[1] = [0.2, -0.1, 0.7, 0.4]默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。
为什么需要
编号方便定位项目,但直接比较编号大小不能表达任务所需关系。
怎样实现
为每项取出可训练的向量,再交给后续运算。
可能的代价与误解
误把0/1教学矩阵当成所有Embedding的格式;误以为同一个词在所有层都不变。
如何判断是否做对
逐项检查原文、编号、取表行号、输出行顺序四者一一对应。
1.文字如何转换为编号
我们用两条文字:“我爱学习”和“你爱”。分词器是一套把文字片段对应到整数编号的规则。本页人为规定它把第一句分成“我/爱/学习”,第二句分成“你/爱”。这只是为了算得清楚,不是任何商业模型的真实分词结果。
| 文本片段 | 我 | 爱 | 学习 | 你 | ! | 填空用的特殊项PAD |
|---|---|---|---|---|---|---|
| 教学编号 | 1 | 2 | 3 | 4 | 5 | 0 |
因此,第一句成为[1,2,3],第二句成为[4,2]。一个编号只回答“是哪一项”,不回答“意思是什么”。4比1大,不表示“你”比“我”更重要。
现在还没有预测答案。我们只把文字换成了以后可以拿来找位置的编号。
2.“组批”到底组了什么
把两条输入交给同一次运算,一起处理,叫组批。这一组是一个批次(batch);批里有2条输入,所以批大小B=2。
这里B只是“有几条”的缩写。没有引入神秘数学对象。两条输入依然各算各的,不能把第二条用户输入当成第一条用户的上下文。
但两条编号列表一条长3、一条长2。为了先用普通规则表格计算,我们给短的那条末尾放一个占位编号0:
第0条输入:1 2 3 真实有3个片段
第1条输入:4 2 0 真实有2个片段,末尾0只是填空
这个动作叫补齐(padding)。补齐后每行安排3个位置,写作T=3。T说的是安排了几个位置;第二句话真实长度仍是2。占位项不是模型突然理解出的一个新词。[F-PADDING]
3.为什么编号表写成[2,3]
两行,每行三个编号,所以表的大小写成[2,3]。这种描述大小的写法叫形状(shape)。
“里面存着[[1,2,3],[4,2,0]]”是在说内容;“形状是[2,3]”是在说大小。不要把两者混淆。编号表含2×3=6个整数。
在机器学习程序中,像这样规则排列的一组数字叫张量(tensor)。本页先把它理解成“数字容器”:一排数、表格、若干张同样大的表,都可以用张量来装。两行三列的编号表是一个有两个轴的张量:第一个轴选哪句话,第二个轴选那句话里的哪一个位置。[F-TENSOR]
4.为什么还要把编号换成一排数
编号可以找到某一项,但直接比较编号大小没有语义意义。模型为每个词表项准备一排可以被后续计算使用的数字,把这些行放成一张表,叫Embedding表(嵌入表)。
先不去猜每个数代表什么意思。本页每行只放4个数,完整表如下。它们是人为设置的小数和负数,不是真实语言模型训练得到的参数。采用小数而不是只有0和1,是为了避免把Embedding误认成二进制编码:
| 查找编号 | 对应片段 | 第0个数 | 第1个数 | 第2个数 | 第3个数 |
|---|---|---|---|---|---|
| 0 | PAD | 0.0 | 0.0 | 0.0 | 0.0 |
| 1 | 我 | 0.2 | -0.1 | 0.7 | 0.4 |
| 2 | 爱 | -0.3 | 0.8 | 0.1 | 0.5 |
| 3 | 学习 | 0.6 | 0.2 | -0.4 | 0.9 |
| 4 | 你 | 0.1 | -0.5 | 0.9 | 0.3 |
| 5 | ! | -0.2 | 0.4 | 0.5 | -0.6 |
这张Embedding表有6行、4列,形状是[6,4]。6是词表项数:一共有多少种可以查找的编号。4是表示宽度d:每项用几个数表示。本页d=4。
编号、二进制、One-hot与Embedding不是一回事:“爱”的编号为2;如果只讨论它在电脑里如何以8位保存,可以写成00000010;如果用长度6的独热标识,可以写成[0,0,1,0,0,0];但本例真正查出的Embedding是[-0.3,0.8,0.1,0.5]。第三个轴就是这一排4个浮点数的索引方向,不是二进制位数。实际查表只需要编号,不需要先构造One-hot。
6、3、4分别回答不同问题。 词表有6种可用项,不意味着一句话必须长6;一句话安排3个位置,不意味着每项只用3个数;每项用4个数,也不意味着一定有4个能人工命名的语义属性。
5.“查Embedding”真的只是先找到对应的行
先看第一句“我爱学习”:按本例的Token划分,它依次是“我/爱/学习”,所以编码后的编号才是[1,2,3]。这里不是拿三个随机数字找行,而是依次把原句的三个Token转换成Embedding向量:编号1对应“我”,编号2对应“爱”,编号3对应“学习”。
“我” (编号1) → [0.2,-0.1,0.7,0.4]
“爱” (编号2) → [-0.3,0.8,0.1,0.5]
“学习” (编号3) → [0.6,0.2,-0.4,0.9]
于是第一句成为:
[[0.2,-0.1,0.7,0.4],
[-0.3,0.8,0.1,0.5],
[0.6,0.2,-0.4,0.9]]
原来“我”这个位置只存编号1;现在这个位置对应一排[0.2,-0.1,0.7,0.4]。并没有把1转换成四位二进制码,而是按编号1读取参数矩阵的一整行。这样的每个Token向量以后还会参与注意力计算。[F-EMBED]
第二句则是“你爱”:它实际只包含“你/爱”两个Token,编码为[4,2]。为了和第一句放在同一批次里计算,才补成[4,2,0]。这表示“你”(4)、“爱”(2),加上一个不属于原句的PAD占位(0)。因此依次查表取的是“你”的向量、“爱”的向量,以及PAD占位的向量:
第二句成为:
[[0.1,-0.5,0.9,0.3],
[-0.3,0.8,0.1,0.5],
[0,0,0,0]]
这不是把“你爱”编码成三个真实词:第三行只是为了补齐到长度3。查表得到的仍是逐Token的初始数值表示,而不是对完整句意的一次理解。注意“爱”在两句话里,初次查表得到同一排数。这不表示它永远不看上下文;后面的网络会把当前位置与允许读取的其他位置结合,产生新的表示。查表是起点,不是全部理解过程。
6.现在为什么多出一层[2,3,4]
把两句话的结果合在一起,是两张表,每张3行、每行4个数。于是整体形状为[2,3,4]。
| 形状中的数 | 这里具体数什么 |
|---|---|
| 2 | 两条输入,因此有两张表 |
| 3 | 每条输入安排三个位置,因此每张表三行 |
| 4 | 每个位置用四个数,因此每行四列 |
总共2×3×4=24个数。原来6个编号,各自换成4个数,所以也可以算成6×4=24。
从下标理解第三个轴:第一个下标选择“我爱学习”还是“你爱”;第二个下标选择其中的哪个位置;第三个下标选择该Token向量的第几个数。例如H[0,1,0]=-0.3,是第一句话的第二个Token“爱”的初始表示第一个数。新增的是每个位置的数值特征轴,不是额外的词或二进制位。
“这是三维张量”说有三个轴;“每个位置是四维向量”说每排有四个数。这里维的两种说法要区分。向量就是此处有顺序的一排数,不需要先想象四维空间。
7.将表示扩展为[2,128,4096]
现在只换三个大小,不换计算道理。
- B=2:本批两条输入。
- T=128:每条输入补齐或安排到128个位置。
- d=4096:每个位置查表后用4096个数表示。
编号表有两行、每行128个整数,所以是[2,128]。每个整数再换成长度4096的一排数,整体就是[2,128,4096]。
它含2×128×4096=1,048,576个数。4096不是Token数,不是词表大小,也不是整个模型的参数总量。 它叫隐藏维度或隐藏表示宽度,意思是模型用这么宽的一排数来表示每个位置。更深的层通常还沿用这个宽度,但会改变数字内容。
若每个数用2字节保存,这一份输出约占2MiB。字节是存储大小的单位,MiB是1,048,576字节。这个估算只针对这一份数据,不包含模型全部参数、缓存或临时计算空间。F06会完整讲存储精度与设备。
8.“放到设备”没有改变这句话的意思
设备在这里指执行计算的处理器,例如CPU或GPU。CPU是通用处理器,GPU擅长并行执行大量合适的数值运算。为了让运算执行,程序要让输入数据与模型参数位于相容的设备和存储格式中。
可以把[1,2,3]存到内存,再复制到GPU显存中;它们仍然是相同编号,并没有因为移动就变得更“智能”。显存是GPU可直接使用的一类存储空间。移动数据可能花时间,空间也可能不够,所以这是实在的工程步骤。
在常见输入Embedding层中,查找地址使用整数编号,取出来的参数通常是浮点数;浮点数就是可以近似表示0.1、−0.7等数值的计算机数值格式。设备、格式和形状是三种不同属性。
9.还要带哪几份“辅助信息”
第一份是填充掩码:告诉后续计算哪些位置是真的。
第一句:[1,1,1] 都是真的
第二句:[1,1,0] 最后是补齐位置
这里的0/1不是Token编号,而是“能不能算作有效内容”的标记。即使PAD的Embedding恰好全0,也不代表后续计算自然会忽略它,因此仍需正确处理这些位置。
第二份是注意力掩码(attention mask)。注意力先理解为“为当前这个位置挑出可以参考的位置,再按不同份额结合它们的数字”。掩码决定“能参考谁”,不是决定“参考多少”。后者要到F09–12用加权平均算出来。
生成下一Token时,训练与运算通常要遵守不能偷看未来的规则。对三位置例子,可以读的位置是:
| 当前查询位置 | 能参考位置0吗 | 位置1吗 | 位置2吗 |
|---|---|---|---|
| 位置0 | 能 | 不能 | 不能 |
| 位置1 | 能 | 能 | 不能 |
| 位置2 | 能 | 能 | 能 |
这是因果掩码。第二句话还必须把作为填充项的位置2排除。模型内部不同接口使用的掩码形状和真假含义不一定相同;本页表格只解释可见关系,不冒充某个API的入参。
第三份是位置编号(position IDs)。比如真实的三个位置记为0、1、2。Token ID回答“哪个片段”,位置ID回答“出现在哪里”,二者不是同一列。采用左补齐或缓存时,逻辑位置与表格列号可能不同,F13会分别举例。
最后可能有KV缓存(KV Cache)。缓存就是保存已经算过、后面还能复用的结果。第一次从头处理时通常没有历史缓存;继续生成时,可以带回之前各层算好的部分数字,避免从头重算。这不是长期记忆,也不是新知识进入模型参数。K和V的含义、为什么可以复用、哪些改动让它失效,会先在F11解释注意力,再在F16完整推演;此页不要求先背这两个字母。[F-CACHE]
10.完整过程小结
把两条独立输入一起计算,并把较短的编号列表补到本批安排的长度。由此得到一张两行、每行128个编号的表。模型根据每个编号,从自己的Embedding参数表中取出一排4096个数,所以结果是两张128行、4096列的表。程序同时准备有效位置、哪些位置能互相参考、各个真实片段的位置编号;若这是继续生成,还带入可复用的历史计算结果。随后开始各层的注意力与其他数值变换。Embedding查表本身已经属于模型数值表示的计算步骤,不是查完之后才第一次碰到神经网络。
你现在不需要先理解Attention的完整公式,便能解释这一步的每个对象。下一段若直接出现Q、K、V或softmax,先走F07→F09→F11;不是靠猜缩写读下去。
自检:请先口头回答,再看答案
问题一:词表1000项、三条输入、每条10个位置、每项用8个数表示,Embedding表和查表结果分别多大?
查看参考答案收起参考答案
[1000,8],编号表是[3,10],查表结果是[3,10,8]。1000决定可以查多少种条目,3和10决定这一批查了多少个位置。问题二:为何同样写0,有时表示PAD编号,有时表示掩码里的不能看?
查看参考答案收起参考答案
input_ids的0用于找词表行;有效性或可见性表里的0是一条运算标记。不能只看数字相同就混为一谈。问题三:Embedding的第2个数是不是永远表示情绪?
查看参考答案收起参考答案
问题四:组批时第二个用户会读取第一个用户的内容吗?