语言模型
语言模型
假设训练数据集是一个大型的文本语料库,训练集中词的概率可以根据给定词的相对词频来计算:一种稍微不太精确的方法是统计单词在数据集中的出现次数,然后将其除以整个语料库中的单词总数
语言模型的应用
使用计数来建模
假设序列长度为 2 ,可以预测
拓展到序列长度为 3 的情况
N 元语法
如果
成立,则序列上的分布满足一阶马尔科夫性质,且阶数越高,对应的依赖关系就越长
一元语法(unigram)
二元语法(bigram)
三元语法(trigram)
N 元语法的好处
自然语言统计
停用词(stop words)
读取长序列数据
总体策略
假设使用神经网络来训练语言模型,模型中的网络一次处理具有预定义长度(例如 n 个时间步)的一个小批量序列
如何随机生成一个小批量数据的特征和标签以供读取?
1、文本序列可以是任意长的,因此任意长的序列可以被划分为具有相同时间步数的子序列
2、训练神经网络时,将这些具有相同步数的小批量子序列输入到模型中
3、由于可以选择任意偏移量来指示初始位置,因此具有相当大的自由度
随机采样(random sampling)
顺序分区(sequential partitioning)
总结
1、语言模型是自然语言处理的关键,语言模型其实就是估计文本序列的联合概率,也是 NLP 领域最常见的应用
2、使用统计方法时通常采用 n元语法,每次看一个长为 n 的子序列来进行计数,对于给定的长序列拆分成很多个连续的长度为 N 的子序列,就能够计算文本序列的联合概率了。n元语法通过截断相关性,为处理长序列提供了一种实用的模型(长序列的问题在于它们很少出现或者从不出现)
3、齐普夫定律(Zipf's law)支配着单词的分布,这个分布不仅适用于一元语法,还适用于其他 n 元语法
4、通过拉普拉斯平滑法可以有效地处理结构丰富而频率不足的低频词词组
5、读取长序列的主要方式是随机采样和顺序分区。在迭代过程中,后者可以保证来自两个相邻的小批量中的子序列在原始序列上也是相邻的
代码:
import random
import torch
from d2l import torch as d2l
tokens = d2l.tokenize(d2l.read_time_machine())
corpus = [token for line in tokens for token in line]
vocab = d2l.Vocab(corpus)
vocab.token_freqs[:10]
# 最流行的词被称为停用词画出的词频图
freqs = [freq for token, freq in vocab.token_freqs]
d2l.plot(freqs, xlabel='token: x', ylabel='frequency: n(x)', xscale='log', yscale='log')
# 其他的词元组合,比如二元语法、三元语法等等,又会如何呢?
bigram_tokens = [pair for pair in zip(corpus[:-1], corpus[1:])] # 二元语法
bigram_vocab = d2l.Vocab(bigram_tokens)
bigram_vocab.token_freqs[:10]
# 三元语法
trigram_tokens = [triple for triple in zip(corpus[:-2],corpus[1:-1],corpus[2:])]
trigram_vocab = d2l.Vocab(trigram_tokens)
trigram_vocab.token_freqs[:10]
# 直观地对比三种模型中的标记频率
bigram_freqs = [freq for token, freq in bigram_vocab.token_freqs]
trigram_freqs = [freq for token, freq in trigram_vocab.token_freqs]
d2l.plot([freqs, bigram_freqs, trigram_freqs], xlabel='token: x',
ylabel='frequency: n(x)', xscale='log', yscale='log',
legend=['unigram','bigram','trigram'])
# 随即生成一个小批量数据的特征和标签以供读取
# 在随即采样中,每个样本都是在原始的长序列上任意捕获的子序列
# 给一段很长的序列,连续切成很多段长为T的子序列
# 一开始加了一点随即,使得每次切的都不一样
# 取随即批量的时候,再随即把它们取出来
def seq_data_iter_random(corpus, batch_size, num_steps):
"""使用随即抽样生成一个小批量子序列"""
corpus = corpus[random.randint(0, num_steps - 1):]
num_subseqs = (len(corpus) - 1) // num_steps
initial_indices = list(range(0, num_subseqs * num_steps, num_steps))
random.shuffle(initial_indices)
def data(pos):
return corpus[pos:pos + num_steps]
num_batches = num_subseqs // batch_size
for i in range(0, batch_size * num_batches, batch_size):
initial_indices_per_batch = initial_indices[i:i + batch_size]
X = [data(j) for j in initial_indices_per_batch]
Y = [data(j+1) for j in initial_indices_per_batch]
yield torch.tensor(X), torch.tensor(Y)
# 生成一个从0到34的序列
my_seq = list(range(35))
for X, Y in seq_data_iter_random(my_seq, batch_size=2, num_steps=5):
print('X: ', X, '\nY: ', Y) # Y是X长度的后一个,X里面的两个不一定是连续的
# 保证两个相邻的小批量中的子序列在原始序列上也是相邻的
def seq_data_iter_sequential(corpus, batch_size, num_steps):
"""使用顺序分区生成一个小批量子序列"""
offset = random.randint(0, num_steps)
num_tokens = ((len(corpus) - offset - 1) // batch_size) * batch_size
Xs = torch.tensor(corpus[offset:offset + num_tokens])
Ys = torch.tensor(corpus[offset + 1:offset + 1 + num_tokens])
Xs, Ys = Xs.reshape(batch_size, -1), Ys.reshape(batch_size, -1)
num_batches = Xs.shape[1] // num_steps
for i in range(0, num_steps * num_batches, num_steps):
X = Xs[:, i:i + num_steps]
Y = Ys[:, i:i + num_steps]
yield X, Y
# 读取每个小批量的子序列的特征X和标签Y
for X, Y in seq_data_iter_sequential(my_seq, batch_size=2, num_steps=5):
print('X: ', X, '\nY: ', Y) # 第二个mini-batch[9-13]是接着第一个mini-batch[3-7]后面