BPE 与 WordPiece 分词方法对比:子词分割与 Tokenizer 原理

对比 BPE 与 WordPiece 两种经典子词分割算法,解析 Tokenizer 在编码与解码阶段的词元切分、映射及词汇表构建机制,及其在预训练模型中的应用。

常见的子词分割方法:

  • BPE(Byte-Pair Encoding):用于 GPT、GPT-2、RoBERTa、BART 和 DeBERTa 等模型。
  • WordPiece:用于 DistilBERT、MobileBERT、Funnel Transformers 和 MPNET 等模型。

什么是 Tokenizer?

Tokenizer(分词器)可以将原始文本(raw text)转换为模型能够理解的数字序列,在模型输入和输出的两个主要阶段中发挥重要作用:

编码Encode阶段

  1. 分词(Tokenize)

    将文本拆分为词元(Token),常见的分词方式包括字级、词级、子词级(如 BPE、WordPiece)、空格分词等。

    1
    2
    输入: "你好"
    分词: ["你", "好"]
  1. 映射(Mapping)

    将每个词元映射为词汇表中的唯一 ID,生成的数字序列即为模型的输入。

    1
    2
    分词: ["你", "好"]
    映射: [1001, 1002]

解码Decode阶段

  1. 反映射(De-mapping)

    模型输出的数字序列通过词汇表映射回对应的词元,二者是一一对应的关系。

    1
    2
    输出: [1001, 1002]
    反映射: ["你", "好"]
  1. 文本重组

    将解码后的词元以某种规则重新拼接为完整文本。

    1
    2
    反映射: ["你", "好"]
    重组: "你好"

实际使用

在进一步讲解之前,我们先通过 Transformers 库中的 AutoTokenizer 类来使用 Tokenizer。

安装库

1
pip install transformers

BPE 分词器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from transformers import AutoTokenizer

# 使用 GPT-2 的分词器(BPE)
tokenizer = AutoTokenizer.from_pretrained("gpt2")

text = "Hello, world!"

# 编码
# 1. 将文本分词为 Tokens
tokens = tokenizer.tokenize(text)
print("Tokens:", tokens)

# 2. 将 Tokens 转换为 Token IDs
token_ids = tokenizer.convert_tokens_to_ids(tokens)
print("Token IDs:", token_ids)

# 解码
# 1. Token IDs 转换为 Tokens
tokens = tokenizer.convert_ids_to_tokens(token_ids)
print("Tokens:", tokens)

# 2. Tokens 拼接为文本
decoded_text = tokenizer.convert_tokens_to_string(tokens)
print("Decoded Text:", decoded_text)

输出

1
2
3
4
Tokens: ['Hello', ',', 'Ġworld', '!']
Token IDs: [15496, 11, 995, 0]
Tokens: ['Hello', ',', 'Ġworld', '!']
Decoded Text: Hello, world!

Note

实际上 GPT-2 用的是 Byte-level BPE,也就是从字符级处理变成了字节级,这样可以直接处理不同语言或者特殊的符号。

WordPiece 分词器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from transformers import AutoTokenizer

# 使用 BERT 的分词器(WordPiece)
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

text = "Hello, world!"

# 编码
# 1. 将文本分词为 Tokens
tokens = tokenizer.tokenize(text)
print("Tokens:", tokens)

# 2. 将 Tokens 转换为 Token IDs
token_ids = tokenizer.convert_tokens_to_ids(tokens)
print("Token IDs:", token_ids)

# 解码
# 1. Token IDs 转换为 Tokens
tokens = tokenizer.convert_ids_to_tokens(token_ids)
print("Tokens:", tokens)

# 2. Tokens 拼接为文本
decoded_text = tokenizer.convert_tokens_to_string(tokens)
print("Decoded Text:", decoded_text)

输出

1
2
3
4
Tokens: ['hello', ',', 'world', '!']
Token IDs: [7592, 1010, 2088, 999]
Tokens: ['hello', ',', 'world', '!']
Decoded Text: hello, world!

encode() 和 decode() 方法

更简洁且常见的使用方式是直接使用 encode()decode() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from transformers import AutoTokenizer

# 取消注释以对比两种分词器的输出差异
tokenizer = AutoTokenizer.from_pretrained("gpt2")
# tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

text = "Hello, world!"

