动手学深度学习之语言模型与数据集

语言模型

所谓语言模型,当给定一个长度为 T T T的词的序列 w 1 , w 2 , . . . , w T w_1,w_2,...,w_T w1,w2,...,wT,语言模型的目标就是评估该序列是否合理,即计算序列的概率: P ( w 1 , w 2 , . . . , w T ) P(w_1,w_2,...,w_T) P(w1,w2,...,wT) 概率越大,合理性越高。本节介绍基于统计的语言模型,主要是 n n n元语法( n n n-gram)。

假设序列 w 1 , w 2 , . . . , w T w_1,w_2,...,w_T w1,w2,...,wT中每个词是依次生成的,那么序列生成的概率为
P ( w 1 , w 2 , . . . , w T ) = ∏ t = 1 T P ( w t ∣ w 1 , w 2 , . . . , w t − 1 ) = ∏ t = 1 T P ( w 1 ) P ( w 2 ∣ w 1 ) . . . P ( w T ∣ w 1 , w 2 , . . . , w T − 1 ) \begin{aligned} P(w_1,w_2,...,w_T)&=\prod_{t=1}^T P(w_t\mid w_1,w_2,...,w_{t-1})\\ &=\prod_{t=1}^T P(w_1)P(w_2\mid w_1)...P(w_T\mid w_1,w_2,...,w_{T-1}) \end{aligned} P(w1,w2,...,wT)=t=1TP(wtw1,w2,...,wt1)=t=1TP(w1)P(w2w1)...P(wTw1,w2,...,wT1)例如,一段含有4个词的文本序列的概率
P ( w 1 , w 2 , w 3 , w 4 ) = P ( w 1 ) P ( w 2 ∣ w 1 ) P ( w 3 ∣ w 1 , w 2 ) P ( w 4 ∣ w 1 , w 2 , w 3 ) 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) P(w1,w2,w3,w4)=P(w1)P(w2w1)P(w3w1,w2)P(w4w1,w2,w3)语言模型的参数就是词的概率以及给定前几个词情况下的条件概率。设训练数据集为一个大型文本语料库,如维基百科的所有条目,词的概率可以通过该词在训练数据集中的相对词频来计算,例如, w 1 w_1 w1的概率可以计算为: P ^ ( w 1 ) = n ( w 1 ) n \hat{P}(w_1)=\dfrac{n(w_1)}{n} P^(w1)=nn(w1)其中 n ( w 1 ) n(w_1) n(w1)为语料库中以作为第一个词的文本的数量, n n n为语料库中文本的总数量。

类似的,给定 w 1 w_1 w1情况下, w 2 w_2 w2的条件概率可以计算为: P ^ ( w 2 ∣ w 1 ) = n ( w 1 , w 2 ) n ( w 1 ) \hat{P}(w_2\mid w_1)=\dfrac{n(w_1,w_2)}{n(w_1)} P^(w2w1)=n(w1)n(w1,w2)其中 n ( w 1 , w 2 ) n(w_1,w_2) n(w1,w2)为语料库中以作为第一个词,作为第二个词的文本的数量。

n n n元语法

