赞
踩
引用翻译:《动手学深度学习》
语言符号(又称词)的数量很大,而且分布很不均匀。因此,预测下一个符号的简单多类分类方法并不总是很有效。此外,我们需要把文本变成我们可以优化的格式,即我们需要把它映射到向量。在其极端情况下,我们有两种选择。一种是将每个词作为一个独特的实体,例如Salton.Wong.Yang.1975。这种策略的问题是,对于非常大的、多样化的语料库,我们很可能要处理100,000到1,000,000个向量。
另一个极端是每次预测一个字符的策略,如Ling等人,2015年提出的。两种策略之间的一个很好的平衡点是字节对编码,如Sennrich、Haddow和Birch, 2015年为神经机器翻译的目的所描述的。它将文本分解为经常出现的类似音节的片段。这使得模型能够根据先前查看的单词,如异质、同质、图和五边形,生成异质或五边形等单词。探讨这些模型的细节已经超出了本章的范围。我们将在以后讨论自然语言处理(chapter_nlp)时更详细地讨论这个问题。我只想说,它可以大大促进自然语言处理模型的准确性。
为了简单起见,我们将把自己限制在纯字符序列上。我们像以前一样使用H.G. Wells的The Timemachine。我们首先对文本进行过滤,并将其转换为一个字符ID的序列。
和以前一样,我们开始加载数据,并将其映射为一连串的空白处、标点符号和常规字符。预处理是最小的,我们只限于去除多个空白。
import sys
sys.path.insert(0, '..')
import torch
import random
import collections
with open('../data/timemachine.txt', 'r') as f:
raw_text = f.read()
print(raw_text[0:210]) # raw_text存储的是文本,未经过任何处理
The Time Machine, by H. G. Wells [1898]
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
接下来,我们需要将数据集,即一个字符串,分割成标记。一个标记是模型要训练和预测的一个数据点。我们通常使用一个词或一个字符作为一个标记。
lines = raw_text.split('\n')
text = ' '.join(' '.join(lines).lower().split()) # 全转化为小写,且将各句用空格连接起来
print('# of chars:', len(text))
print(text[0:70])
# of chars: 178605
the time machine, by h. g. wells [1898] i the time traveller (for so i
然后,我们需要将令牌映射成数字索引。我们通常称它为词汇表。它的输入是一个标记的列表,称为语料库。
然后,它计算每个标记在这个语料库中的频率,然后根据其频率给每个标记分配一个数字索引。很少出现的标记经常被删除以减少复杂性。
一个在语料库中不存在或已被删除的标记被映射为一个特殊的未知(“< unk>”)标记。我们还可以选择添加另外三个特殊标记。"< pad>“是一个用于填充的标记,”< bos>“表示一个句子的开始,”< eos>"表示一个句子的结束。
class Vocab(object): def __init__(self, tokens, min_freq=0, use_special_tokens=False): # 通过frequency and token排序 counter = collections.Counter(tokens) # 对词进行统计计数 token_freqs = sorted(counter.items(), key=lambda x: x[0]) # 根据统计的结果进行排序,全量的排序结果 token_freqs.sort(key=lambda x: x[1], reverse=True) # 根据token名称进行排序,相当于优先以频率排,再以名称排 if use_special_tokens: # 填充,句首,句尾,未知 self.pad, self.bos, self.eos, self.unk = (0, 1, 2, 3) tokens = ['<pad>', '<bos>', '<eos>', '<unk>'] # 对四种类型标记建立标签映射 else: self.unk = 0 tokens = ['<unk>'] tokens += [token for token, freq in token_freqs if freq >= min_freq] # 对于频率较低的token进行过滤 self.idx_to_token = [] self.token_to_idx = dict() # 建立token和id的映射词典,即vocab.txt for token in tokens: self.idx_to_token.append(token) # 记录token词 self.token_to_idx[token] = len(self.idx_to_token) - 1 # 按顺序从0开始编号 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) # 未出现词添加unk标签 else: 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] else: return [self.idx_to_token[index] for index in indices]
我们以时间机器数据集为语料库构建了一个词汇表,然后打印标记与索引之间的映射。
vocab = Vocab(text)
print(vocab.token_to_idx)
{'<unk>': 0, ' ': 1, 'e': 2, 't': 3, 'a': 4, 'i': 5, 'n': 6, 'o': 7, 's': 8, 'h': 9, 'r': 10, 'd': 11, 'l': 12, 'm': 13, 'u': 14, 'c': 15, 'f': 16, 'w': 17, 'g': 18, 'y': 19, 'p': 20, ',': 21, 'b': 22, '.': 23, 'v': 24, 'k': 25, "'": 26, '-': 27, 'x': 28, 'z': 29, ';': 30, 'j': 31, '?': 32, 'q': 33, '!': 34, '"': 35, '_': 36, ':': 37, '(': 38, ')': 39, '8': 40, '[': 41, ']': 42, '1': 43, '9': 44}
之后,训练数据集中的每个字符都被转换为一个索引ID。为了说明问题,我们打印前20个字符和它们相应的索引。
corpus_indices = [vocab[char] for char in text] # 将字符转化为index编码
sample = corpus_indices[:15]
print('chars:', [vocab.idx_to_token[idx] for idx in sample]) # 将idx转化为字符
print('indices:', sample)
chars: ['t', 'h', 'e', ' ', 't', 'i', 'm', 'e', ' ', 'm', 'a', 'c', 'h', 'i', 'n']
indices: [3, 9, 2, 1, 3, 5, 13, 2, 1, 13, 4, 15, 9, 5, 6]
在训练过程中,我们需要随机读取小型批次的例子和标签。由于序列数据在本质上是有顺序的,我们需要解决处理它的问题。当我们在chapter_sequence中介绍时,我们是以一种相当临时的方式进行的。让我们把这个问题正式化一下。考虑一下我们刚刚处理的这本书的开头。如果我们想把它分割成每个5个符号的序列,我们有相当大的自由,因为我们可以选择一个任意的偏移量。
图:拆分文本时,不同的偏移量会导致不同的子序列。
事实上,这些偏移量中的任何一个都是可以的。因此,我们应该选择哪一个呢?
事实上,所有的偏移量都是一样好的。但是如果我们挑选所有的偏移量,由于重叠,我们最终会得到相当多余的数据,特别是如果序列很长的话。
仅仅选取一组随机的初始位置也不好,因为它不能保证阵列的均匀覆盖。
例如,如果我们从一组
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。