1. 文本预处理
这里语言预处理的目的是把文本变成一个词频字典,即将语料中的单词出现的次数做统计,实现的结果就是生成一个由 单词唯一索引 : 预料中该单词出现的频率键值对组成的词典。
实现步骤分为三步:
- 读入文本
import re
def read_time_machine():
with open('/home/kesci/input/timemachine7163/timemachine.txt', 'r') as f:
lines = [re.sub('[^a-z]+', ' ', line.strip().lower()) for line in f]
return lines
- 分词
# 简易分词
def tokenize(sentences, token='word'):
"""Split sentences into word or char tokens"""
if token == 'word':
return [sentence.split(' ') for sentence in sentences]
elif token == 'char':
return [list(sentence) for sentence in sentences]
else:
print('ERROR: unkown token type '+token)
以上自定义函数依然存在缺点,一些现有工具可以很好地完成分词步骤,例如spaCy
和NLTK
,另外中文分词一般用jieba
text = "Mr. Chen doesn't agree with my suggestion."
# spaCy
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp(text)
# NLTK
from nltk.tokenize import word_tokenize
from nltk import data
data.path.append('/home/kesci/input/nltk_data3784/nltk_data')
print(word_tokenize(text))
- 建立词典
def count_corpus(sentences):
tokens = [tk for st in sentences for tk in st]
class Vocab(object):
def __init__(self, tokens, min_freq=0, use_special_tokens=False):
counter = count_corpus(tokens) # : (key, value):(词, 词频)
self.token_freqs = list(counter.items())
self.idx_to_token = []
if use_special_tokens:
# padding, begin of sentence, end of sentence, unknown
self.pad, self.bos, self.eos, self.unk = (0, 1, 2, 3)
self.idx_to_token += ['', '', '', '']
else:
self.unk = 0
self.idx_to_token += ['']
self.idx_to_token += [token for token, freq in self.token_freqs
if freq >= min_freq and token not in self.idx_to_token]
self.token_to_idx = dict()
for idx, token in enumerate(self.idx_to_token):
self.token_to_idx[token] = idx
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]
2. 语言模型
2.1 语言模型
把一段文本看作是一个离散时间序列,若给定长度,则各个时间点上依次生成,语言模型的目标是评估该序列是否合理,即计算该序列出现的概率.
2.2 马尔可夫假设与n远语法
首先接受以下假设,其中为以开头的文本数量,同理:
阶马尔可夫模型假设:
即该序列中某一时间的状态与前个时间点有关。元语法实际上就是阶马尔可夫模型,某个位置出现的某单词的概率仅与包括自己在内的前个位置的状态有关。特别地,时,该位置的单词独立。
元语法的缺陷:
- 参数空间过大
- 数据稀疏
2.3 时序数据采样
首先给定文本为“想要有直升机,想要和你飞到宇宙去,想要和”,假设时间步数为5,样本序列为5个字符。该样本的标签序列为这些字符分别在训练集中的下一个字符采样的数据和标签分别为“想要有直升”,“要有直升机”。如果序列的长度为,时间步数为,那么一共有个合法的样本,但是这些样本有大量的重合,有两种高效的采样方式:随机采样和相邻采样。
2.2.1 随机采样
字面意思,就是在序列里先划分,再随机取
import torch
import random
def data_iter_random(corpus_indices, batch_size, num_steps, device=None):
# 减1是因为对于长度为n的序列,X最多只有包含其中的前n - 1个字符
num_examples = (len(corpus_indices) - 1) // num_steps # 下取整,得到不重叠情况下的样本个数
example_indices = [i * num_steps for i in range(num_examples)] # 每个样本的第一个字符在corpus_indices中的下标
random.shuffle(example_indices)
def _data(i):
# 返回从i开始的长为num_steps的序列
return corpus_indices[i: i + num_steps]
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
for i in range(0, num_examples, batch_size):
# 每次选出batch_size个随机样本
batch_indices = example_indices[i: i + batch_size] # 当前batch的各个样本的首字符的下标
X = [_data(j) for j in batch_indices]
Y = [_data(j + 1) for j in batch_indices]
yield torch.tensor(X, device=device), torch.tensor(Y, device=device)
2.2.2 相邻采样
先按需要的batct_size将序列划分成等长batch_size份,然后在每部分中的相同位置抽取样本,得到第一个batch;接着往后相邻的位置抽取下一个batch
def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
corpus_len = len(corpus_indices) // batch_size * batch_size # 保留下来的序列的长度
corpus_indices = corpus_indices[: corpus_len] # 仅保留前corpus_len个字符
indices = torch.tensor(corpus_indices, device=device)
indices = indices.view(batch_size, -1) # resize成(batch_size, )
batch_num = (indices.shape[1] - 1) // num_steps
for i in range(batch_num):
i = i * num_steps
X = indices[:, i: i + num_steps]
Y = indices[:, i + 1: i + num_steps + 1]
yield X, Y
网友评论