datawhale:Happy-LLM 打卡-Task03:2.3搭建一个Transformer(tokenizer分词器、Embedding 层token嵌入、位置编码、输出头) 前言该部分[1]主要就是对“输入、输出”部分一些细节的补充了一段话如何变成token矩阵然后输入Task02打卡中的结构里运算再如何转换回一段话。主要涉及到tokenizer分词器、Embedding 层token嵌入、位置编码、输出头等。1tokenizer分词器核心就是把自然语言序列通过分词、查表的方式转化为token id序列。首先来说明一下什么是“token”根据国家数据局的定义[2]词元Token是大模型处理信息的最小信息单元具有智能时代可计量、可定价、可交易的特征。也就是说一段话要被拆分为一串token才能被大模型处理。tokenizer分词器的作用就正是把一段话拆分为一串token然后再转化为token id序列以便于大模型处理。分词器一般是一个相对独立的组件与神经网络本身关系不是太大使用如下所示from transformers import AutoTokenizer # 导入分词器 tokenizer AutoTokenizer.from_pretrained(./) # 编码意思是把自然语言序列转化为token id 序列 full_ids tokenizer.encode(text)具体而言就是两步先分词再查表转化为索引过程如下图tokenizer分词器本身是一个比较大、独立的知识概念了可以从用jieba库做分词词云讲起在此不进行赘述仅进行举例说明。以qwen2.5-1.5B的词表为例采用了Byte-level BPEBBPE分词器[3]。基础 BBPE 子词的词表的总大小为151643专用控制特殊 token22 个对话标记 |im_start|/|im_end|、结束符 |endoftext| 等【第1步】原始 Unicode 文本也就是人类可读的输入原文我爱学习。 I love learning.字符总数22 个字符列表[我, 爱, 学, 习, 。, , I, , l, o, v, e, , l, e, a, r, n, i, n, g, .]【第2步】编码为 UTF-8 字节流是分词器处理的对象形式1个中文汉字占3个字节英文/数字/符号占1个字节字节总数32 个字节十进制[230, 136, 145, 231, 136, 177, 229, 173, 166, 228, 185, 160, 227, 128, 130, 32, 73, 32, 108, 111, 118, 101, 32, 108, 101, 97, 114, 110, 105, 110, 103, 46]字节十六进制[0xe6, 0x88, 0x91, 0xe7, 0x88, 0xb1, 0xe5, 0xad, 0xa6, 0xe4, 0xb9, 0xa0, 0xe3, 0x80, 0x82, 0x20, 0x49,0x20, 0x6c, 0x6f, 0x76, 0x65, 0x20, 0x6c, 0x65, 0x61, 0x72, 0x6e, 0x69, 0x6e, 0x67, 0x2e]【第3步】通过字节映射、BPE 合并得到词表原生Token分词后 Token 数量8 个词表原生 Token 列表[æĪij, çα, åŃ¦ä¹ł, ãĢĤ, ĠI, Ġlove, Ġlearning, .]这里的 Token 是「字节映射后的字符串」不是原始中文和 vocab.json 里的 key 一致【第4步】每个 Token 的详细对照如下原生形式 → 还原文本 → 对应字节Token[ 0] 原生词表: æĪij 还原文本: 我 对应字节: [230, 136, 145]Token[ 1] 原生词表: çα 还原文本: 爱 对应字节: [231, 136, 177]Token[ 2] 原生词表: åŃ¦ä¹ł 还原文本: 学习 对应字节: [229, 173, 166, 228, 185, 160]Token[ 3] 原生词表: ãĢĤ 还原文本: 。 对应字节: [227, 128, 130]Token[ 4] 原生词表: ĠI 还原文本: I 对应字节: [32, 73]Token[ 5] 原生词表: Ġlove 还原文本: love 对应字节: [32, 108, 111, 118, 101]Token[ 6] 原生词表: Ġlearning 还原文本: learning 对应字节: [32, 108, 101, 97, 114, 110, 105, 110, 103]Token[ 7] 原生词表: . 还原文本: . 对应字节: [46]【第5步】原生 Token 映射为数字 IDID 序列[35946, 99242, 100134, 1773, 358, 2948, 6832, 13]这样自然语言序列就转化为了token序列、再到进一步的token id序列。由此可见Transformer 神经网络部分输入的就是一串整数token id序列。注12017 原始论文《Attention Is All You Need》里的 Transformer模型在机器翻译实验中统一使用 BPEByte Pair Encoding字节对编码分词器。注2Transformer 架构本身不绑定任何分词器分词器是独立的预处理模块可以独立得到或者复用。注3BBPE字节级字节对编码BPE的增强变体在UTF-8 原始字节流上进行操作不会出现未知 token所有文字、符号、生僻字、emoji 都能拆成字节子词。BPE类分词器是大语言模型最常用的分词方法。注4有时候也会把神经网络中进行计算的向量、token id等称为“token”、“token向量”等等习惯性叫法同时也算是对应的关系“token-token id-token向量-token向量1-token向量2......”从“token向量1”开始就经过了注意力机制的信息融合变成动态量了。2 Embedding 层token嵌入核心就是把token id转化为token向量、把token id序列转化为token矩阵。而Embedding 层就是⼀个存储固定⼤⼩词典、可学习的嵌⼊向量查找表。根据token id查找对应的向量。在模型训练好以后Embedding 层是进行静态嵌入每一个token id对应着一个唯一确定的向量。之后通过注意力机制不断进行信息融合才是动态的“上下文嵌入”。“token-token id-token向量-token向量1-token向量2......”“token向量”还是静态的嵌入从token向量1开始经过了注意力机制的信息融合才算是动态的上下文嵌入了。3 位置编码Positional Encoding位置编码是为了解决注意力机制无法区分位置顺序的问题。Transformer模型使用的是绝对位置编码现在的大模型大多采用旋转位置编码RoPE等其他方式。3.1 为什么需要首先自然语言需要区分位置。“我爱学习”和“学习爱我”显然含义不同。同时同一个词在不同位置也需要区分“苹果手机上放了一个苹果”。其次为什么“注意力机制无法区分位置顺序”。注意力权重计算阶段是计算一个序列A的token i与序列B的token j的权重是q与k两个向量做内积得到相似度进而归一化得到注意力权重而与i、j的下标无关。也就是得到了(我, 爱, 学习)三个元素之间的权重关系一个3*3的矩阵。加权求和阶段得到信息融合后新的token向量序列A的token i的注意力输出也就是token i就是对序列B每个token的注意力权重与值加权求和token j 的注意力权重与值绑定改变顺序不影响token i 的计算结果。多义词上得到了(苹果, 手机, 上, 放, 了一个, 苹果)在首次融合的时候第一个“苹果”与最后一个“苹果”的词向量相同都是根据“苹果”token的token id 104167得到对应的静态嵌入向量因此与其他token计算得到的注意力权重相同最后计算得到融合后新的token向量也相同。但这就有问题了显然第一个“苹果”后面跟着“手机”是指代了品牌名称这两个“苹果”应该有区别而且是与其所在位置有关也就是与其所处的上下文有关。即对于任意一个 token第 1 个位置和第 100 个位置的 token只要词向量内容相同对它的注意力贡献就完全相同相邻的两个 token 和相隔很远的两个 token在注意力计算中没有天然的权重差异不存在 “近的更重要” 的默认规则。由此可见尽管形式上被组织成有序的“矩阵”但是实际看到的是无序的“集合”是在感知 token 内容集合所以需要引入位置编码。3.2 绝对位置编码的实现绝对位置编码通过给每个位置索引分配一个唯一的、可被模型识别的标识向量将其与原始词向量逐元素相加让输入的每一行同时包含“语义内容” 和 “位置身份”两部分信息。绝对位置编码的生成只依赖当前 token 的全局绝对位置序号 i0,1,2,…,n-1和其他 token 的位置、和上下文语义都没有关系。即绝对位置编码。本质是一个整数ID位置索引到向量的映射。一般分为正弦余弦绝对位置编码、可学习绝对位置编码。正弦余弦绝对位置编码 Transformer 原始论文《Attention Is All You Need》中提出的经典方案没有可训练参数不是靠学习得到的其映射由固定的三角函数公式直接定义。具体算法参考教程即可对不同的token向量均进行仅与绝对位置序号 i有关的位置编码而在同一向量编码内部对于奇偶位置计算公式不同。可学习绝对位置编码BERT、GPT-2 等模型采用的方案与Embedding 层同理是一个可学习的嵌⼊向量查找表。本质是一个可训练的参数矩阵只不是不是通过token id查找而是通过位置索引查找对应的向量。不考虑编码的具体函数形式绝对位置编码的使用如下其中谢谢豆包老师解答1. 项1纯内容交互这就是原本的相似度计算完全由两个 token 的语义内容决定和位置无关。没有位置编码时注意力分数只有这一项。2. 项2第i个token的内容 × 第j个token的位置模型可以学习到当某个语义的 token 出现在第 j 个位置时当前 token 对它的注意力该增强还是减弱。例如模型可以学到“动词对位于宾语位置的名词注意力更高”这类规律。3. 项3第i个token的位置 × 第j个token的内容和项2对称模型可以学习到处于第 i 个位置的 token倾向于关注某个语义的 token。例如“处于句首位置的 token更关注句尾的标点/结论词”。4. 项4纯位置交互这一项和 token 内容完全无关只由 i 和 j 两个绝对位置决定。模型可以学到纯粹的位置偏好比如“相邻位置的 token 注意力更高”“第0个位置对所有位置的注意力都偏高”等固定位置模式。如果使用“正弦余弦绝对位置编码”项4的纯位置交互还可以进一步化简为相对距离 i-j 的函数这也是为什么固定的绝对位置编码能间接捕捉相对位置信息的数学根源。使得注意力分数同时依赖两个 token 的内容和位置。以“正弦余弦绝对位置编码”为例进行进一步的阐述对于位置索引 i 的token向量对应的绝对位置编码在第 2k/2k1 位置上的计算如下对于,位置索引 i0向量维度d4,4个位置上分别 k 0,0,1,1因此 绝对位置编码的4个位置上分别为3.3 绝对位置编码的原理位置编码的原理即使是绝对位置编码的原理也可以单独拿出来仔细研究。。看教程上的可以可以看这篇 [4]https://zhuanlan.zhihu.com/p/454482273主要我自己也看不太懂了已经。。4 输出头输出头因为与输入部分的Embedding 层相关所以放到这里吧。对于解码器模块Decoder每一层输出的都是新的、信息融合后的多个token向量token矩阵不断更新向量表示让其它包含更丰富的上下文、来自编码器也叫跨模态的信息。解码器最后一层的输出也是m个token向量token矩阵形状 m * d然后送到输出头。如下图所示。输出头的线性层将每个 d 维的 token 向量投影为一个 V 维词表的大小的原始分值对数几率logits形状 m * V 。输出头的softmax层对最后一维V 维做 softmax 运算把所有分值压缩到 0~1 之间且总和为 1得到每个位置上所有 token 的概率分布形状 m * V 。序列中第 i 个位置的 V 维向量就代表 “在该位置上词表中每个 token 出现的概率”。具体过程如下图所示最后得到了预测概率矩阵P即对象A的m个位置每个位置上是对下一个词的预测表现为对整个词表的概率分布p。5 预测概率矩阵P的使用预测概率矩阵P就是Transformer神经网络部分的输出。在不同的Transformer变体下以及不同阶段预测概率矩阵P会有不同的用处。对于训练阶段使用完整的矩阵每一行的概率分布与对应位置的真实 token 标签计算交叉熵损失用损失反向传播更新整个模型的参数全程不取出具体 token id。注意这里的“对应位置的真实 token 标签 ”在指的是对应位置的真实标签是指对应位置的下一个真实token 标签而非对应位置本位置的真实token 标签。token位置上的标签是下一个token。对于推理阶段一次生成一个 token拼到对象A的序列末尾再生成下一个。仅取矩阵的最后一行代表下一个词的预测通过解码策略贪心搜索 / Beam Search/Top-k 采样等从概率分布中选出 1 个 token 的 id整数贪心直接选概率最大的那个 token id采样按概率分布随机抽样这个 token id 就是预测得到的下一个词。一方面拼接到对象A的token序列后面通过词嵌入层Embedding查表转成对应的 静态嵌入token向量加位置编码继续生成另一方面通过tokenizer分词器解码得到token id所对应的自然语言输出呈现。注1编码/解码在不同语境下含义不同。对于tokenizer分词器而言“编码”是把自然语言序列转换为token id序列“解码”是把token id序列转换为自然语言序列。解码采样“解码”是从概率分布中获取token id。编码器与解码器...单纯是习惯称呼沿用吧...注2实际使用的时候会使用批次batch最前面多一个批次维度原理叙述的时候省略掉了。注3输出头的线性层有时候会与Embedding 层进行权重绑定。核心是让「输入端词嵌入id→向量」和「输出端线性层向量→logits」共享同一套权重参数尤其在小模型上便于节省参数。本质是让 “词→向量” 和 “向量→词” 这一对互逆映射共用同一个语义空间既大幅节省参数量又能让语义表示更对齐。具体而言输出层的权重直接复用嵌入层权重的转置嵌入层权重是V个token id每个是d维向量尽管使用的方式是查表...转置后刚好是从d维映射到V维。[1]GitHub - datawhalechina/happy-llm: 从零开始构建大模型 · GitHub[2]人民日报 | “Token”中文名定了词元-国家数据局[3]https://zhuanlan.zhihu.com/p/13936916587[4]https://zhuanlan.zhihu.com/p/454482273