# 文本预处理
# 读取数据集
| import collections |
| import re |
| from d2l import torch as d2l |
| d2l.DATA_HUB['time_machine'] = (d2l.DATA_URL + 'timemachine.txt','090b5e7e70c295757f55df93cb0a180b9691891a') |
| |
| def read_time_machine(): |
| 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]) |
文本总行数:3221
the time machine by h g wells
twinkled and his usually pale face was flushed and animated the
# 词元化
| 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) |
| 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']
# 词表
实际是一个字典,用来将字符串类型的词元映射到从 0 开始的数字索引中。我们对训练集的所有文档合并对他们的唯一词元进行统计,得到的结果为语料库。根据每个词元出现的频率为其分配索引。
| class Vocab: |
| 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) |
| """ |
| * counter.items() 是字典的一个方法。它返回一个包含字典所有项((键, 值) 对,即 (key, value) tuples)的视图对象。例如,如果 counter 是 {'the': 10, 'a': 5},counter.items() 会提供像 ('the', 10) 和 ('a', 5) 这样的元素。 |
| * sorted() 在对元素进行比较之前,会先将每个元素传递给 key 指定的函数,然后根据这些函数返回的结果进行排序。 |
| * lambda匿名函数见下 |
| """ |
| self._token_freqs = sorted(counter.items(), key=lambda x:x[1], reverse=True) |
| |
| |
| self.idx_to_token = ['<unk>'] + reserved_tokens |
| self.token_to_idx = {token: idx for idx, token in enumerate(self.idx_to_token)} |
| |
| for token, freq in self._token_freqs: |
| if freq < min_freq: |
| break |
| if token not in self.token_to_idx: |
| self.idx_to_token.append(token) |
| self.token_to_idx[token] = len(self.idx_to_token) |
| |
| |
| 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] |
| |
| |
| @property |
| def unk(self): |
| return 0 |
| @property |
| def token_freqs(self): |
| return self._token_freqs |
| |
| def count_corpus(tokens): |
| """统计词元出现的频率""" |
| if len(tokens) == 0 or isinstance(tokens[0], list): |
| |
| tokens = [token for line in tokens for token in line] |
| return collections.Counter(tokens) |
lambda
匿名函数 (Anonymous Function): lambda x: x[1]
定义了一个简单的匿名函数(没有名字的函数)。
lambda
: 关键字,表示这是一个 lambda 函数。
x
: 参数名,代表输入给这个 lambda 函数的单个元素。在这个上下文中,因为 sorted
正在处理 counter.items()
的结果,所以 x
会是一个 (键, 值)
对的元组,例如 ('the', 10)
。
: x[1]
: 冒号后面是函数的返回值。 x[1]
表示取元组 x
中索引为 1 的元素,也就是那个键值对中的值 (value)—— 即词元的频率。
-
key=lambda x: x[1]
的整体作用:告诉 sorted
函数,在排序 (词元, 频率)
对时,应该根据频率(元组的第二个元素 x[1]
)来排序,而不是根据词元本身(元组的第一个元素 x[0]
)。
| vocab = Vocab(tokens) |
| print(list(vocab.token_to_idx.items())[:10]) |
[('<unk>', 0), ('the', 2), ('i', 3), ('and', 4), ('of', 5), ('a', 6), ('to', 7), ('was', 8), ('in', 9), ('that', 10)]
| for i in [0,10]: |
| print('文本: ', tokens[i]) |
| print('索引: ', vocab[tokens[i]]) |
文本: ['the', 'time', 'machine', 'by', 'h', 'g', 'wells']
索引: [2, 20, 51, 41, 2184, 2185, 401]
文本: ['twinkled', 'and', 'his', 'usually', 'pale', 'face', 'was', 'flushed', 'and', 'animated', 'the']
索引: [2187, 4, 26, 1045, 363, 114, 8, 1422, 4, 1046, 2]
| def load_corpus_time_machine(max_tokens=-1): |
| """返回时光机器数据集的词元索引列表和词表""" |
| lines = read_time_machine() |
| tokens = tokenize(lines,'char') |
| vocab = Vocab(tokens) |
| |
| corpus = [vocab[token] for line in tokens for token in lines] |
| if max_tokens > 0: |
| corpus = corpus[:max_tokens] |
| return corpus,vocab |
| |
| corpus,vocab = load_corpus_time_machine() |
| len(corpus),len(vocab) |
(10374841, 28)