# 使用 encode() 将文本直接转换为 Token IDs
token_ids = tokenizer.encode(text)
print("Token IDs:", token_ids)

# 使用 decode() 将 Token IDs 转换回文本
decoded_text = tokenizer.decode(token_ids)
print("Decoded Text:", decoded_text)

输出

1
2
Token IDs: [15496, 11, 995, 0]
Decoded Text: Hello, world!

构造词汇表

我们需要将语料库(corpus)的文本拆分为单词,假设当前语料库包含的单词和对应频次如下:

1
("low", 5), ("lower", 2), ("newest", 6), ("widest", 3)

有些论文也用 vocab 来表述,知道后面是频次即可,命名不用纠结。

Byte-Pair Encoding (BPE)

参考文献:

  • A new algorithm for data compression. 1994
  • Neural Machine Translation of Rare Words with Subword Units. 2015

BPE 是一种基于数据压缩的技术,最早由 Gage 在 1994 年提出,后来被用于 GPT 等模型。它是一种子词分割算法,从字符级别开始,通过迭代合并频率最高的字符对(或字符序列)来构建新的 Token,从而可以处理部分 OOV(Out-Of-Vocabulary)情况。

Q: 什么是 OOV ?

其实就是不在词汇表中的词,也称之为「未登录词」。

BPE 每次的迭代目标是找到频率最高的相邻字符对,定义 Score 以与 WordPiece 作对比:

$$
Score_{BPE}(x,y)=freq(x,y)
$$

其中, $freq(x,y)$ 表示字符对 $(x,y)$ 在语料库中的出现频次。

步骤

  1. 初始化词汇表 V
    • V 包含语料库中的所有唯一字符,即单词字符的集合。
  2. 统计字符对的频次
    • 对于每个单词的字符序列,统计相邻字符对的出现频次。
  3. 找到频次(Score)最高的字符对并合并
    • 选择出现频率最高的字符对$(x,y)$,将其合并为新符号$xy$ 。
  4. 更新词汇表并重复步骤 2 到 4
    • 将新符号添加到词汇表 $V=V∪xy$。
    • 更新语料库中的单词表示,重复统计和合并过程,直到满足停止条件(例如,词汇表达到预定大小)。

示例

步骤 1:初始化词汇表

  • 将单词拆分为字符序列

    1
    2
    3
    4
    ("l", "o", "w"), 5
    ("l", "o", "w", "e", "r"), 2
    ("n", "e", "w", "e", "s", "t"), 6
    ("w", "i", "d", "e", "s", "t"), 3
  • 词汇表 V

    1
    {'l', 'o', 'w', 'e', 'r', 'n', 's', 't', 'i', 'd'}

步骤 2:统计字符对的频次

编写一个函数,根据给定的单词和其频次,自动统计字符对的频次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from collections import defaultdict

def count_char_pairs(word_freq):
"""
计算字符对的频次。

参数:
word_freq: Lst of tuples, 每个元组包含单词和其频次

返回:
字符对频次的字典
"""
pair_freq = defaultdict(int)
for word, freq in word_freq:
chars = list(word)
for i in range(len(chars) - 1):
pair = (chars[i], chars[i + 1])
pair_freq[pair] += freq
return pair_freq

# 示例词汇表和单词频次
word_freq = [
("low", 5),
("lower", 2),
("newest", 6),
("widest", 3)
]

pair_freq = count_char_pairs(word_freq)
print("字符对频次统计结果:")
for pair, freq in pair_freq.items():
print(f"{pair}: {freq}")

输出

1
2
3
4
5
6
7
8
9
10
11
12
字符对频次统计结果:
('l', 'o'): 7 # 5 (low) + 2 (lower)
('o', 'w'): 7 # 5 (low) + 2 (lower)
('w', 'e'): 8 # 2 (lower) + 6 (newest)
('e', 'r'): 2
('n', 'e'): 6
('e', 'w'): 6
('e', 's'): 9 # 6 (newest) + 3 (widest)
('s', 't'): 9 # 6 (newest) + 3 (widest)
('w', 'i'): 3
('i', 'd'): 3
('d', 'e'): 3

步骤 3:找到频次最高的字符对并合并

  • 选择频次最高的字符对

    • ("e", "s")("s", "t"),频次均为 9。可以任选其一进行合并,假设选择排序第一的: ("e", "s")
  • 合并 ("e", "s") 为新符号 es

  • 记录合并操作

    1
    Merge 1: ("e", "s") -> "es"

