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 BertTokenizer
model_name = 'bert-base-uncased'
tokenizer = BertTokenizer.from_pretrained(model_name)

1. vocab#

  • tokenizer.vocab vs. tokenizer.convert_ids_to_tokens
  • len(tokenizer.vocab) == 30522

下面我们先看看 tokenizer 的词表:

tokenizer.vocab
tokenizer.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.vocabtokenizer.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 = 0
for token, id in tokenizer.vocab.items():
if token.startswith('##'):
print(token, id)
cnt_sharp += 1
print(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/
作者
HAC
发布于
2026-06-11
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
HAC
观之非易,行且克难
Greetings
欢迎来到我的博客!这里主要分享我的学习笔记与兴趣爱好。
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
32
分类
5
标签
13
总字数
79,889
运行时长
0
最后活动
0 天前

文章目录