一段自然语言文本可以看作是一个离散时间序列,给定一个长度为\(T\)的词的序列\(w_1, w_2, \ldots, w_T\),语言模型的目标就是评估该序列是否合理,即计算该序列的概率:

\[P(w_1, w_2, \ldots, w_T).
\]

1. 语言模型

假设序列\(w_1, w_2, \ldots, w_T\)中的每个词是依次生成的,我们有

例如,一段含有4个词的文本序列的概率

\[P(w_1, w_2, w_3, w_4) = P(w_1) P(w_2 \mid w_1) P(w_3 \mid w_1, w_2) P(w_4 \mid w_1, w_2, w_3).
\]

语言模型的参数就是词的概率以及给定前几个词情况下的条件概率。设训练数据集为一个大型文本语料库,如维基百科的所有条目,词的概率可以通过该词在训练数据集中的相对词频来计算,例如,\(w_1\)的概率可以计算为:

其中\(n(w_1)\)为语料库中以\(w_1\)作为第一个词的文本的数量,\(n\)为语料库中文本的总数量。

类似的,给定\(w_1\)情况下,\(w_2\)的条件概率可以计算为:

其中\(n(w_1, w_2)\)为语料库中以\(w_1\)作为第一个词,\(w_2\)作为第二个词的文本的数量。

2. n元语法

序列长度增加,计算和存储多个词共同出现的概率的复杂度会呈指数级增加。\(n\)元语法通过马尔可夫假设一个词的出现只与前面\(n\)个词相关,即\(n\)阶马尔可夫链(Markov chain of order \(n\)))来简化模型。如果\(n=1\),那么有\(P(w_3 \mid w_1, w_2) = P(w_3 \mid w_2)\)。基于\(n-1\)阶马尔可夫链,我们可以将语言模型改写为

\[P(w_1, w_2, \ldots, w_T) = \prod_{t=1}^T P(w_t \mid w_{t-(n-1)}, \ldots, w_{t-1}) .
\]

以上也叫\(n\)元语法(\(n\)-grams),它是基于\(n - 1\)阶马尔可夫链的概率语言模型。例如,当\(n=2\)时,含有4个词的文本序列的概率就可以改写为:

当\(n\)分别为1、2和3时,我们将其分别称作一元语法(unigram)、二元语法(bigram)和三元语法(trigram)。

例如,长度为4的序列\(w_1, w_2, w_3, w_4\)在一元语法、二元语法和三元语法中的概率分别为

当\(n\)较小时,\(n\)元语法往往并不准确。例如,在一元语法中,由三个词组成的句子“你走先”和“你先走”的概率是一样的。然而,当\(n\)较大时,\(n\)元语法需要计算并存储大量的词频和多词相邻频率。

  • n元语法的缺陷有哪些?

    1. 参数空间过大
    2. 数据稀疏

3. 语言模型数据集

3.1 读取数据集

with open('/home/kesci/input/jaychou_lyrics4703/jaychou_lyrics.txt') as f:
corpus_chars = f.read()
print(len(corpus_chars))
print(corpus_chars[: 40])
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[: 10000]

3.2 建立字符索引

idx_to_char = list(set(corpus_chars)) # 去重,得到索引到字符的映射
char_to_idx = {char: i for i, char in enumerate(idx_to_char)} # 字符到索引的映射
vocab_size = len(char_to_idx)
print(vocab_size) corpus_indices = [char_to_idx[char] for char in corpus_chars] # 将每个字符转化为索引,得到一个索引的序列
sample = corpus_indices[: 20]
print('chars:', ''.join([idx_to_char[idx] for idx in sample]))
print('indices:', sample)

定义函数load_data_jay_lyrics,在后续章节中直接调用。

def load_data_jay_lyrics():
with open('/home/kesci/input/jaychou_lyrics4703/jaychou_lyrics.txt') as f:
corpus_chars = f.read()
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[0:10000]
idx_to_char = list(set(corpus_chars))
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
vocab_size = len(char_to_idx)
corpus_indices = [char_to_idx[char] for char in corpus_chars]
return corpus_indices, char_to_idx, idx_to_char, vocab_size