步骤 4:更新词汇表并重复

  • 更新单词序列

    1
    2
    3
    4
    ("l", "o", "w"), 5
    ("l", "o", "w", "e", "r"), 2
    ("n", "e", "w", "es", "t"), 6
    ("w", "i", "d", "es", "t"), 3
  • 更新词汇表 V

    1
    {'l', 'o', 'w', 'e', 'r', 'n', 's', 't', 'i', 'd', 'es'}
  • 重复步骤 2 到 4,直到达到预定的词汇表大小

WordPiece

参考文献:

  • Japanese and Korean voice search. 2012
  • Google’s Neural Machine Translation System: Bridging the Gap between Human and Machine Translation. 2016

WordPiece 是一种子词分割算法,最初用于处理日语和韩语的语音搜索,后来在 Google 的神经机器翻译系统中得到应用。

与 BPE 不同,WordPiece 的 Score 由字符对频次与其组成部分频次的比值决定,定义 Score:

$$
\text{Score}_{\text{WordPiece}}(x, y) = \frac{\text{freq}(xy)}{\text{freq}(x) \times \text{freq}(y)}
$$

其中, $freq(x)$, $freq(y)$ 和 $freq(xy)$ 分别表示符号 x, y 和它们合并后的符号 $xy$ 的频次。

步骤

  1. 初始化词汇表 V
    • 与 BPE 相同, V 包含语料库中的所有唯一字符,但处理方式略有不同:对于每个单词,除了首个字符外,其他字符前都加上 ## 前缀。
  2. 统计字符对的频次及 Score
    • 对于每个可能的字符对 (x,y),计算 freq(x), freq(y), freq(xy),并计算 Score。
  3. 找到 Score 最高的字符对并合并
    • 选择 Score 最高的字符对 (x,y),将其合并为新符号 xy,注意:
      • 如果第二个符号以 ## 开头,合并时去掉 ## 前缀再进行连接。
      • 新符号是否以 ## 开头,取决于第一个符号是否以 ## 开头。
  4. 更新词汇表并重复步骤 2 到 4
    • 将新符号添加到词汇表 $V=V∪xy$。
    • 更新语料库中的单词表示,重复统计和合并过程,直到满足停止条件。

示例

使用与 BPE 示例相同的语料库。

步骤 1:初始化词汇表

  • 将单词拆分为字符序列

    1
    2
    3
    4
    ('l', '##o', '##w'), 5                       # "low"
    ('l', '##o', '##w', '##e', '##r'), 2 # "lower"
    ('n', '##e', '##w', '##e', '##s', '##t'), 6 # "newest"
    ('w', '##i', '##d', '##e', '##s', '##t'), 3 # "widest"
  • 词汇表 V

    1
    {'l', '##o', '##w', '##e', '##r', 'n', '##s', '##t', 'w', '##i', '##d'}

步骤 2:统计字符和字符对的频次,计算 Score

可以设计一个函数完成这个步骤(直接运行查看输出):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from collections import defaultdict

def count_char_pairs_wordpiece(word_freq):
"""
计算字符对的频次和单个字符的频次。

参数:
word_freq: List of tuples, 每个元组包含单词(列表形式)和其频次

返回:
两个字典,分别为字符对频次和单个字符频次
"""
pair_freq = defaultdict(int)
char_freq = defaultdict(int)
for word, freq in word_freq:
for i in range(len(word)):
char_freq[word[i]] += freq
if i < len(word) - 1:
pair = (word[i], word[i + 1])
pair_freq[pair] += freq
return pair_freq, char_freq

def compute_wordpiece_score(freq_xy, freq_x, freq_y):
"""
根据 WordPiece 的定义计算 Score。

参数:
freq_xy: 符号对的频次
freq_x: 符号 x 的频次
freq_y: 符号 y 的频次

返回:
计算得到的 Score
"""
if freq_x == 0 or freq_y == 0:
return 0
return freq_xy / (freq_x * freq_y)

# 示例词汇表和单词频次
word_freq = [
(['l', '##o', '##w'], 5),
(['l', '##o', '##w', '##e', '##r'], 2),
(['n', '##e', '##w', '##e', '##s', '##t'], 6),
(['w', '##i', '##d', '##e', '##s', '##t'], 3)
]

