Tokenizer and Embedding——从人类到LLM

Tokenizer and Embedding——从人类到LLM

最后更新:


2351 字 约 12 分钟

1.绪言

最近是决定从头开始学习LLM,看到Transformer架构感觉非常复杂,读下来云里雾里,一时间有点迷茫

但是很快我又想到,之所以读下来看不懂,不就是因为其中的那些方法或者技术我其实并不熟悉吗,那我只需要先逐步掌握其中的小的前置技术,就像点技能树一样,要学习一个吊炸天的技能,就得先把那些前置的小技能学会

所以今天,Transformer系列的第一篇笔记文章开始撰写

首先对于LLM,我们需要知道,它本质上只是一个数学工具,根据所给的向量输入,通过矩阵与概率运算,得出最后的向量结果

所以也就是说,LLM是看不懂人类的文字、语音、图片的,它无法直接理解什么叫夏日晴朗的阳光,什么是含泪破碎的颤音,什么是图画里明媚的笑容;同样,我们人类也没办法从大量抽象的向量和矩阵中理解LLM的意图

换而言之,人类与LLM所使用的语言,或者更准确地说,交流媒介之间,存在着难以逾越的鸿沟

而此时我们就需要一个翻译的工具,从人类的交流方式翻译到LLM的交流方式,于是此时,Tokenizer和Embedding出现了


2.Tokenizer

2.1.从文本到Token的三种策略

所谓Tokenizer,就是将人类的交流语言都切分成一个个小块,我们称之为Token,目的就是建立一套统一的映射标准,在这个标准之下,“我”不再是“我”,而是数字1001,“打”不再是一个动作,而是数字9527,“恨”不再是情感的汹涌,而是数字1145

通过这样的标准,原本复杂的语义分析变成了简单的——至少说对于机器容易理解的预测题,当前面出现了这样的数字序列,下一个数字最可能是多少?这就回归了线性代数,回归了概率统计,回归了纯粹的数学

所以现在遇到的第一个难点就是如何切分,在这方面我们有三种方式:

  1. 按字符切分(Character Based):比如英文单词tomato,就被拆成t-o-m-a-t-o,这样做的话有一个好处就是词表特别小,英文26个字母,中文几千个汉字就搞定了;但是问题就在于这种切分方式,每个Token所表达的信息极其稀疏,你单拎出一个t,鬼知道你想说什么?

    一个单词被拆分成多个无关的维度,模型需要耗费巨大的算力去学习,然后才知道哦哦原来这个的意思是番茄。

    同时,这样拆分还有一个坏处就是会使得单个Prompt会被拆分成极长的Token序列,这里可以知道后续模型推理的过程中,Token序列越长,推理所需算力和内存资源的要求就会越高,所以显然是不可行的

  2. 按单词切分(Word Based):区别于按字符切分,一个单词就是一个Token,比如说tomato,比如说书本;

    但同样,这也有一个问题就是最终的词表会非常非常大,因为词语真的太多了,为了覆盖足够多的词,最终的词表可能需要撑到几百万,而且如果遇到没有见过的词,模型直接抓瞎,不知道如何处理

  3. 按子词切分(Subword Based):中和了上面两者的优势,成为了现在最为泛用的方案,它选择在单词切分的基础上,对复杂的词语做子词划分,从而在保证词表大小适中的同时,模型也易于基于训练集进行训练

    究其根本,还是折中的思想,字符切分语义效率太低,单词切分计算效率太低,所以子词切分就诞生了


2.2.BPE算法

现在切分的大概策略出来了,但是具体如何实现呢,对于一个长词比如supermarket,Tokenizer是如何将其切分得恰到好处呢?

这就涉及到了Tokenizer算法的选择,而我们接下来要讲的就是BPE算法,它本来是90年代出现的一个数据压缩算法,但是现在被广泛应用于Tokenizer

其实BPE的核心非常简单,就是下面这几步:

  1. 拿到一个句子,算法不知道每个单词都是什么意思,干脆把它们划分为最细粒度的一个个字母
  2. 开始统计每对相邻出现的字母的频次,得到出现最多的一对相邻字母
  3. 把句子中的每一对这个相邻字母都合并为起来,用一个新的字符表示
  4. 重复2-3步,直到认为已经足够为止

然后这个就是训练的过程,直到词表大小达到了预期,就停止训练

词表内部除了存放对应Token到数字编号的映射,还存储了合成的路径,其中的每一个复合起来的Token都是如何被合成出来的

然后实际应用,模型遇到了新的句子,还是按照上面的流程把它们先切分为一个个独立的字符,随后根据存储的合成路径,频次高的优先级最高,把最后符合语义的Token列表合成出来

但是还有一个问题就是,如果不对单词之间做划分,比如像higher和herself这两个单词的her难以区分,一个是后缀,一个是前缀

解决的方法就是在每个单词的最后放一个表示单词结束的Token,比如说</w>,这样higher中的后缀就会标识成her</w>,从而可以区分开来

我们接下来尝试来跑一遍BPE算法,以一组经典的训练语料为例:

  • l o w </w>:5次
  • l o w e r </w>:2次
  • n e w e s t </w>:6次
  • w i d e s t </w>:3次