4. 时序数据的采样

在训练中我们需要每次随机读取小批量样本和标签。与之前章节的实验数据不同的是,时序数据的一个样本通常包含连续的字符。假设时间步数为5,样本序列为5个字符,即“想”“要”“有”“直”“升”。该样本的标签序列为这些字符分别在训练集中的下一个字符,即“要”“有”“直”“升”“机”,即\(X\)=“想要有直升”,\(Y\)=“要有直升机”。

现在我们考虑序列“想要有直升机,想要和你飞到宇宙去”,如果时间步数为5,有以下可能的样本和标签:

  • \(X\):“想要有直升”,\(Y\):“要有直升机”
  • \(X\):“要有直升机”,\(Y\):“有直升机,”
  • \(X\):“有直升机,”,\(Y\):“直升机,想”
  • ...
  • \(X\):“要和你飞到”,\(Y\):“和你飞到宇”
  • \(X\):“和你飞到宇”,\(Y\):“你飞到宇宙”
  • \(X\):“你飞到宇宙”,\(Y\):“飞到宇宙去”

可以看到,如果序列的长度为\(T\),时间步数为\(n\),那么一共有\(T-n\)个合法的样本,但是这些样本有大量的重合,我们通常采用更加高效的采样方式。我们有两种方式对时序数据进行采样,分别是随机采样和相邻采样。

4.1 随机采样

下面的代码每次从数据里随机采样一个小批量。其中批量大小batch_size是每个小批量的样本数,num_steps是每个样本所包含的时间步数。

在随机采样中,每个样本是原始序列上任意截取的一段序列,相邻的两个随机小批量在原始序列上的位置不一定相毗邻。

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)

测试一下这个函数,我们输入从0到29的连续整数作为一个人工序列,设批量大小和时间步数分别为2和6,打印随机采样每次读取的小批量样本的输入X和标签Y

my_seq = list(range(30))
for X, Y in data_iter_random(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')

4.2 相邻采样

在相邻采样中,相邻的两个随机小批量在原始序列上的位置相毗邻。

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

同样的设置下,打印相邻采样每次读取的小批量样本的输入X和标签Y。相邻的两个随机小批量在原始序列上的位置相毗邻。

for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n') my_seq = list(range(11))
for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=2):
print('X: ', X, '\nY:', Y, '\n')