# 统计字符对频次和单个字符频次
pair_freq, char_freq = count_char_pairs_wordpiece(word_freq)

# 计算每对字符的 Score
scores = {}
for pair in pair_freq:
freq_xy = pair_freq[pair]
freq_x = char_freq[pair[0]]
freq_y = char_freq[pair[1]]
score = compute_wordpiece_score(freq_xy, freq_x, freq_y)
scores[pair] = score

# 输出结果
print("字符对频次统计结果:")
for pair, freq in pair_freq.items():
print(f"{pair}: {freq}")

print("\n单个字符频次统计结果:")
for char, freq in char_freq.items():
print(f"{char}: {freq}")

print("\n字符对 Score 计算结果:")
for pair, score in scores.items():
print(f"{pair}: {score:.4f}")

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
字符对频次统计结果:
('l', '##o'): 7
('##o', '##w'): 7
('##w', '##e'): 8
('##e', '##r'): 2
('n', '##e'): 6
('##e', '##w'): 6
('##e', '##s'): 9
('##s', '##t'): 9
('w', '##i'): 3
('##i', '##d'): 3
('##d', '##e'): 3

单个字符频次统计结果:
l: 7
##o: 7
##w: 13
##e: 17
##r: 2
n: 6
##s: 9
##t: 9
w: 3
##i: 3
##d: 3

字符对 Score 计算结果:
('l', '##o'): 0.1429
('##o', '##w'): 0.0769
('##w', '##e'): 0.0362
('##e', '##r'): 0.0588
('n', '##e'): 0.0588
('##e', '##w'): 0.0271
('##e', '##s'): 0.0588
('##s', '##t'): 0.1111
('w', '##i'): 0.3333
('##i', '##d'): 0.3333
('##d', '##e'): 0.0588
  • 选择频次最高的字符对

    • ('w', '##i')('##i', '##d'),Score 都为 0.3333。可以任选其一进行合并,假设选择排序第一的: ("w", "##i")
  • 合并 ('w', '##i') 为新符号 wi

    • 注意:合并时,若第二个符号以 ## 开头,合并后的新符号为第一个符号加上第二个符号去掉 ## 前缀的部分。
  • 记录合并操作:

    1
    Merge 1: ('w', '##i') -> 'wi'

步骤 4:更新词汇表并重复

  • 更新词汇表 V

    1
    {'l', '##o', '##w', '##e', '##r', 'n', '##s', '##t', 'w', '##i', '##d', 'wi'}
  • 更新单词序列

    1
    2
    3
    4
    ('l', '##o', '##w'), 5                       # "low"
    ('l', '##o', '##w', '##e', '##r'), 2 # "lower"
    ('n', '##e', '##w', '##e', '##s', '##t'), 6 # "newest"
    ('wi', '##d', '##e', '##s', '##t'), 3 # "widest"
  • 重复步骤 2 到 4,直到达到预定的词汇表大小

标记文本

每次合并时都会记录对应的 merge 规则,但并未详细说明其作用,下面将以 BPE 为例进行解释。

BPE

在之前的示例中,三轮合并后将得到以下合并规则(按合并顺序排列):

  1. 合并字符对 'e''s',得到 'es'
  2. 合并字符对 'es''t',得到 'est'
  3. 合并字符对 'l''o',得到 'lo'

假设当前词汇表包含所有单个字符,修改官方文档最后提供的 tokenize() 示例代码进行演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def tokenize(text):
# 预分词处理:将文本拆分为初步的单词列表
pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
pre_tokenized_text = [word for word, offset in pre_tokenize_result]

print("初始预分词结果:")
print(pre_tokenized_text)

# 将每个单词拆分为字符列表
splits = [[l for l in word] for word in pre_tokenized_text]
print("\n初始拆分结果:")
print(splits)

# 遍历所有合并规则(merges),逐步应用到拆分后的结果中
for pair, merge in merges.items():
print(f"\n应用合并规则: {pair} -> {merge}")

# 遍历每个已拆分的单词
for idx, split in enumerate(splits):
print(f" 合并前第 {idx+1} 个单词: {split}")
i = 0
# 在当前拆分的字符中查找匹配的字符对
while i < len(split) - 1:
if split[i] == pair[0] and split[i + 1] == pair[1]:
# 合并字符对
split = split[:i] + [merge] + split[i + 2 :]
print(f" 在位置 {i} 处合并: {split}")
else:
i += 1
# 更新拆分后的结果
splits[idx] = split