首先将所有单词拆成单个字符,Vocab就是{d, e, i, l, n, o, r, s, t, w, </w>}

然后第一回合,统计高频的相邻字符对,记得乘上词频,其中es出现在newest和widest中,一共9次;st出现在newest和widest中,一共9次

按照字典序排序,选择es合并

现在的语料变为

  • l o w </w>:5次
  • l o w e r </w>:2次
  • n e w es t </w>:6次
  • w i d es t </w>:3次

第二轮,统计发现est9次,合并

  • l o w </w>:5次
  • l o w e r </w>:2次
  • n e w **est** </w>:6次
  • w i d **est** </w>:3次

第三轮,统计发现est</w>9次,合并,我们可以发现这就是最高频的形容词最高级后缀

  • l o w </w> (5)
  • l o w e r </w> (2)
  • n e w est</w> (6)
  • w i d est</w> (3)

第四轮,统计发现lo7次,合并

  • lo w </w> (5)
  • lo w e r </w> (2)
  • n e w est</w> (6)
  • w i d est</w> (3)

第五轮,统计发现low7次,合并

  • low </w> (5)
  • low e r </w> (2)
  • n e w est</w> (6)
  • w i d est</w> (3)

发现了没有?经过五轮的BPE,合并出来了low和est这样的组合,算法可以说原理很简单,但是非常实用


2.3.杂谈

BPE虽然好用,但是会遇到OOV问题,即Out-of-Vocabulary,意思就是这个对应的输入根本在词表中找不到,而且也拼不起来,典型的场景就是emoji表情

为了解决这个问题,人们引入了BBPE,即Byte-level BPE,BPE是字符级的,而BBPE是字节级,基础词表的大小直接固定为256个bytes,彻底解决了OOV问题,从此理论上世界上所有的二进制文件、可以被表达为二进制的文字都能被切分,不会再报UNK未知字符


3.Embedding

现在我们成功地把人类的语言变成了Token编码,但问题在于现在呈现的形态还是数字,并没有什么特殊的意义

比如说假如苹果代表的Token编号是1234,电视机代表的Token编号是5678,后者的数字更大,但是你没办法说电视机比苹果要大,要厉害,或者其他的比较怎么样

这个时候就要尝试建立起每个Token的特征


3.1.One-Hot

最开始人们使用名为One-Hot编码的方法来建立特征

根据词表的大小,将每个Token编码映射成一个长度为词表大小的向量,其中只有它所对应的那一位编码的一维是1,其他维度都是0

比如说词表大小为三万,tomato的编码为1919,那么就是第1919个位置的数字是1,其他是0,然后构成了这样的一个向量

这个方法优点就是简单直接,而且真的可以用,摆脱了原来数字编号离散的顺序假设

但是缺点也是显然的,首先就是词表越大,向量长度越长,训练越困难

然后这个编码方法让任意的Token之间都失去了关联性,比如苹果和香蕉都是水果,AbelTomato和彭于晏都是帅哥,居然在余弦相似度上都是0,这显然是不合理的

随后就是算力大部分都被浪费在了无意义的0乘0的计算上


3.2.Embedding

所以现在的困难就是,如何让计算机理解语言概念上的相似与不同?要通过怎样的向量映射方式,才能描述Token之间的关联性?

而这就是Embedding所实现的

简单来讲,为了不像One-Hot那样建立过于庞大的向量,它使用相对低维而稠密的向量来描述Token,比如1024维,4096维

对于每个维度,我们定义一个特征,而每个Token在向量上对应维度的得分就是基于与这个特征的拟合度

用个简单的例子来理解

现在假设我们有四个特征,分别是帅气(handsome)、电子的(electronic)、地球的(terrain)、毛茸茸的(furry),然后就可以基于此建立四维向量

然后现在又有四种事物,分别是AbelTomato、彭于晏、一艘神秘的布满毛发的外星宇宙飞船、仓鼠

那么对应的向量可以这样设定:

  • AbelTomato:{ 1.00, 0.05, 0.95, 0.32 }
  • 彭于晏:{ 1.00, 0.04, 0.96, 0.35 }
  • 一艘神秘的布满毛发的外星宇宙飞船:{ 0.04, 0.93, 0.01, 0.66 }
  • 仓鼠:{ 0.10, 0.03, 0.96, 0.92 }

根据这个向量特征就可以很清楚地看到,首先AbelTomato和彭于晏的向量特征几乎一致,这说明二者具有极强的关联性,这显然是不争的事实

然后两人与这艘宇宙飞船都没有什么关联,但是由于毛茸茸这一项,它和仓鼠建立起了一种隐秘的联系

最后由于AbelTomato、彭于晏、仓鼠都是地球的,所以他们之间也有一维上的联系

通过这种方式,当模型训练完成后,相似的Token就天然具有某种逻辑关系,虽然像现在的大模型所使用的训练维度可能已经非常复杂而无法用语言描述,但是数学规律仍然是存在的

当训练得到的高维向量列表通过降维算法落到二维平面上,就可以显著地看到相似的Token都落在了平面上相近的区域

评论

评论会在审核后展示

正在加载人机验证...

查看原图