参考沐神
8.2. 文本预处理 — 动手学深度学习 2.0.0-beta0 documentationhttps://zh-v2.d2l.ai/chapter_recurrent-neural-networks/text-preprocessing.html
做了什么事??
- 读取《时光机器》这本书
- 建立词表
- 通过词表将这本书转化为向量表示?
Code?
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@Project :深度学习入门
@File :文本预处理.py
@Author :little_spice
@Date :2022/5/11 18:13
"""
import collections
import re
from d2l import torch as d2l
# 读取数据集:从H.G.Well的《时光机器》中加载?本。为简单起?,我们在这?忽略了标点符号和字??写。
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为一维列表,每一个元素代表一行文本
lines = read_time_machine()
print(f"# 文本总行数:{len(lines)}")
# print(lines[0:4])
# print(lines[0])
# print(lines[10])
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为二维列表,表示将原始文本的每一行都切成词元后的结果
tokens = tokenize(lines)
print(tokens[0])
"""
构建?个字典,通常也叫做词表(vocabulary)
?来将字符串类型的词元映射到从0开始的数字索引中。我们先将训练集中的所有?档合并在?起,对它们的唯?词元进?统计,得到的统计结果称之为语料(corpus)。
然后根据每个唯?词元的出现频率,为其分配?个数字索引。很少出现的词元通常被移除,这可以降低复杂性。另外,语料库中不存在或已删除的任何词元都将映射到?个
特定的未知词元“<unk>”。我们可以选择增加?个列表,?于保存那些被保留的词元,例如:填充词元(“<pad>”);序列开始词元(“<bos>”);序列结束词元(“<eos>”)。
"""
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)
# 对字典按值降序排序
self._token_freqs = sorted(counter.items(),key=lambda x:x[1],reverse=True)
# 定义词表,即 下标:词元 映射,其中未知词元的索引为0,保留的词元跟在未知词元后面,往后新增词元依次加到保留词元后面
self.idx_to_token = ['<unk>']+reserved_tokens
# 用字典存储 词元和对应的下标idx
self.token_to_idx = {token:idx for idx,token in enumerate(self.idx_to_token)}
for token,freq in self._token_freqs:
# 出现次数小于min_freq的词元我们就直接舍弃了
if freq < min_freq:
break
# 如果该词元不在 下标:词元 映射中就把该词元加进去,同时更新{词元:对应的下标idx}的字典
if 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):
# 判断tokens是不是列表或元组,如果是,则返回tokens对应的idx值
if not isinstance(tokens,(list,tuple)):
# 如果token_to_idx里有tokens就返回对应的值,否则返回self.unk
return self.token_to_idx.get(tokens,self.unk)
return [self.__getitem__(token) for token in tokens]
# 用@property修饰器修饰方法有两个作用,1是将方法变成属性调用的形式,2是属性是私有的,用户不可修改。
# 此处的意思是 未知词元的索引为0
@property
def unk(self):
return 0
@property
def token_freqs(self):
return self._token_freqs
# 统计词元的频率,这里的tokens是1维或2维列表
def count_corpus(tokens):
# 一维列表为空 or 是二维列表
if len(tokens) == 0 or isinstance(tokens[0],list):
# 将词元列表展平成一个列表
tokens = [token for line in tokens for token in line]
# 返回字典形式的key:value统计个数,注意没有排序哦!
return collections.Counter(tokens)
# 使?时光机器数据集作为语料库来构建词表,然后打印前?个?频词元及其索引。
vocab = Vocab(tokens)
# 打印前10条高频的瞧一瞧~
print(list(vocab.token_to_idx.items())[:10])
# 现在,我们可以将每?条?本?转换成?个数字索引列表。
# 打印第0行和第10行文本以及对应的索引
for i in [0,10]:
print('文本:',tokens[i])
print('索引:',vocab[tokens[i]])
# 整合所有功能
"""
在使?上述函数时,我们将所有功能打包到load_corpus_time_machine函数中,该函数返回
corpus(词元索引列表)和vocab(时光机器语料库的词表)。我们在这?所做的改变是:
1. 为了简化后?章节中的训练,我们使?字符(?不是单词)实现?本词元化;
2. 时光机器数据集中的每个?本?不?定是?个句?或?个段落,还可能是?个单词,因此返回
的corpus仅处理为单个列表,?不是使?多词元列表构成的?个列表。
"""
def load_corpus_time_machine(max_tokens=-1):
"""返回时光机器数据集的词元索引列表和词表"""
lines = read_time_machine()
tokens = tokenize(lines,'char')
vocab = Vocab(tokens)
# 因为时光机器数据集中的每个?本?不?定是?个句?或?个段落,所以将所有?本?展平到?个列表中
"""
这里等价于
for line in tokens:
for token in line:
corpus.append(vocab[token])
也就是对于每一行的每个词元,我都去查一下词表,然后添加到corpus里面。
所以corpus存储的是《时光机器》这本书的词元表示,vocab是词表。
搞懂这个其实就可以了!
"""
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()
print(len(corpus),len(vocab))
print(corpus[:4],vocab.token_to_idx)
|