序列长度增加,计算和存储多个词共同出现的概率的复杂度会呈指数级增加。元语法通过马尔可夫假设简化模型,马尔科夫假设是指一个词的出现只与前面 n n n个词相关,即 n n n阶马尔可夫链(Markov chain of order),如果 n = 1 n=1 n=1,那么有 P ( w 3 ∣ w 1 , w 2 ) = P ( w 3 ∣ w 2 ) P(w_3\mid w_1,w_2)=P(w_3\mid w_2) P(w3w1,w2)=P(w3w2)。基于 n − 1 n-1 n1阶马尔可夫链,我们可以将语言模型改写为 P ( w 1 , w 2 , . . . , w T ) = ∏ t = 1 T P ( w t ∣ w t − ( n − 1 ) , . . . , w t − 1 ) P(w_1,w_2,...,w_T)=\prod_{t=1}^T P(w_t\mid w_{t-(n-1)},...,w_{t-1}) P(w1,w2,...,wT)=t=1TP(wtwt(n1),...,wt1)以上也叫 n n n元语法( n n n-gram),它是基于 n − 1 n-1 n1阶马尔可夫链的概率语言模型。例如,当 n = 2 n=2 n=2时,含有4个词的文本序列的概率就可以改写为:
P ( w 1 , w 2 , w 3 , w 4 ) = P ( w 1 ) P ( w 2 ∣ w 1 ) P ( w 3 ∣ w 1 , w 2 ) P ( w 4 ∣ w 1 , w 2 , w 3 ) = P ( w 1 ) P ( w 2 ∣ w 1 ) P ( w 3 ∣ w 2 ) P ( w 4 ∣ w 3 ) \begin{aligned} 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)\\ &=P(w_1)P(w_2\mid w_1)P(w_3\mid w_2)P(w_4\mid w_3) \end{aligned} P(w1,w2,w3,w4)=P(w1)P(w2w1)P(w3w1,w2)P(w4w1,w2,w3)=P(w1)P(w2w1)P(w3w2)P(w4w3) n n n分别为1、2和3时,我们将其分别称作一元语法(unigram)、二元语法(bigram)和三元语法(trigram)。例如,长度为4的序列在一元语法、二元语法和三元语法中的概率分别为
P ( w 1 , w 2 , w 3 , w 4 ) = P ( w 1 ) P ( w 2 ) P ( w 3 ) P ( w 4 ) P ( w 1 , w 2 , w 3 , w 4 ) = P ( w 1 ) P ( w 2 ∣ w 1 ) P ( w 3 ∣ w 2 ) P ( w 4 ∣ w 3 ) P ( w 1 , w 2 , w 3 , w 4 ) = P ( w 1 ) P ( w 2 ∣ w 1 ) P ( w 3 ∣ w 1 , w 2 ) P ( w 4 ∣ w 2 , w 3 ) \begin{aligned} P(w_1,w_2,w_3,w_4)&=P(w_1)P(w_2)P(w_3)P(w_4)\\ P(w_1,w_2,w_3,w_4)&=P(w_1)P(w_2\mid w_1)P(w_3\mid w_2)P(w_4\mid w_3)\\ 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_2,w_3)\\ \end{aligned} P(w1,w2,w3,w4)P(w1,w2,w3,w4)P(w1,w2,w3,w4)=P(w1)P(w2)P(w3)P(w4)=P(w1)P(w2w1)P(w3w2)P(w4w3)=P(w1)P(w2w1)P(w3w1,w2)P(w4w2,w3) n n n较小时, n n n元语法往往并不准确。例如,在一元语法中,由三个词组成的句子“你走先”和“你先走”的概率是一样的。然而, n n n当较大时, n n n元语法需要计算并存储大量的词频和多词相邻频率。故而 n n n元语法存在以下缺陷:

  1. 参数空间过大; 例设 n = 3 n=3 n=3,字典大小为 V V V,那么需要维护的参数数量为 V + V 2 + V 3 V+V^{2}+V^{3} V+V2+V3
  2. 数据稀疏;齐夫定律说明大部分词的词频很小,甚至不会在语料库里出现,因此对这些词概率的估计都不准确,而且频率较高的词通常是一些连接词,这些词会有大量的组合,但这些组合根本不会出现,因此都是0,从而使得数据稀疏。齐夫定律在自然语言的语料库里,一个单词出现的频率与它在频率表里的排名成反比

读取语言数据集

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

时序数据的采样

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

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

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

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

'''
随机采样中,每个样本是原始序列上任意截取的一段序列,相邻的两个随机小批量在原始序列上的不一定相毗邻。
'''
import torch
import random
# 批量大小batch_size是每个小批量的样本数,num_steps是每个样本所包含的时间步数
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)


'''
在相邻采样中,相邻的两个随机小批量在原始序列上的位置相毗邻。
'''
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

'''
测试函数,输入从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')
'''
同样的设置下,打印相邻采样每次读取的小批量样本的输入X和标签Y。
'''
for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=6):
    print('X: ', X, '\nY:', Y, '\n')
# ---------------------随机采样---------------------
X:  tensor([[18, 19, 20, 21, 22, 23],
        [ 6,  7,  8,  9, 10, 11]], device='cuda:0') 
Y: tensor([[19, 20, 21, 22, 23, 24],
        [ 7,  8,  9, 10, 11, 12]], device='cuda:0')

X:  tensor([[12, 13, 14, 15, 16, 17],
        [ 0,  1,  2,  3,  4,  5]], device='cuda:0')
Y: tensor([[13, 14, 15, 16, 17, 18],
        [ 1,  2,  3,  4,  5,  6]], device='cuda:0')
# ---------------------相邻采样---------------------
X:  tensor([[ 0,  1,  2,  3,  4,  5],
        [15, 16, 17, 18, 19, 20]], device='cuda:0') 
Y: tensor([[ 1,  2,  3,  4,  5,  6],
        [16, 17, 18, 19, 20, 21]], device='cuda:0')

X:  tensor([[ 6,  7,  8,  9, 10, 11],
        [21, 22, 23, 24, 25, 26]], device='cuda:0')
Y: tensor([[ 7,  8,  9, 10, 11, 12],
        [22, 23, 24, 25, 26, 27]], device='cuda:0')

你可能感兴趣的:(python,机器学习,深度学习)