文本预处理
- 1. 读取数据集
- 2. 词元化
- 3. 词表
- 4. 整合所有功能
- 5. 小结
-
将文本作为字符串加载到内存中。
-
将字符串拆分为词元(如单词和字符)。
-
建立一个词表,将拆分的词元映射到数字索引。
-
将文本转换为数字索引序列,方便模型操作。
import collections
import re
from d2l import torch as d2l
1. 读取数据集
从H.G.Well的时光机器中加载文本,只有30000多个单词。
strip()将删除字符串开头和结尾的空格
lower()将字符串中的大写字母转换为小写字母
#@save
d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt','090b5e7e70c295757f55df93cb0a180b9691891a')def read_time_machine(): #@save"""将时间机器数据集加载到文本行的列表中"""with open(d2l.download('time_machine'), 'r') as f:lines = f.readlines()# 忽略标点符号和大小写return [re.sub('[^A-Za-z]+', ' ', line).strip().lower() for line in lines]lines = read_time_machine()
print(f'# 文本总行数: {len(lines)}')
print(lines[0])
print(lines[10])
2. 词元化
- tokenize函数将文本行列表(lines)作为输入
- 列表中的每个元素是一个文本序列(如一条文本行)
- 每个文本序列又被拆分成一个词元列表,词元(token)是文本的基本单位
- 返回一个由词元列表组成的列表,其中的每个词元都是一个字符串(string)
# 如果是单词进行拆分返回,如果是字符次元返回列表
def tokenize(lines, token='word'): #@save"""将文本行拆分为单词或字符词元"""if token == 'word':# 以空格划分return [line.split() for line in lines]elif token == 'char':return [list(line) for line in lines]else:print('错误:未知词元类型:' + token)tokens = tokenize(lines)
for i in range(11):print(tokens[i])
3. 词表
词元是字符串,需要将其转换为数字再输入到模型中。我们构建一个字典(词表),使得词元映射到从0开始的数字索引中。对唯一次元进行统计,得到的统计结果称之为语料库。
根据每个唯一词元出现的频率分配一个数字索引,语料库不存在的或已删除的任何词元都将映射到一个特定的未知词元“”。
class Vocab: #@save"""文本词表"""# 使用两个字典将词元和索引相互映射起来,方便后续的查询操作def __init__(self, tokens=None, min_freq=0, reserved_tokens=None):if tokens is None:tokens = []if reserved_tokens is None:reserved_tokens = []# 按出现频率排序counter = count_corpus(tokens)self._token_freqs = sorted(counter.items(), key=lambda x: x[1],reverse=True)# 未知词元的索引为0self.idx_to_token = ['<unk>'] + reserved_tokensself.token_to_idx = {token: idxfor idx, token in enumerate(self.idx_to_token)}for token, freq in self._token_freqs:if freq < min_freq:breakif token not in self.token_to_idx:self.idx_to_token.append(token)self.token_to_idx[token] = len(self.idx_to_token) - 1# 返回词表中词元的数量def __len__(self):return len(self.idx_to_token)# 根据输入的词元或词元列表返回对应的索引或索引列表def __getitem__(self, tokens):if not isinstance(tokens, (list, tuple)):return self.token_to_idx.get(tokens, self.unk)return [self.__getitem__(token) for token in tokens]# 根据输入的索引或索引列表返回对应的词元或词元列表def to_tokens(self, indices):if not isinstance(indices, (list, tuple)):return self.idx_to_token[indices]return [self.idx_to_token[index] for index in indices]# 返回未知词元的索引,即0@propertydef unk(self): # 未知词元的索引为0return 0# 返回词元及其频率的列表@propertydef token_freqs(self):return self._token_freqsdef count_corpus(tokens): #@save"""统计词元的频率"""# 这里的tokens是1D列表或2D列表if len(tokens) == 0 or isinstance(tokens[0], list):# 将词元列表展平成一个列表tokens = [token for line in tokens for token in line]return collections.Counter(tokens)
使用时光机器数据集作为语料库来构建词表,然后打印前几个高频词元及其索引
# 使用时光机器数据集作为语料库来构建词表,然后打印前几个高频词元及其索引。
vocab = Vocab(tokens)
print(list(vocab.token_to_idx.items())[:10])
将每一条文本行转换成一个数字索引列表
# 将每一条文本行转换成一个数字索引列表
for i in [0, 10]:print('文本:', tokens[i])print('索引:', vocab[tokens[i]])
4. 整合所有功能
将所有功能打包到load_corpus_time_machine函数中, 该函数返回corpus(词元索引列表)和vocab(时光机器语料库的词表)。
def load_corpus_time_machine(max_tokens=-1): #@save"""返回时光机器数据集的词元索引列表和词表"""lines = read_time_machine()tokens = tokenize(lines, 'char')vocab = Vocab(tokens)# 因为时光机器数据集中的每个文本行不一定是一个句子或一个段落,# 所以将所有文本行展平到一个列表中corpus = [vocab[token] for line in tokens for token in line]if max_tokens > 0:corpus = corpus[:max_tokens]return corpus, vocabcorpus, vocab = load_corpus_time_machine()
len(corpus), len(vocab)
5. 小结
为了对文本进行预处理,我们通常将文本拆分为词元,构建词表将词元字符串映射为数字索引,并将文本数据转换为词元索引以供模型操作。