print("\n最终拆分结果:")
print(splits)

# 将所有拆分后的结果合并为一个 Token 列表并返回
return sum(splits, [])

# 示例 merges 字典
merges = {
('e', 's'): 'es',
('es', 't'): 'est',
('l', 'o'): 'lo'
}

# 示例文本
text = "estimate, local"

# 调用 tokenize 函数,并打印中间过程
tokens = tokenize(text)
print("\n最终生成的 Tokens:")
print(tokens)

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
初始预分词结果:
['estimate', ',', 'local']

初始拆分结果:
[['e', 's', 't', 'i', 'm', 'a', 't', 'e'], [','], ['l', 'o', 'c', 'a', 'l']]

应用合并规则: ('e', 's') -> es
合并前第 1 个单词: ['e', 's', 't', 'i', 'm', 'a', 't', 'e']
在位置 0 处合并: ['es', 't', 'i', 'm', 'a', 't', 'e']
合并前第 2 个单词: [',']
合并前第 3 个单词: ['l', 'o', 'c', 'a', 'l']

应用合并规则: ('es', 't') -> est
合并前第 1 个单词: ['es', 't', 'i', 'm', 'a', 't', 'e']
在位置 0 处合并: ['est', 'i', 'm', 'a', 't', 'e']
合并前第 2 个单词: [',']
合并前第 3 个单词: ['l', 'o', 'c', 'a', 'l']

应用合并规则: ('l', 'o') -> lo
合并前第 1 个单词: ['est', 'i', 'm', 'a', 't', 'e']
合并前第 2 个单词: [',']
合并前第 3 个单词: ['l', 'o', 'c', 'a', 'l']
在位置 0 处合并: ['lo', 'c', 'a', 'l']

最终拆分结果:
[['est', 'i', 'm', 'a', 't', 'e'], [','], ['lo', 'c', 'a', 'l']]

最终生成的 Tokens:
['est', 'i', 'm', 'a', 't', 'e', ',', 'lo', 'c', 'a', 'l']

不过,在之前的过程中生成的最终词汇表 V 并未包含所有单个字符,而是:

1
{'e', 'r', 's', 'est', 'w', 'l', 'o', 'lo', 'es', 'i', 'n', 't', 'd'}

因此,对于输入 "estimate, local",其标记结果为:

1
['est', 'i', '[UNK]', 'a', 't', 'e', '[UNK]', 'lo', '[UNK]', '[UNK]', l]

这里的 '[UNK]'(UNKNOWN)表示该子词不在词汇表中,即属于 OOV(Out-of-Vocabulary) 的情况。

WordPiece

和 BPE 不同,WordPiece 对 OOV 采取的是「宁杀错不放过」策略,即只要有一个字符没见过,整个单词都标记为 '[UNK]'

修改官方文档最后提供的 tokenize() 示例代码进行演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from transformers import AutoTokenizer

def tokenize(text):
# 预分词处理:将文本拆分为初步的单词列表
pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
pre_tokenized_text = [word for word, offset in pre_tokenize_result]

print("\n初始预分词结果:")
print(pre_tokenized_text)

# 对每个单词进行标记
tokenized_words = []
for word in pre_tokenized_text:
tokens = []
print(f"\n正在标记单词: {word}")

while len(word) > 0:
i = len(word)
# 尝试匹配词汇表中的最长子词
while i > 0 and word[:i] not in vocab:
i -= 1
if i == 0:
print(f" [UNK] 标记: {word}")
tokens = ["[UNK]"] # 没有匹配到则返回 [UNK]
break # 跳出循环,不再继续处理该单词

# 匹配到子词,添加到 tokens 列表中
matched_token = word[:i]
tokens.append(matched_token)
print(f" 匹配到 Token: {matched_token}")

# 更新剩余部分,并添加“##”作为前缀
word = word[i:]
if len(word) > 0:
word = f"##{word}"
print(f" 剩余部分添加前缀: {word}")

print(f" 标记结果: {tokens}")
tokenized_words.append(tokens)

print("\n最终标记结果:")
flattened_tokens = sum(tokenized_words, []) # 展平成单层列表
print(flattened_tokens)

