网站推广的平台排名天津网站建设排名

当前位置: 首页 > news >正文

网站推广的平台排名,天津网站建设排名,营销渠道方案,网站建设功能规划0 tokenizer综述 根据不同的切分粒度可以把tokenizer分为: 基于词的切分#xff0c;基于字的切分和基于subword的切分。 基于subword的切分是目前的主流切分方式。subword的切分包括: BPE(/BBPE), WordPiece 和 Unigram三种分词模型。其中WordPiece可以认为是一种特殊的BPE。完…0 tokenizer综述 根据不同的切分粒度可以把tokenizer分为: 基于词的切分基于字的切分和基于subword的切分。 基于subword的切分是目前的主流切分方式。subword的切分包括: BPE(/BBPE), WordPiece 和 Unigram三种分词模型。其中WordPiece可以认为是一种特殊的BPE。完整的分词流程包括文本归一化预切分基于分词模型的切分后处理。SentencePiece是一个分词工具内置BEP等多种分词方法基于Unicode编码并且将空格视为特殊的token。这是当前大模型的主流分词方案。 BPEGPT, GPT-2, GPT-J, GPT-Neo, RoBERTa, BART, LLaMA, ChatGLM-6B, Baichuan WordPieceBERT, DistilBERTMobileBERT UnigramAlBERT, T5, mBART, XLNet 中文分词新词发现 1 基于subword的切分 基于词和字的切分都会存在一定的问题直接应用的效果比较差。 基于词的切分会造成: 词表规模过大 一定会存在UNK造成信息丢失 不能学习到词缀之间的关系例如dog与dogshappy与unhappy 基于字的切分会造成: 每个token的信息密度低 序列过长解码效率很低 所以基于词和基于字的切分方式是两个极端其优缺点也是互补的。而折中的subword就是一种相对平衡的方案。 基于subword的切分能很好平衡基于词切分和基于字切分的优缺点也是目前主流最主流的切分方式。 subword的基本切分原则是 高频词依旧切分成完整的整词 低频词被切分成有意义的子词例如 dogs [dog, ##s] 基于subword的切分可以实现 词表规模适中解码效率较高 不存在UNK信息不丢失 能学习到词缀之间的关系 基于subword的切分包括BPEWordPiece 和 Unigram 三种分词模型。 1.1 处理流程概述 归一化:最基础的文本清洗包括删除多余的换行和空格转小写移除音调等。 HuggingFace tokenizer的实现 https://huggingface.co/docs/tokenizers/api/normalizers预分词:把句子切分成更小的“词”单元。可以基于空格或者标点进行切分。 不同的tokenizer的实现细节不一样。例如: pre-tokenize: [BERT]: [(‘Hello’, (0, 5)), (‘,’, (5, 6)), (‘how’, (7, 10)), (‘are’, (11, 14)), (‘you’, (16, 19)), (‘?’, (19, 20))] [GPT2]: [(‘Hello’, (0, 5)), (‘,’, (5, 6)), (‘Ġhow’, (6, 10)), (‘Ġare’, (10, 14)), (‘Ġ’, (14, 15)), (‘Ġyou’, (15, 19)), (‘?’, (19, 20))] [t5]: [(‘▁Hello,’, (0, 6)), (‘▁how’, (7, 10)), (‘▁are’, (11, 14)), (‘▁you?’, (16, 20))] 可以看到BERT的tokenizer就是直接基于空格和标点进行切分。 GPT2也是基于空格和标签但是空格会保留成特殊字符“Ġ”。 T5则只基于空格进行切分标点不会切分。并且空格会保留成特殊字符▁并且句子开头也会添加特殊字符▁。 预分词的实现 https://huggingface.co/docs/tokenizers/api/pre-tokenizers基于分词模型的切分:不同分词模型具体的切分方式。分词模型包括BPEWordPiece 和 Unigram 三种分词模型。 分词模型的实现 https://huggingface.co/docs/tokenizers/api/models后处理:后处理阶段会包括一些特殊的分词逻辑例如添加sepcial token[CLS],[SEP]等。 后处理的实现 https://huggingface.co/docs/tok 1.2 BPE Byte-Pair Encoding(BPE)是最广泛采用的subword分词器。 训练方法从字符级的小词表出发训练产生合并规则以及一个词表 编码方法将文本切分成字符再应用训练阶段获得的合并规则 经典模型GPT, GPT-2, RoBERTa, BART, LLaMA, ChatGLM等 因为BPE是从字符级别的小词表逐步合并成大词表所以需要先获得字符级别的小词表。 基于word2splits统计vocabs中相邻两个pair的词频pair2count 经过统计当前频率最高的pair为: (‘Ġ’, ‘t’) 频率为7次。 将(‘Ġ’, ‘t’)合并成一个词并添加到词表中。同时在合并规则中添加(‘Ġ’, ‘t’)这条合并规则。 根据更新后的vocab重新对word2count进行切分。具体实现上可以直接在旧的word2split上应用新的合并规则(‘Ġ’, ‘t’) 从而获得新的word2split 重复上述循环直到整个词表的大小达到预先设定的词表大小。 在推理阶段给定一个句子我们需要将其切分成一个token的序列。 具体实现上需要先对句子进行预分词并切分成字符级别的序列然后根据合并规则进行合并。 BPE 的适用范围 BPE 一般适用在欧美语言拉丁语系中因为欧美语言大多是字符形式涉及前缀、后缀的单词比较多。而中文的汉字一般不用 BPE 进行编码因为中文是字无法进行拆分。对中文的处理通常只有分词和分字两种。理论上分词效果更好更好的区别语义。分字效率高、简洁因为常用的字不过 3000 字词表更加简短。 1.3 BBPE 2019年提出的Byte-level BPE (BBPE)算法是上面BPE算法的进一步升级。具体参见Neural Machine Translation with Byte-Level Subwords。 核心思想是用byte来构建最基础的词表而不是字符。首先将文本按照UTF-8进行编码每个字符在UTF-8的表示中占据1-4个byte。 在byte序列上再使用BPE算法进行byte level的相邻合并。编码形式如下图所示 通过这种方式可以更好的处理跨语言和不常见字符的特殊问题(例如颜文字)相比传统的BPE更节省词表空间同等词表大小效果更好每个token也能获得更充分的训练。 但是在解码阶段一个byte序列可能解码后不是一个合法的字符序列这里需要采用动态规划的算法进行解码使其能解码出尽可能多的合法字符。具体算法如下 假定f(k)表示字符序列B(1,k)最大能解码的合法字符数量f(k)有最优的子结构 1.4 WordPiece WordPiece分词与BPE非常类似只是在训练阶段合并pair的策略不是pair的频率而是互信息。 这里的动机是一个pair的频率很高但是其中pair的一部分的频率更高这时候不一定需要进行该pair的合并。 而如果一个pair的频率很高并且这个pair的两个部分都是只出现在这个pair中就说明这个pair很值得合并。 训练方法从字符级的小词表出发训练产生合并规则以及一个词表 编码方法将文本切分成词对每个词在词表中进行最大前向匹配 经典模型BERT及其系列DistilBERTMobileBERT等 在训练环节给定语料通过训练算法生成最终的词表。 WordPiece算法也是从一个字符级别的词表为基础逐步扩充成大词表。合并规则为选择相邻pair互信息最大的进行合并。 def _compute_pair2score(word2splits, word2count):计算每个pair的分数score(freq_of_pair)/(freq_of_first_element×freq_of_second_element):return:vocab2count defaultdict(int)pair2count defaultdict(int)for word, word_count in word2count.items():splits word2splits[word]if len(splits) 1:vocab2count[splits[0]] word_countcontinuefor i in range(len(splits) - 1):pair (splits[i], splits[i 1])vocab2count[splits[i]] word_countpair2count[pair] word_countvocab2count[splits[-1]] word_countscores {pair: freq / (vocab2count[pair[0]] * vocab2count[pair[1]])for pair, freq in pair2count.items()}return scores1.5 Unigram Unigram分词与BPE和WordPiece不同是基于一个大词表逐步裁剪成一个小词表。 通过Unigram语言模型计算删除不同subword造成的损失来衡量subword的重要性保留重要性较高的子词。 训练方法从包含字符和全部子词的大词表出发逐步裁剪出一个小词表并且每个词都有自己的分数。 编码方法将文本切分成词对每个词基于Viterbi算法求解出最佳解码路径。 经典模型AlBERT, T5, mBART, Big Bird, XLNet 在训练环节目标是给定语料通过训练算法生成最终的词表并且每个词有自己的概率值。 Unigram算法是从大词表为基础逐步裁剪成小词表。裁剪规则是根据Unigram语言模型的打分依次裁剪重要度相对较低的词。 首先进行预切分处理。这里采用xlnet的预切分逻辑。具体会按照空格进行切分标点不会切分。并且空格会保留成特殊字符▁句子开头也会添加特殊字符▁。 获得的pre_tokenized_corpus如下每个单元分别为[word, (start_index, end_index)] 统计词表的全部子词和词频取前300个词构成最初的大词表。为了避免OOVchar级别的词均需要保留。 进一步统计每个子词的概率并转换成Unigram里的loss贡献 基于每个子词的loss以及Viterbi算法就可以求解出输入的一个词的最佳分词路径。即整体语言模型的loss最小。词的长度为N解码的时间复杂度为O(N^2)。 尝试移除model中的一个子词并计算移除后新的model在全部语料上的loss从而获得这个子词的score即删除这个子词使得loss新增的量。 为了提升迭代效率批量删除前10%的结果即让整体loss增量最小的前10%的词。(删除这些词对整体loss的影响不大。) 获得新的词表后重新计算每个词的概率获得新的模型。并重复以上步骤直到裁剪到词表大小符合要求。 初始时建立一个足够大的词表。一般可用语料中的所有字符加上常见的子字符串初始化词表也可以通过BPE算法初始化。 针对当前词表用EM算法求解每个子词在语料上的概率。 对于每个子词计算当该子词被从词表中移除时总的loss降低了多少记为该子词的loss。 将子词按照loss大小进行排序丢弃一定比例loss最小的子词(比如20%)保留下来的子词生成新的词表。这里需要注意的是单字符不能被丢弃这是为了避免OOV情况。 重复步骤2到4直到词表大小减少到设定范围。 可以看出ULM会保留那些以较高频率出现在很多句子的分词结果中的子词因为这些子词如果被丢弃其损失会很大。 1.6 SentencePiece SentencePiece是Google出的一个分词工具: 内置BPEUnigramchar和word的分词方法无需预分词以unicode方式直接编码整个句子空格会被特殊编码为▁ 相比传统实现进行优化分词速度速度更快 当前主流的大模型都是基于sentencepiece实现例如ChatGLM的tokenizer。 byte回退 当SentencePiece在训练BPE的时开启–byte_fallback, 在效果上类似BBPE遇到UNK会继续按照byte进行进一步的切分。参见https://github.com/google/sentencepiece/issues/621 具体实现上是将0x00 … 0xFF这256个token添加到词表中。 分析ChatGLM的模型可以发现ChatGLM就是开启了–byte_fallback 同样的方法可以验证LLaMA, ChatGLM-6B, Baichuan这些大模型都是基于sentencepiece实现的BPE的分词算法并且采用byte回退。 参考链接https://zhuanlan.zhihu.com/p/651430181 1.7 bytepiece 一个理想的Tokenizer应该是怎样的这样才能判断最终是否达到了预期。照笔者看来Tokenizer至少应该具备如下基本特性 1、无损重构分词结果应该可以无损还原为输入 2、高压缩率词表大小相同时同一批数据的tokens数应该尽可能少 3、语言无关基于统计训练和分词过程都不应引入语言特性 4、数据驱动可以直接基于原始语料进行无监督训练 5、训练友好能够在合理的时间和配置上完成训练过程。 最后还有一些加分项比如分词速度快、代码易读、方便二次拓展等这些满足自然最好但笔者认为可以不列入基本特性里边。 对于笔者来说SentencePiece最大的槽点就是“无损重构”和“训练友好”。 首先SentencePiece默认会进行NFKC normalization这会导致“全角逗号转半角逗号”等不可逆变化所以默认情况下它连“无损重构”都不满足所以很长时间里它都不在笔者的候选名单中直到后来发现在训练时添加参数–normalization_rule_nameidentity就可以让它不做任何转换。所以SentencePiece算是支持无损重构只不过要特别设置。 至于训练方面就更让人抓狂了。SentencePiece支持BPE和Unigram两种主流算法Unigram训练速度尚可但压缩率会稍低一些BPE的压缩率更高但是训练速度要比Unigram慢上一个数量级而且不管是BPE还是Unigram训练过程都极费内存。总而言之用较大的语料去训练一个SentencePiece模型真不是一种好的体验。 1.7.1 byte-based 我们知道Python3的默认字符串类型是Unicode如果以Unicode为基本单位我们称之为Char-based。Char-based很直观方便汉字表现为长度为1的单个字符但不同语言的Char实在太多即便只是覆盖单字都需要消耗非常大的vocab_size更不用说引入Word。所以BytePiece跟主流的Tokenizer一样以Byte为基本单位。 回到Byte之后很多问题都“豁然开朗”了。因为不同的单Byte只有256个所以只要词表里包含了这256个单Byte那么就可以杜绝OOVOut of Vocabulary这是它显而易见的好处。 此外我们知道汉字的平均信息熵要比英文字母的平均信息熵要大如果我们选择Char-based那么虽然每个Char表面看起来长度都是1但“内在”的颗粒度不一样这会导致统计结果有所偏置。相比之下每个Byte的信息熵则更加均匀【比如大部分汉字的UTF-8编码对应3个Byte而汉字的平均信息熵正好是英文字母对应一个Byte的23倍左右】因此用Byte的统计结果会更加无偏这将会使得模型更加“语言无关”。 在Byte-based方面BytePiece比SentencePiece更彻底SentencePiece是先以Char-based进行处理然后遇到OOV再以Byte-based处理BytePiece则是在一开始就将文本通过text.encode()转为Bytes然后才进行后续操作相比之下更加纯粹。 1.7.2 分词算法 基于词典进行分词的算法无非就那几种比如最大匹配、最短路径、最大概率路径等 跟jieba等中文分词工具一样BytePiece选择的是最大概率路径分词也称“一元文法模型”即Unigram。 选择Unigram有三方面的考虑 第一Unigram的最大概率换言之就是最大似然而LLM的训练目标也是最大似然两者更加一致 第二从压缩的角度看最大概率实际上就是最短编码长度也叫最小描述长度是压缩率最大化的体现这也跟“压缩就是智能”的信仰一致 第三Unigram求最优分词方案可以通过Viterbi算法在线性复杂度内完成这是理论最优的复杂度了。 当然既然有“一元文法模型”自然也有更复杂的“二元文法模型”、“三元文法模型”等但它们的复杂度增加远大于它能带来的收益所以我们通常不考虑这些高阶模型。 1.7.3 训练算法 Tokenizer的训练本质上就是以往的“新词发现”而笔者之前也提了好几种新词发现算法。现在看来跟Unigram分词算法最契合、最有潜力的应该是《基于语言模型的无监督分词》 BytePiece的训练就是基于它实现的这里称之为Byte-based N-gram Language ModelBNLM。 具体来说对于Unigram分词如果一个长度为l的字节串c1,c2,…,cl最优分词结果为w1,w2,…,wm那么概率乘积p(w1)p(w2)…p(wm)应该是所有切分中最大的。 设w1,w2,⋯,wm的长度分别为l1,l2,⋯,lm那么根据条件分解公式 ∏i1mp(wi)∏i1m∏jLi−11jLi−1lip(cj|cLi−11,⋯,cj−1) (1) 这里Lil1l2⋯li。只考虑n-gram模型将jLi−1n的p(cj|cLi−11,⋯,cj−1)统一用p(cj|cj−n1,⋯,cj−1)近似 那么Unigram分词就转化为一个字节标注问题而Tokenizer的训练则转化为n-gram语言模型的训练推荐n6可以直接无监督完成。 注意n6只是说BytePiece的统计信息最多到6-gram但并非最大只能生成长度为6的piece因为大于6的n-gram条件概率我们会用6-gram的近似所以它是可以做到任意阶的即理论上可以生成任意长度piece。 1.7.4 代码实现效果 Githubhttps://github.com/bojone/bytepiece 代码很简单单文件里边就Trainer和Tokenizer两个类分别对应分词两部分。分词借助pyahocorasick来构建AC自动机来稍微提了一下速能凑合用但还是会比SentencePiece慢不少毕竟速度方面纯Python跟C确实没法比。 训练则分为四个主要步骤 1、n-gram计数 2、n-gram剪枝 3、预分词 4、预分词结果剪枝。 其中1、3、4都是计算密集型并且都是可并行的所以编写了相应的多进程实现。在开足够多的进程笔者开了64进程每个进程的使用率基本上都是满的下训练速度能媲美SentencePiece的Unigram训练速度。 这里特别要提一下结果剪枝方面。剪枝最基本的依据自然是频数和vocab_size但这还不够因为有时候会出现p(w1)p(w2)p(w1∘w2)w1∘w2指两个词拼接且w1,w2,w1∘w2三个词都在词表中这种情况下w1∘w2这个词永远不会切分出来所以将它放在词表中是纯粹浪费空间的因此剪枝过程也包含了这类结果的排除。 首先做个小规模的测试从悟道之前开源的数据集里边随机采样10万条作为训练集导出来的文件大概330MB然后另外采样1千作为测试集训练一个vocab_size50k的词表结果对比如下 接下来进行一个更大规模的测试。从中英比例大致为3:5的混合语料库中抽取出10万条样本训练vocab_size100k的Tokenizer。这个语料库的文本都比较长所以这时候10万条导出来的文件已经13GB了测试集包含两部分一部分是同样的语料库中采样出1000条即同源另一部分是刚才采样出来的1000条悟道数据集代表不同源。结果如下