《动手学深度学习》系列笔记 —— 语言模型(n元语法、随机采样、连续采样)的更多相关文章

  1. 对比《动手学深度学习》 PDF代码+《神经网络与深度学习 》PDF

    随着AlphaGo与李世石大战的落幕,人工智能成为话题焦点.AlphaGo背后的工作原理"深度学习"也跳入大众的视野.什么是深度学习,什么是神经网络,为何一段程序在精密的围棋大赛中 ...

  2. 【动手学深度学习】Jupyter notebook中 import mxnet出错

    问题描述 打开d2l-zh目录,使用jupyter notebook打开文件运行,import mxnet 出现无法导入mxnet模块的问题, 但是命令行运行是可以导入mxnet模块的. 原因: 激活 ...

  3. 小白学习之pytorch框架(2)-动手学深度学习(begin-random.shuffle()、torch.index_select()、nn.Module、nn.Sequential())

    在这向大家推荐一本书-花书-动手学深度学习pytorch版,原书用的深度学习框架是MXNet,这个框架经过Gluon重新再封装,使用风格非常接近pytorch,但是由于pytorch越来越火,个人又比 ...

  4. 《动手学深度学习》系列笔记—— 1.2 Softmax回归与分类模型

    目录 softmax的基本概念 交叉熵损失函数 模型训练和预测 获取Fashion-MNIST训练集和读取数据 get dataset softmax从零开始的实现 获取训练集数据和测试集数据 模型参 ...

  5. 动手学深度学习14- pytorch Dropout 实现与原理

    方法 从零开始实现 定义模型参数 网络 评估函数 优化方法 定义损失函数 数据提取与训练评估 pytorch简洁实现 小结 针对深度学习中的过拟合问题,通常使用丢弃法(dropout),丢弃法有很多的 ...

  6. 动手学深度学习9-多层感知机pytorch

    多层感知机 隐藏层 激活函数 小结 多层感知机 之前已经介绍过了线性回归和softmax回归在内的单层神经网络,然后深度学习主要学习多层模型,后续将以多层感知机(multilayer percetro ...

  7. 动手学深度学习6-认识Fashion_MNIST图像数据集

    获取数据集 读取小批量样本 小结 本节将使用torchvision包,它是服务于pytorch深度学习框架的,主要用来构建计算机视觉模型. torchvision主要由以下几个部分构成: torchv ...

  8. 动手学深度学习1- pytorch初学

    pytorch 初学 Tensors 创建空的tensor 创建随机的一个随机数矩阵 创建0元素的矩阵 直接从已经数据创建tensor 创建新的矩阵 计算操作 加法操作 转化形状 tensor 与nu ...

  9. mxnet 动手学深度学习

    http://zh.gluon.ai/chapter_crashcourse/introduction.html 强化学习(Reinforcement Learning) 如果你真的有兴趣用机器学习开 ...

随机推荐

  1. input输入文字的时候背景会变色,如何去掉呢?

    默认,如图: 当input框输入文字的时候背景会变色,如图: 有两种方法: 1.在form标签里家这个属性就行: autocomplete="off"

  2. hadoop集群的各部分一般都会使用到多个端口,有些是daemon之间进行交互之用,有些是用于RPC访问以及HTTP访问。而随着hadoop周边组件的增多,完全记不住哪个端口对应哪个应用,特收集记录如此,以便查询。这里包含我们使用到的组件:HDFS, YARN, Hbase, Hive, ZooKeeper:

    组件 节点 默认端口 配置 用途说明 HDFS DataNode 50010 dfs.datanode.address datanode服务端口,用于数据传输 HDFS DataNode 50075 ...

  3. 3_05_MSSQL课程_Ado.Net_SQLDataAdapter

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Configurat ...

  4. 【PAT甲级】1027 Colors in Mars (20 分)

    题意: 输入三个范围为0~168的整数,将它们从十三进制转化为十进制然后前缀#输出. AAAAAccepted code: #define HAVE_STRUCT_TIMESPEC #include& ...

  5. flex布局(非常重要)

    首先明确一点是, flex 是 flex-grow.flex-shrink.flex-basis的缩写.故其取值可以考虑以下情况: flex 的默认值是以上三个属性值的组合.假设以上三个属性同样取默认 ...

  6. LCS(最长公共子序列)

    这个问题很有意思,在生物应用中,经常需要比较两个(或多个)不同生物体的DNA片段.例如,某种生物的DNA可能为S1 = ACCGGTCGAGTGCGCGGAAGCCGGCCGAA,S2 = GTCGT ...

  7. 定位(left 、right 、top 、 bottom)、padding、margin 值设为百分比值时

    定位(left .right .top . bottom): top 为例 right 为例 padding.margin : 当padding.margin 值设为百分比值时,其百分比会相对于父元素 ...

  8. NXP TJA1040, TJA1042, TJA1050 TJA1051, TJA1057, TJA1044, TJA1055区别

    历史关系 PCA82C250和PCA82C251是属于NXP第一代 CAN PHY(CAN物理层收发器): TJA1050, TJA1040和TJA1041是属于NXP第二代CAN PHY: TJA1 ...

  9. Numpy 为运算

    Numpy “bitwise_” 开头的函数是位运算函数: Numpy 位运算包括以下几个函数: 函数 描述  bitwise_and  对数组元素执行位与操作  bitwise_or 对数组元素执行 ...

  10. JavaScript引用类型与对象

    1.引用类型 引用类型的值(对象)是引用类型的一个实例.引用类型有时候也被称为对象定义,因为它们描述的是一类对象所具有的属性和方法. 对象是某个特定引用类型的实例.新对象是使用new操作符后跟一个构造 ...