return flattened_tokens

# 示例词汇表
vocab = {'##st', 'n', '##i', '##s', 'wid', '##d', 'wi', '##r', '##o',
'lo', 'w', '##e', '##w', '##t', 'l'}

# 示例文本
text = "estimate, local, lows"

# 使用 BERT 的分词器(WordPiece)
tokenizer = AutoTokenizer.from_pretrained('bert-base-uncased')

# 调用 tokenize 函数,并打印中间过程
tokens = tokenize(text)

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
初始预分词结果:
['estimate', ',', 'local', ',', 'lows']

正在标记单词: estimate
[UNK] 标记: estimate
标记结果: ['[UNK]']

正在标记单词: ,
[UNK] 标记: ,
标记结果: ['[UNK]']

正在标记单词: local
匹配到 Token: lo
剩余部分添加前缀: ##cal
[UNK] 标记: ##cal
标记结果: ['[UNK]']

正在标记单词: ,
[UNK] 标记: ,
标记结果: ['[UNK]']

正在标记单词: lows
匹配到 Token: lo
剩余部分添加前缀: ##ws
匹配到 Token: ##w
剩余部分添加前缀: ##s
匹配到 Token: ##s
标记结果: ['lo', '##w', '##s']

最终标记结果:
['[UNK]', '[UNK]', '[UNK]', '[UNK]', 'lo', '##w', '##s']

分词(Transformers)

在 Transformers 中,分词(tokenization) 实际上包含以下几个步骤:

  1. 标准化(Normalization):对文本进行必要的清理操作,例如删除多余空格或重音符号、进行 Unicode 标准化等。
  2. 预分词(Pre-tokenization):将输入拆分为单词。
  3. 通过模型处理输入(Running the input through the model):使用预分词后的单词生成一系列词元(tokens)。
  4. 后处理(Post-processing):添加分词器的特殊标记,生成注意力掩码(attention mask)和词元类型 ID(token type IDs)。

!整体流程图,图源

整体流程图,图源

运行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from transformers import AutoTokenizer

# 加载 BERT 的分词器
tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")

# 原始文本
text = "Hello how are U tday"
print("原始文本:", text)

# 1. 标准化:转换为小写
normalized_text = text.lower()
print("标准化后的文本:", normalized_text)

# 2. 预分词(Pre-tokenization):将输入拆分为单词
pre_tokenized = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(normalized_text)
print("预分词结果:", pre_tokenized)

# 3. 分词:将预分词后的结果转换为子词级词元
tokens = tokenizer.tokenize(normalized_text)
print("词元(Tokens):", tokens)

# 4. 将 tokens 转换为 token IDs
token_ids = tokenizer.convert_tokens_to_ids(tokens)
print("词元 ID(Token IDs):", token_ids)

# 5. 编码(包含特殊标记和后处理)
encoded = tokenizer(normalized_text, return_tensors="pt")
print("编码结果:", encoded)

# 6. 打印注意力掩码和词元类型 ID(后处理部分)
print("注意力掩码(Attention Mask):", encoded["attention_mask"])
print("词元类型 ID(Token Type IDs):", encoded["token_type_ids"])

# 7. 解码:将 token IDs 转换回文本
decoded_text = tokenizer.decode(token_ids)
print("解码后的文本:", decoded_text)

输出

1
2
3
4
5
6
7
8
9
原始文本: Hello how are U tday
标准化后的文本: hello how are u tday
预分词结果: [('hello', (0, 5)), ('how', (6, 9)), ('are', (10, 13)), ('u', (14, 15)), ('tday', (16, 20))]
词元(Tokens): ['hello', 'how', 'are', 'u', 'td', '##ay']
词元 ID(Token IDs): [7592, 2129, 2024, 1057, 14595, 4710]
编码结果: {'input_ids': tensor([[ 101, 7592, 2129, 2024, 1057, 14595, 4710, 102]]), 'token_type_ids': tensor([[0, 0, 0, 0, 0, 0, 0, 0]]), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1]])}
注意力掩码(Attention Mask): tensor([[1, 1, 1, 1, 1, 1, 1, 1]])
词元类型 ID(Token Type IDs): tensor([[0, 0, 0, 0, 0, 0, 0, 0]])
解码后的文本: hello how are u tday
本文结束 感谢您的阅读
0%