资讯详情

资讯详情

[特殊字符] Transformers 分词器完全指南:BPE、WordPiece、Unigram 与 SentencePiece 原理与源码解析

Transformers 分词器完全指南BPE、WordPiece、Unigram 与 SentencePiece 原理与源码解析【免费下载链接】transformers Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers本文围绕 Transformers 库当前仓库GitHub_Trending/tra/transformers中的分词体系展开系统讲解文本如何被切分为词元token并映射为 ID 的完整链路。你将理解字节对编码BPE、WordPiece、Unigram 与 SentencePiece 四种主流子词subword算法的训练与推理细节掌握如何在仓库源码中确认每个预训练模型实际使用的分词器类型并能够正确选择与模型匹配的分词器完成预处理、编码与解码。一、为什么分词是一道难题在 预处理教程 中可以看到所谓分词tokenization就是把一段文本切分成单词或子词再通过查找表把每个词元映射为整数 ID。单词到 ID 的映射本身很简单真正的难点在于如何切分。以句子Dont you love Transformers? We sure do.为例切分方式的不同会直接改变模型的输入。1.1 空格分词与标点问题最直观的做法是按空格切分得到[Dont, you, love, , Transformers?, We, sure, do.]这样切分看起来合理但Transformers?与do.把标点粘连在了单词上。如果保留这种粘连模型就不得不为单词 每一种可能的后续标点分别学习一份表示表示数量会爆炸式增长。考虑标点后句子应切分为[Don, , t, you, love, , Transformers, ?, We, sure, do, .]然而Dont本身又引出新问题它表示do not因此[Do, nt]才是更合理的切分。可见采用不同的切分规则同一段文本会得到完全不同的输出而预训练模型只有在输入的分词规则与训练数据一致时才能正常工作——这正是每个模型都绑定自己专属分词器的原因。1.2 规则分词与词表爆炸spaCy 与 Moses 是两种流行的基于规则的分词器对上述例句会产出[Do, nt, you, love, , Transformers, ?, We, sure, do, .]空格/标点分词与规则分词都属于单词级分词虽然直观但在大规模语料上会产生极其庞大的词表vocabulary即所有唯一词元的集合。仓库文档中给出的实例是Transformer XL 采用空格/标点分词词表规模高达267,735。这么大的词表意味着模型的输入与输出层必须携带巨大的嵌入矩阵内存与时间开销都会显著上升——实践中Transformer 模型尤其是单语预训练的词表规模很少超过 50,000。1.3 字符级分词的困境既然单词级分词词表太大为什么不退回到字符级字符级分词极其简单、内存开销小但模型几乎不可能学到有意义的输入表示为字符t学习一个与上下文无关的表示远比为单词today学习同样的表示困难得多因此字符级分词往往伴随性能下降。Transformer 模型的最终方案是取两者之长的子词subword分词。二、子词分词取单词级与字符级之长子词分词算法遵循一条核心原则高频常用词保持完整、不再拆分生僻词则分解为有意义的子词。例如annoyingly属于生僻词会被分解为annoying与ly——这两个片段各自更常出现而annoyingly的语义仍由二者合成保留。这一特性对土耳其语等黏着语尤其重要其子词可以拼接成任意长度的复杂单词。子词分词带来的三大收益模型维持合理词表规模每个子词仍能学到有意义的上下文无关表示模型可以处理从未见过的单词将其拆解为已知子词。2.1 BERT 的##子词标记仓库中的BertTokenizer实现于 src/transformers/models/bert/tokenization_bert.py使用 WordPiece 算法对I have a new GPU!的分词结果为 from transformers import BertTokenizer tokenizer BertTokenizer.from_pretrained(google-bert/bert-base-uncased) tokenizer.tokenize(I have a new GPU!) [i, have, a, new, gp, ##u, !]由于是uncased模型句子首先被转为小写。词表中存在[i, have, a, new]但不含gpu于是分词器把gpu拆成已知子词[gp, ##u]。这里的##前缀表示该词元必须不带空格地拼接在上一词元之后解码分词逆操作时据此还原原文。2.2 XLNet 的▁空格标记XLNetTokenizer实现于 src/transformers/models/xlnet/tokenization_xlnet.py基于 SentencePiece对例句的分词结果为 from transformers import XLNetTokenizer tokenizer XLNetTokenizer.from_pretrained(xlnet/xlnet-base-cased) tokenizer.tokenize(Dont you love Transformers? We sure do.) [▁Don, , t, ▁you, ▁love, ▁, , ▁, Transform, ers, ?, ▁We, ▁sure, ▁do, .]可以看到生僻词Transformers被拆为高频子词Transform与ers▁的含义将在 SentencePiece 一节详细解释。下面分别剖析四种子词算法。它们都依赖训练过程——通常就是在对应模型的训练语料上统计词元频率、学习切分规则。三、Byte-Pair EncodingBPEBPE 由 Neural Machine Translation of Rare Words with Subword UnitsSennrich et al., 2015 提出分三步工作第一步预分词pre-tokenization。先把训练数据切分成单词策略可简可繁简单的空格切分GPT-2、RoBERTa规则分词XLM、FlauBERT 在多数语言上使用 Moses更复杂的组合GPT 使用 spaCy 与 ftfy 统计训练语料中每个单词的频率。第二步统计单词频率。预分词后得到唯一单词集合及其在语料中的出现次数。第三步迭代学习合并规则。BPE 先建立基础词表base vocabulary然后反复从基础词表的两个符号中学习合并为新符号的规则直到词表达到目标规模。目标词表大小是在训练分词器之前就要定好的超参数。3.1 一个完整的 BPE 训练示例假设预分词后得到如下单词及频率(hug, 10), (pug, 5), (pun, 12), (bun, 4), (hugs, 5)基础词表为[b, g, h, n, p, s, u]所有单词拆为基础符号后(h u g, 10), (p u g, 5), (p u n, 12), (b u n, 4), (h u g s, 5)BPE 统计所有可能符号对的频率并选择最高频的一对h后接u出现 15 次hug10 次 hugs5 次但最高频的是u后接g合计 20 次hug中 10 次、pug中 5 次、hugs中 5 次。于是第一条合并规则是所有ug合并为ug词表加入ug单词集合变为(h ug, 10), (p ug, 5), (p u n, 12), (b u n, 4), (h ug s, 5)接下来最高频对是un16 次合并为un再之后是hug15 次合并为hug。此时词表为[b, g, h, n, p, s, u, ug, un, hug]单词集合变为(hug, 10), (p ug, 5), (p un, 12), (b un, 4), (hug s, 5)3.2 合并规则如何作用于新词假设训练在此停止则学习到的合并规则可以直接套用到新单词上前提是新词不包含基础词表之外的符号bug→[b, ug]mug→ 由于基础词表中没有m只能得到[unk, ug]。实践中单个字母如m几乎总在训练数据中出现过不会落入unk但表情符号等极特殊字符确实可能触发unk替换。词表规模 基础词表大小 合并次数这是一个需预先设定的超参数。例如 GPT 的基础字符为 478 个合并 40,000 次后停止词表规模为 40,478。3.3 Byte-level BPEGPT-2 的 256 字节巧思如果把所有 Unicode 字符都纳入基础字符基础词表会异常庞大。GPT-2 的做法是直接以 256 个字节作为基础词表确保所有可能的基础字符天然齐全再配合少量处理标点的规则GPT-2 分词器可以在完全不需要unk符号的情况下分词任意文本。其词表规模为 50,257构成如下256 个字节级基础词元 1 个文本结束专用词元 50,000 次合并学习到的符号。仓库中 src/transformers/models/gpt2/tokenization_gpt2.py 给出了实现证据GPT2Tokenizer的VOCAB_FILES_NAMES声明vocab.json词表与merges.txt合并规则两个文件底层Tokenizer使用BPE模型并搭配pre_tokenizers.ByteLevel(add_prefix_space...)预分词器与decoders.ByteLevel()解码器。同时源码注释提醒GPT-2 把空格当作词元的一部分处理类似 SentencePiece因此Hello world与 Hello world句首多一个空格得到的input_ids不同可通过add_prefix_spaceTrue规避但由于模型并非按此方式预训练可能带来性能下降。四、WordPieceWordPiece 是 BERT、DistilBERT 与 Electra 使用的子词算法算法思路由 Japanese and Korean Voice SearchSchuster et al., 2012 提出。它与 BPE 非常相似但合并标准不同BPE 选择出现频率最高的符号对WordPiece 选择加入后能让训练数据似然最大化的符号对。具体来说训练时 WordPiece 计算每个候选符号对的概率再除以第一个符号后接第二个符号的条件概率选择该比值最大的符号对进行合并。直觉上WordPiece 评估的是合并两个符号会损失多少信息值不值得合并——这正是它与 BPE 的微妙差异。在仓库源码中BertTokenizer的底层模型即WordPiece见 tokenization_bert.py并围绕它配置了完整的词元体系[UNK]未知词、[SEP]分隔符、[PAD]填充、[CLS]分类符、[MASK]掩码以及do_lower_case、tokenize_chinese_chars等预处理选项load_vocab函数按行读取vocab.txt构建有序词表。五、UnigramUnigram 由 Subword Regularization: Improving Neural Network Translation Models with Multiple Subword CandidatesKudo, 2018 提出。与 BPE、WordPiece 的自底向上合并相反Unigram 走的是自顶向下剪枝路线用大量符号初始化基础词表通常覆盖所有预分词后的单词及其最常见子串每一步基于当前词表与一个 unigram 语言模型定义训练数据上的损失通常为负对数似然对词表中每个符号计算若将其删除整体损失增加多少删除损失增量最小的 p%通常为 10% 或 20%符号——即对整体损失影响最小的符号重复直到词表达到目标大小。Unigram 始终保留基础字符因此任意单词都能被分词。此外Unigram 不基于合并规则训练后对新文本存在多种合法切分。假设训练好的词表为[b, g, h, n, p, s, u, ug, un, hug],则hugs可以是[hug, s]、[h, ug, s]或[h, u, g, s]。Unigram 在训练时保存每个词元的概率推理时可计算每种切分的概率默认选择概率最高的切分同时也可按概率分布采样多种切分这正是论文标题中Subword Regularization的由来。这些概率由训练损失定义设训练数据由单词 \(x_{1}, \dots, x_{N}\) 构成单词 \(x_{i}\) 的所有可能切分集合为 \(S(x_{i})\)则整体损失为$$\mathcal{L} -\sum_{i1}^{N} \log \left ( \sum_{x \in S(x_{i})} p(x) \right )$$Unigram 很少被 Transformers 中的模型直接使用而是与 SentencePiece 组合出现见下文。六、SentencePiece语言无关的子词方案前面所有算法都有一个共同假设文本用空格分隔单词。但并非所有语言都用空格分词如中日泰语。一种思路是使用语言特定的预分词器如 XLM 针对中文、日语、泰语配备专用预分词器而 SentencePieceA simple and language independent subword tokenizer and detokenizer for Neural Text ProcessingKudo et al., 2018 提供了更通用的解法把输入当作原始字符流将空格本身纳入字符集合再用 BPE 或 Unigram 算法构建词表。6.1▁符号与解码规则XLNet 使用 SentencePiece因此其词表中出现▁字符它代表一个空格及词首位置。SentencePiece 的解码非常简单——把所有词元直接拼接再把▁替换为空格即可。仓库中 xlnet/tokenization_xlnet.py 与 albert/tokenization_albert.py 提供了完整的实现佐证二者的VOCAB_FILES_NAMES均声明spiece.modelSentencePiece 词表文件与tokenizer.json源码顶部定义SPIECE_UNDERLINE ▁预分词器由pre_tokenizers.WhitespaceSplit()与pre_tokenizers.Metaspace(replacement▁, ...)组成——即先用空白切分再把空格替换为▁标记解码器为decoders.Metaspace(replacement▁, ...)把▁还原为空格归一化阶段包含NFKD()与StripAccents()可配合keep_accents、do_lower_case选项并有针对中文等语言的可选预编译字符映射表_spm_precompiled_charsmap。以 ALBERT 为例其底层模型是Unigram说明SentencePiece 负责把空格当字符处理的预处理与▁标记Unigram 负责子词词表的构建与切分二者组合使用。T5 同样基于 SentencePiece见 t5/tokenization_t5.py 中声明的spiece.model。七、在仓库中定位每个模型的分词器7.1 查看模型对应的分词器类型每个模型页面都会标注其预训练模型采用的分词器类型。更直接的方式是在仓库源码中确认模型分词器文件底层算法词表文件BERT / DistilBERT / Electramodels/bert/tokenization_bert.pyWordPiecevocab.txtGPT-2 / RoBERTamodels/gpt2/tokenization_gpt2.pyByte-level BPEvocab.jsonmerges.txtXLNetmodels/xlnet/tokenization_xlnet.pySentencePiece Unigramspiece.modelALBERTmodels/albert/tokenization_albert.pySentencePiece Unigramspiece.modelT5 / Marianmodels/t5/tokenization_t5.py 等SentencePiece Unigramspiece.model值得注意的是仓库中的分词器类统一继承自TokenizersBackend底层由 HuggingFace tokenizers 库驱动其基类契约定义于 src/transformers/tokenization_utils_base.py包括tokenize文本 → 词元列表、encode文本 → ID 列表等价于convert_tokens_to_ids(tokenize(text))与decodeID → 文本等核心方法。7.2 快速验证示例from transformers import BertTokenizer, GPT2Tokenizer, XLNetTokenizer # WordPiece注意 ## 续接标记 bert BertTokenizer.from_pretrained(google-bert/bert-base-uncased) print(bert.tokenize(I have a new GPU!)) # [i, have, a, new, gp, ##u, !] # Byte-level BPE空格参与编码 gpt2 GPT2Tokenizer.from_pretrained(openai-community/gpt2) print(gpt2(Hello world)[input_ids]) print(gpt2( Hello world)[input_ids]) # 与上行不同 # SentencePiece空格以 ▁ 表示 xlnet XLNetTokenizer.from_pretrained(xlnet/xlnet-base-cased) print(xlnet.tokenize(Dont you love Transformers? We sure do.))对应地仓库测试 tests/models/bert/test_tokenization_bert.py 中验证了 WordPiece 分词器解码相关行为如clean_up_tokenization_spaces与cleanup标志对hello , world !解码结果的影响可作为继续研读测试用法的入口。八、小结如何选择分词器选择分词器的第一原则是与模型严格匹配预训练模型只对用相同规则分词的输入表现正常。实务要点归纳如下永远用AutoTokenizer.from_pretrained(model_id)或模型专属分词器加载让分词器文件vocab.txt/vocab.json/spiece.model与权重一起从同一模型仓库下载避免不匹配理解##WordPiece 的续接标记与▁SentencePiece 的空格标记是解码还原的关键切勿手工拼接词元后再解码分词是训练出来的BPE 与 WordPiece 自底向上合并Unigram 自顶向下剪枝SentencePiece 解决非空格分词语言的问题——了解这些差异有助于判断分词结果是否符合预期以及在需要自定义分词器时选择合适的算法当处理生僻词、表情符号或非空格分隔语言时Byte-level BPEGPT-2 方案与 SentencePiece 具有天然优势因为它们分别通过字节回退与字符级处理避免unk泛滥。以上内容以 docs/source/ja/tokenizer_summary.md 为骨架结合仓库中各模型分词器源码与测试整理而成可作为深入阅读各分词器实现如PreTrainedTokenizerBase、TokenizersBackend及各类tokenization_*.py的起点。【免费下载链接】transformers Transformers: the model-definition framework for state-of-the-art machine learning models in text, vision, audio, and multimodal models, for both inference and training.项目地址: https://gitcode.com/GitHub_Trending/tra/transformers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →