最近接触文本处理,查询了一些资料,记录一下中文文本编码的处理方法吧。
先下载模型和词表:bert-base-chinese镜像下载
如下图示,下载好的以下文件均存放在 bert-base-chinese 文件夹下
1. 词编码嵌入简介
按我通俗的理解,就是文本要进入模型,得编码成数字的形式,那么,怎么给定数字的形式呢,不能随便给一个数字吧,此时就需要一个词表,该表中有很多很多的字,每个字都有在该表中唯一的位置,每个字编码时,采用其在词表中的位置。
下载文件中的 vocab.txt 就是已经设定好的词表,打开看看:
2. 词编码嵌入实现
利用transformers库中的BertTokenizer实现分词编码,实例化一个tokenizer,载入预先下载好的词表,调用encode函数进行编码,encode函数有5个常用参数:
①text: 需要编码的文本;
②add_special_tokens: 是否添加特殊token,即CLS分类token和SEP分隔token;
③max_length: 文本的最大长度,根据需要处理的最长文本长度设置;
④pad_to_max_length: 是否填充到最大长度,以0补位;
⑤return_tensors: 返回的tensor类型,有4种为 [‘pt’, ‘tf’, ‘np’, ‘jax’] 分别代表 pytorch tensor、tensorflow tensor、int32数组形式和 jax tensor;
from transformers import BertTokenizer
bert_name = './bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(bert_name)
text = '一念月落,一念身错,一念关山难涉过。棋逢过客,执子者不问因果。'
input_ids = tokenizer.encode(
text,
add_special_tokens=True,
max_length=128,
pad_to_max_length=True,
return_tensors='pt'
)
print('text:\n', text)
print('text字符数:', len(text))
print('input_ids:\n', input_ids)
print('input_ids大小:', input_ids.size())
输出为:
text:
一念月落,一念身错,一念关山难涉过。棋逢过客,执子者不问因果。
text字符数: 31
input_ids:
tensor([[ 101, 671, 2573, 3299, 5862, 8024, 671, 2573, 6716, 7231, 8024, 671,
2573, 1068, 2255, 7410, 3868, 6814, 511, 3470, 6864, 6814, 2145, 8024,
2809, 2094, 5442, 679, 7309, 1728, 3362, 511, 102, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0]])
input_ids大小: torch.Size([1, 128])
查看一下tokenizer的信息:
可以看到整个词表的大小为21128个字,共有5种特殊token标记:
[PAD]: 填充标记,编码为0;
[UNK]: 未知字符标记,即该字不在所定义的词表中,编码为100;
[CLS]: 分类标记,蕴含整个文本的含义,编码为101;
[SEP]: 分隔字符标记,用于断开两句话,编码为102;
[MASK]: 掩码标记,该字被遮挡,编码为103;
测试一下这些特殊token:
from transformers import BertTokenizer
bert_name = './bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(bert_name)
text = '[CLS]一念月落,一念身错,[SEP]一念关山难涉过。[MASK]逢过客,执子者不问因果。[PAD][PAD][PAD],檒檒'
input_ids = tokenizer.encode(
text,
add_special_tokens=False,
max_length=128,
pad_to_max_length=False,
return_tensors='pt'
)
print('text:\n', text)
print('text字符数:', len(text))
print('input_ids:\n', input_ids)
print('input_ids大小:', input_ids.size())
输出为:
text:
[CLS]一念月落,一念身错,[SEP]一念关山难涉过。[MASK]逢过客,执子者不问因果。[PAD][PAD][PAD],檒檒
text字符数: 64
input_ids:
tensor([[ 101, 671, 2573, 3299, 5862, 8024, 671, 2573, 6716, 7231, 8024, 102,
671, 2573, 1068, 2255, 7410, 3868, 6814, 511, 103, 6864, 6814, 2145,
8024, 2809, 2094, 5442, 679, 7309, 1728, 3362, 511, 0, 0, 0,
117, 100, 100]])
input_ids大小: torch.Size([1, 39])
也可以利用tokenize函数直接实现分词,并采用convert_tokens_to_ids函数和convert_ids_to_tokens函数实现词与编码的相互转换:
from transformers import BertTokenizer
bert_name = './bert-base-chinese'
tokenizer = BertTokenizer.from_pretrained(bert_name)
text = '一念月落,一念身错,一念关山难涉过。棋逢过客,执子者不问因果。'
tokens = tokenizer.tokenize(text)
input_ids = tokenizer.convert_tokens_to_ids(tokens)
tokenxx = tokenizer.convert_ids_to_tokens(input_ids)
print('中文分词:\n', tokens)
print('分词-->编码:\n', input_ids)
print('编码-->分词:\n', tokenxx)
输出为:
中文分词:
['一', '念', '月', '落', ',', '一', '念', '身', '错', ',', '一', '念', '关', '山', '难', '涉', '过', '。', '棋', '逢', '过', '客', ',', '执', '子', '者', '不', '问', '因', '果', '。']
分词-->编码:
[671, 2573, 3299, 5862, 8024, 671, 2573, 6716, 7231, 8024, 671, 2573, 1068, 2255, 7410, 3868, 6814, 511, 3470, 6864, 6814, 2145, 8024, 2809, 2094, 5442, 679, 7309, 1728, 3362, 511]
编码-->分词:
['一', '念', '月', '落', ',', '一', '念', '身', '错', ',', '一', '念', '关', '山', '难', '涉', '过', '。', '棋', '逢', '过', '客', ',', '执', '子', '者', '不', '问', '因', '果', '。']
除了BertTokenizer,还有AutoTokenizer也是常用的分词类,使用方法与BertTokenizer类似,可以参考这篇文章了解不同的Tokenizer。