|
Tokenizer 是自然语言处理中常用的工具,用于将文本数据转换为模型能够理解的输入形式。它的主要作用是将输入的文本分割成单词、子词或者字符,并将它们映射到对应的编号(或者词向量)上。
在深度学习中,文本数据通常需要转换成数值形式才能被神经网络处理,以下我们通过三个步骤,去了解Tokenizer底层原理的实现
1.准备数据
2.为数据进行标记
3.构建一个Tokenizer数据预处理工具
一、准备数据
先加载我们准备好的the-verdict.txt 英文文本,用英文内容主要是英文单词之间可以通过空格或标点符号就可以分割了,词汇量较少,单词之间的边界清晰,准确率高。中文词汇之间没有明显的分割符,而且还需要考虑上下文的语义关系,处理起来相对复杂,不方便我们开始的学习。
下面代码中我们主要是要对文本进行分割,按空格,逗号、句号等特殊符号分割
import re
with open("the-verdict.txt", "r", encoding="utf-8") as f: raw_text = f.read()
print("文本的大小:", len(raw_text)) print("查看开头的20个字符为:", raw_text[:20])
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', raw_text) preprocessed = [item.strip() for item in preprocessed if item.strip()] print(preprocessed[:20])
文本的大小: 20479 查看开头的20个字符为: I HAD always thought
['I', 'HAD', 'always', 'thought', 'Jack', 'Gisburn', 'rather', 'a', 'cheap', 'genius', '--', 'though', 'a', 'good', 'fellow', 'enough', '--', 'so', 'it', 'was']
二、为数据进行标记
接着对数据进行去重和排序,目的是构建一个词典,所以不需要重复的数据,通过单词,我们可以查到对应的单词索引。
all_words = sorted(list(set(preprocessed)))
# 构建一个词典表,用json来表示,key为单词,value为索引 vocab = {token:integer for integer,token in enumerate(all_words)}
for i, item in enumerate(vocab.items()): print(item) if i >= 20: break
输出的结果可以看到,排在前面的都是特殊字符,接着是我们切割好的单词,按字母进行排序
('!', 0) ('"', 1) ("'", 2) ('(', 3) (')', 4) (',', 5) ('--', 6) ('.', 7) (':', 8) (';', 9) ('?', 10) ('A', 11) ('Ah', 12) ('Among', 13) ('And', 14) ('Are', 15) ('Arrt', 16) ('As', 17) ('At', 18) ('Be', 19) ('Begin', 20)
三、构建一个Tokenizer数据预处理工具
现在我们要专门一个类来处理文本数据,主要两个功能,通过单词,可以找到对应的标记,通过标记可以找到对应的索引。
以下是我们工具的内容,虽然简单,但是包含的分词两个核心点,文本的编码和解码。
class TokenizerV1: # 对词表进行初始化,并创建两个变量,str_to_int为单词映射为数字 # int_to_str为数字映射单词 def __init__(self, vocab): self.str_to_int = vocab self.int_to_str = {i:s for s,i in vocab.items()} # 通过单词找到对应的数字标记 def encode(self, text): preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text) preprocessed = [item.strip() for item in preprocessed if item.strip()] ids = [self.str_to_int[s] for s in preprocessed] return ids # 通过数字标记找到对应的单词 def decode(self, ids): text = " ".join([self.int_to_str[i] for i in ids]) text = re.sub(r'\s+([,.?!"()\'])', r'\1', text) return text
TokenizerV1初始化,参数为我们的单词词典,并对一段句子进行单词转数字、数字转单词。
tokenizer = TokenizerV1(vocab)
text = """"Why _has_ he chucked painting?" I asked abruptly""" ids = tokenizer.encode(text) print(ids)
decodeText = tokenizer.decode(tokenizer.encode(text)) print(decodeText)
输出结果
[1, 115, 118, 537, 118, 541, 268, 766, 10, 1, 55, 184, 125]
" Why _ has _ he chucked painting?" I asked abruptly
我们已经有了文本处理工具了-TokenizerV1,但是它还不够完善,如果我们要查找一个在该文本词典没有的单词,就会出现错误。
要处理这种情况的话,我们要添加特殊的上下文标记,大家可以发散下思想,不同的文本处理,有他们特殊的文本处理方式,有兴趣的话可以多去了解其他库的一些分词处理。
在这里<|endoftext|>意思是为结束的句子增加结束标记。这通常用于连接多个不相关的文本或句子
<|unk|> 意思为未知单词的标记,如果该单词如果在字段中没有,那么我们就把他标记为<|unk|>
preprocessed = re.split(r'([,.?_!"()\']|--|\s)', raw_text) preprocessed = [item.strip() for item in preprocessed if item.strip()]
all_tokens = sorted(list(set(preprocessed))) # 在词典的最后增加两个特殊的标记 all_tokens.extend(["<|endoftext|>", "<|unk|>"])
vocab = {token:integer for integer,token in enumerate(all_tokens)}
for i, item in enumerate(list(vocab.items())[-5:]): print(item)
可以看到我们在词典的最后增加了对应的特殊标记
('younger', 1156) ('your', 1157) ('yourself', 1158) ('<|endoftext|>', 1159) ('<|unk|>', 1160)
我们重新完善下之前写的工具类
class TokenizerV2: def __init__(self, vocab): self.str_to_int = vocab self.int_to_str = { i:s for s,i in vocab.items()} def encode(self, text): preprocessed = re.split(r'([,.?_!"()\']|--|\s)', text) preprocessed = [item.strip() for item in preprocessed if item.strip()] # 增加了未知单词标记判断 preprocessed = [item if item in self.str_to_int else "<|unk|>" for item in preprocessed]
ids = [self.str_to_int[s] for s in preprocessed] return ids def decode(self, ids): text = " ".join([self.int_to_str[i] for i in ids]) # Replace spaces before the specified punctuations text = re.sub(r'\s+([,.?!"()\'])', r'\1', text) return text
再调用工具看看效果,对文本内容进行编码,我们在拼接句子时,在第一个句子后面增加的一个结束的特殊标记
tokenizer = TokenizerV2(vocab)
text1 = "Hello, do you like tea?" text2 = "In the sunlit terraces of the palace."
text = " <|endoftext|> ".join((text1, text2))
print(text)
encodeText = tokenizer.encode(text) print(encodeText)
以下是输出的结果,找找看下特殊标记的数字是多少?
[1160, 5, 362, 1155, 642, 1000, 10, 1159, 57, 1013, 981, 1009, 738, 1013, 1160, 7]
好了,本文主要介绍了文本的数据处理,并编写了一个词汇表文本处理工具TokenizerV2,同时我们实现了通过字符查找标记数字,通过数字查找字符,这两个方法是数据预处理的核心,我们想学习大语言模型,是绕不开它的。
|