06_subword_wordpiece_tokenizer
1316 字
7 分钟
06_subword_wordpiece_tokenizer
我们来看下面几个语料:
s1 = 'albums sold 500,000 copies' # tokenizer 如何处理数字s2 = 'techically perfect, melodically correct' # tokenizer 如何处理类似 melodically 这样的单词s3 = 'featuring a previously unheard track' # tokenizer 如何处理 unheard 这样的单词s4 = 'best-selling music artist' # tokenizer 如何处理连字符s5 = 's1 d1 o1 and o2' # tokenizer 如何处理缩写和数字s6 = 'asbofwheowbeif' # tokenizer 如何处理未知词汇0. 实例化 tokenizer
from transformers import BertTokenizermodel_name = 'bert-base-uncased'tokenizer = BertTokenizer.from_pretrained(model_name)1. vocab
tokenizer.vocabvs.tokenizer.convert_ids_to_tokenslen(tokenizer.vocab) == 30522
下面我们先看看 tokenizer 的词表:
tokenizer.vocabtokenizer.convert_ids_to_tokens输出为:
{'butt': 10007, 'film': 2143, '##kia': 21128, 'bart': 12075, 'wrath': 14532, 'humidity': 18213, 'streetcar': 21420, 'unable': 4039, 'taiwanese': 16539, '[unused908]': 913, 'lyman': 27587, 'slain': 19668, '[unused182]': 187, 'regimental': 17604, 'prolonged': 15330, 'libraries': 8860, 'explicitly': 12045, '38th': 22051, 'extinction': 14446, '\\': 1032, 'mammals': 11993, 'constraint': 27142, 'masters': 5972, 'madeira': 27309, '##pee': 28084,... 'eyebrow': 9522, 'rushes': 18545, 'kung': 18577, 'isla': 25340, ...}
<bound method TokenizersBackend.convert_ids_to_tokens of BertTokenizer(name_or_path='bert-base-uncased', vocab_size=30522, model_max_length=512, padding_side='right', truncation_side='right', special_tokens={'unk_token': '[UNK]', 'sep_token': '[SEP]', 'pad_token': '[PAD]', 'cls_token': '[CLS]', 'mask_token': '[MASK]'}, added_tokens_decoder={ 0: AddedToken("[PAD]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 100: AddedToken("[UNK]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 101: AddedToken("[CLS]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 102: AddedToken("[SEP]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 103: AddedToken("[MASK]", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True),})>实际上,tokenizer.vocab 与 tokenizer.convert_ids_to_tokens 是相对应的:
tokenizer.vocab展示了 word 与 id 的对应关系;tokenizer.convert_ids_to_tokens则展示了 id 与 word 的对应关系。
len(tokenizer.vocab) # 30522
tokenizer.vocab['[UNK]'] # 100- 词表大小 30522
[UNK]对应的 id 为 100
2. 样本子词测试
print(tokenizer(s1))print(tokenizer(s2))print(tokenizer(s3))print(tokenizer(s4))print(tokenizer(s5))print(tokenizer(s6))输出为:
{'input_ids': [101, 4042, 2853, 3156, 1010, 2199, 4809, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1]}
{'input_ids': [101, 6627, 15004, 3819, 1010, 17187, 3973, 6149, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
{'input_ids': [101, 3794, 1037, 3130, 4895, 26362, 2650, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1]}
{'input_ids': [101, 2190, 1011, 4855, 2189, 3063, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1]}
{'input_ids': [101, 1055, 2487, 1040, 2487, 1051, 2487, 1998, 1051, 2475, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}
{'input_ids': [101, 2004, 5092, 2546, 2860, 5369, 5004, 19205, 2546, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]}print(tokenizer.tokenize(s1))print(tokenizer.tokenize(s2))print(tokenizer.tokenize(s3))print(tokenizer.tokenize(s4))print(tokenizer.tokenize(s5))print(tokenizer.tokenize(s6))输出为:
['albums', 'sold', '500', ',', '000', 'copies']['tech', '##ically', 'perfect', ',', 'melodic', '##ally', 'correct']['featuring', 'a', 'previously', 'un', '##heard', 'track']['best', '-', 'selling', 'music', 'artist']['s', '##1', 'd', '##1', 'o', '##1', 'and', 'o', '##2']['as', '##bo', '##f', '##w', '##he', '##ow', '##bei', '##f']2.1 ## 符号是什么意思
## 是 WordPiece 分词算法(BERT 使用的分词方法)中的一个重要标记。它表示这个子词(subword)是某个单词的”后续部分”,不是新词的开头。
换句话说,在还原回原句时,## 前面的部分和 ## 后面的部分是要拼在一起的。
2.1.1 具体例子分析
例 1:s2 = "techically perfect, melodically correct"
['tech', '##ically', 'perfect', ',', 'melodic', '##ally', 'correct']"tech" + "##ically"= technically(原词被拆成两部分)"melodic" + "##ally"= melodically(同理)
词表中没有完整的 "technically" 这个词,但有 "tech" 和 "##ically",所以 WordPiece 把它们拼起来表示原词。
例 2:s3 = "featuring a previously unheard track"
['featuring', 'a', 'previously', 'un', '##heard', 'track']"un" + "##heard"= unheard(原词被拆为前缀 + 剩余部分)
例 3:s5 = "s1 d1 o1 and o2"
['s', '##1', 'd', '##1', 'o', '##1', 'and', 'o', '##2']"s" + "##1"= s1,"d" + "##1"= d1,"o" + "##1"= o1,"o" + "##2"= o2- 词表中没有
"s1"这个完整词,但有"s"和"##1",所以拼起来
例 4:s6 = "asbofwheowbeif"(生造词)
['as', '##bo', '##f', '##w', '##he', '##ow', '##bei', '##f']- 这是一个不存在的词,词表中完全没有
- WordPiece 会尽可能用已知子词来拼凑:
"as" + "##bo" + "##f" + "##w" + "##he" + "##ow" + "##bei" + "##f" - 注意:这里没有出现
[UNK](未知标记),说明 WordPiece 成功用子词拼了出来
2.1.2 词表中的 ## 长什么样?
从前面打印的词表可以看到,## 开头的词条确实存在:
tokenizer.vocab# ... '##kia': 21128, '##pee': 28084, ..."##kia" 就是专门用来接在某个词根后面的子词。比如 "olympia" → "olym" + "##pi" + "##a",但 "##kia" 可能出现在类似 "magnolia" → "magnol" + "##ia" 的场景中(具体取决于词表)。
2.1.3 总结一下
| 符号 | 含义 | 示例 |
|---|---|---|
| 完整词 | 词表中直接有这个单词 | "albums", "perfect", "featuring" |
##xxx | 是前一个子词的后缀,需要拼在一起 | "tech" + "##ically" → "technically" |
没有 ## | 表示新词的开头(或完整词) | "un", "melodic" 都是独立子词的开头 |
所以通过 ## 标记,BERT 的 tokenizer 可以用有限的词表(30522 个词条)覆盖无限的词汇——即使没见过的词,也能拆成已知子词来理解。这正是 WordPiece 算法的核心思想 🎯"##kia" 就是专门用来接在某个词根后面的子词。比如 "olympia" → "olym" + "##pi" + "##a",但 "##kia" 可能出现在类似 "magnolia" → "magnol" + "##ia" 的场景中(具体取决于词表)。
cnt_sharp = 0for token, id in tokenizer.vocab.items(): if token.startswith('##'): print(token, id) cnt_sharp += 1print(cnt_sharp) # 5828输出为:
##ode 10244##■ 30144##gai 23805##ツ 30238##ɹ 29691##dice 24598##gical 26715##ake 13808##edo 26010##ר 29811##gating 16961##lese 24527##aj 13006##re 2890##rians 23543##宗 30347##ates 8520##rys 24769##helm 24546##dict 29201##rained 27361##lena 20844##tale 22059##water 5880##ivist 21997...##hee 21030##ق 29834##rds 17811
5828可见,词表中共有 5828 个 ## 开头的词。
3. Summary
- tokenizer 不会轻易将一个词处理为
[UNK](100) - 基于词表,tokenizer, encode, decode 为一体
- tokenize: word -> tokens(s), 将 word 尽可能地映射为 vocab 中的 keys
- encode: token -> id
- decode: id -> token -> word
- encode 完后并不是终点,decode 还要能很好地将 id 还原,尽可能与输入的 word 对齐
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
06_subword_wordpiece_tokenizer
https://github.com/chunhuizhang/bilibili_vlogs/ 相关文章 智能推荐
1
12_masked_LM
动手写BERT系列 动手写 BERT 系列笔记第十二节(终章)
2
11_bert_head_pooler_output
动手写BERT系列 动手写 BERT 系列笔记第十一节
3
10_add_&_norm_residual_conn
动手写BERT系列 动手写 BERT 系列笔记第十节
4
09_attention_02
动手写BERT系列 动手写 BERT 系列笔记第九节
5
08_attention_01
动手写BERT系列 动手写 BERT 系列笔记第八节
随机文章 随机推荐