机器学习 / A LEAF IN THE GARDEN
文本预处理
机器学习 · 动手深度学习。来自 Daily Learning 的学习笔记。
简介
把文本视为时序序列,每一个词或 token 作为一个时间步,将文本转换为张量形式。
实现
代码 4 · In [1]
import collections
import random
import re
import torch
import util
导入数据集
代码 6 · In [3]
def read_time_machine():
"""将《Time Machine》小说加载到文本行的列表中"""
with open(util.data.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'lines: {len(lines)}')
print(lines[0])
print(lines[10])
已保存输出
正在从https://d2l-data.s3-accelerate.amazonaws.com/timemachine.txt下载./data/timemachine.txt... lines: 3221 the time machine by h g wells twinkled and his usually pale face was flushed and animated the
文本序列拆分成 token 列表
代码 8 · In [4]
def tokenize(lines, token='word'):
"""将文本行拆分为单词或字符"""
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, 'word')
for i in range(11):
print(tokens[i])
已保存输出
['the', 'time', 'machine', 'by', 'h', 'g', 'wells'] [] [] [] [] ['i'] [] [] ['the', 'time', 'traveller', 'for', 'so', 'it', 'will', 'be', 'convenient', 'to', 'speak', 'of', 'him'] ['was', 'expounding', 'a', 'recondite', 'matter', 'to', 'us', 'his', 'grey', 'eyes', 'shone', 'and'] ['twinkled', 'and', 'his', 'usually', 'pale', 'face', 'was', 'flushed', 'and', 'animated', 'the']
构建词汇表(Vocabulary),把 token 映射为 id
代码 10 · In [6]
class Vocab:
"""文本词汇表"""
def __init__(self, tokens=None, min_freq: int=0, reserved_tokens=None):
"""通过传入的 token 列表构建词汇表
:param tokens: 列表,包含所有文本的 token
:param min_freq: token 的最小频率(频率低于该值的 token 会被忽略)
:param reserved_tokens: 保留的 token 列表(BOS、EOS、PAD 等)
"""
if tokens is None:
tokens = []
if reserved_tokens is None:
reserved_tokens = []
# 按出现频率排序
counter = count_corpus(tokens)
self._token_freqs = sorted(counter.items(), # token-频率 对
key=lambda x: x[1], # 按频率排序
reverse=True) # 降序
# 未知词元索引为 0
self.unk = 0 # 未知词元的索引
unique_tokens = ['<unk>'] + reserved_tokens
# 按频率添加 token
unique_tokens += [token for token, freq in self._token_freqs
if freq >= min_freq and
token not in unique_tokens]
# 构建映射表
self.idx_to_token, self.token_to_idx = [], dict()
for token in unique_tokens:
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):
"""获取 token 的索引"""
if not isinstance(tokens, (list, tuple)): # 单个 token
return self.token_to_idx.get(tokens, self.unk) # 返回索引,若 token 不在词汇表中则返回 unk 索引
return [self.__getitem__(token) for token in tokens] # token 列表(递归调用)
def to_tokens(self, indices):
"""获取索引对应的 token"""
if not isinstance(indices, (list, tuple)): # 单个索引
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices] # 索引列表
def count_corpus(tokens):
"""统计 token 的频率"""
# 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)
构建
代码 12 · In [7]
vocab = Vocab(tokens)
print(list(vocab.token_to_idx.items())[:10]) # 打印前 10 个
已保存输出
[('<unk>', 0), ('the', 1), ('i', 2), ('and', 3), ('of', 4), ('a', 5), ('to', 6), ('was', 7), ('in', 8), ('that', 9)]
文本转成 id 列表
代码 14 · In [8]
for i in [0, 10]:
print('tokens:', tokens[i])
print('indices:', vocab[tokens[i]])
已保存输出
tokens: ['the', 'time', 'machine', 'by', 'h', 'g', 'wells'] indices: [1, 19, 50, 40, 2183, 2184, 400] tokens: ['twinkled', 'and', 'his', 'usually', 'pale', 'face', 'was', 'flushed', 'and', 'animated', 'the'] indices: [2186, 3, 25, 1044, 362, 113, 7, 1421, 3, 1045, 1]
打包所有功能
代码 16 · In [10]
def load_corpus_time_machine(max_tokens=-1, token='char'):
"""返回时序数据和词汇表
:param max_tokens: 最大 token 数量,若为 -1 则不限制
:param token: 'word' 或 'char',按单词或字符拆分
:return: corpus (列表,包含所有 token 的索引) 和 vocab (词汇表)
"""
lines = read_time_machine()
tokens = tokenize(lines, token) # 按字符拆分
vocab = Vocab(tokens) # 构建词汇表
# 将所有 token 转成索引
corpus = [vocab[token] for line in tokens for token in line]
if max_tokens > 0:
corpus = corpus[:max_tokens]
return corpus, vocab
corpus, vocab = load_corpus_time_machine(token='word')
len(corpus), len(vocab)
已保存输出
(32775, 4580)