【人工智能学习】【五】语言模型

语言模型

在上篇【人工智能学习】【四】文本预处理中,我们最后把一行文本信息通过分词,创建字典,转换成了一行向量表示。本篇要介绍的是语言模型,一言以蔽之就是输入一行文本,预测下一个文本信息。是不是和输入法的提示很像?
在神经网络体系之前,人们尝试过各种方式来对文本进行预测。基于概率模型的隐含马尔可夫模型是以前较为常用的。这在吴军的数学之美里有提到。此外文本数据的输入具有顺序性的特点。为了解决这个问题有了后来的RNN,LSTM,Bi-LSTM等越来越先进的模型,这里先不展开。

隐含马尔可夫模型

在这里插入图片描述
隐含马尔可夫模型的含义是,文本的下一个单词和已经出现的单词应该是有关系的,且和前n个词有关系。所以要预测第 n n n个词是什么,需要知道前 n − 1 n-1 n1个词都是什么,然后查找前 n − 1 n-1 n1个词所记录的下一个词的概率。当然不可能取到 n n n,据说 n = 4 n=4 n=4就已经使模型足够大了。你的数据字典中至少包括 n + n 2 + n 3 + n 4 n+n^2+n^3+n^4 n+n2+n3+n4个词的组合数据。(这完全是暴力的排列组合问题)
来看 n n n阶马尔可夫链(Markov chain of order n),也叫 n n n元语法(n-grams)。假设 w 1 , w 2 , w 3 , w 4 . . . w n w_1,w_2,w_3,w_4...w_n w1,w2,w3,w4...wn是依次出现的,那么
P ( w 1 , w 2 , w 3 , . . . w n ) = ∏ i = 1 n P ( w n ∣ w 1 , w 2 , w 3 , . . . w n − 1 ) P(w_1,w_2,w_3,...w_n)=\prod_{i=1}^nP(w_n|w_1,w_2,w_3,...w_{n-1}) P(w1,w2,w3,...wn)=i=1nP(wnw1,w2,w3,...wn1)

举个例子,当 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 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_2)P(w_4|w_3) P(w1,w2,w3,w4)=P(w1)P(w2w1)P(w3w2)P(w4w3)
中文解释:首先要计算的(概率)这个序列是 w 1 , w 2 , w 3 , w 4 w_1,w_2,w_3,w_4 w1,w2,w3,w4,他是有顺序的。
w 1 w_1 w1出现的概率为 P ( w 1 ) P(w_1) P(w1),由于 n = 2 n=2 n=2,说明 w 2 w_2 w2出现的概率,和 w 1 w_1 w1有关。(头晕不,明明 n = 2 n=2 n=2,却只和前1个有关。这个无关紧要,就是取以 w 2 w_2 w2自己为参照, w 2 w_2 w2占了1个位置)。所以 w 2 w_2 w2出现的概率不应该是 P ( w 2 ) P(w_2) P(w2),而是条件概率,当 w 1 w_1 w1出现时,再出现 w 2 w_2 w2的概率 P ( w 2 ∣ w 1 ) P(w_2|w_1) P(w2w1)。其他的也同理可得。(我觉得这个可比推公式简单多了)

上面这个模型有两个缺陷

  1. 模型占内存太大,如果n取得小又没什么价值了
  2. 矩阵过于稀疏。因为暴力排列组合,总有没有价值的句子组合出现,他们出现的概率几乎为0

ps.有没有大牛用hidden markov弄个预测彩票的,反正大家买彩票也是看看前几期都出了啥。

数据读取

读文本数据

with open('/home/words.txt') as f:
    corpus_chars = f.read()
# 预处理一下把换行符都变成空格
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')

建立索引

idx_to_char = list(set(corpus_chars)) # set集合去重,得到一个list,该字符的索引就是它在list的位置,此时idx_to_char中的字符顺序和corpus_chars不一样了。set进行了排序。
char_to_idx = {char: i for i, char in enumerate(idx_to_char)} # 字符到索引的映射
print(idx_to_char)
print(char_to_idx)

# 然后看一下原来的句子和对应的索引
corpus_indices = [char_to_idx[char] for char in corpus_chars]  # 将每个字符转化为索引,得到一个索引的序列
print('chars:', ''.join([idx_to_char[idx] for idx in sample]))
print('indices:', corpus_indices)

idx_to_char
[‘逗’, ‘血’, ‘些’, ‘好’, ‘碑’, ‘经’, ‘回’, ‘封’, ‘银’, ‘?’, ‘补’, ‘鸠’, ‘联’, ‘古’, ‘爬’, ‘怯’, ‘谢’, ‘跑’, ‘作’, ‘阻’, ‘意’, ‘汉’, ‘抢’, ‘间’, ‘养’, ‘于’, ‘吸’, ‘而’, ‘篇’, ‘方’, ‘丹’, ‘躲’,
char_to_idx
{‘逗’: 0, ‘血’: 1, ‘些’: 2, ‘好’: 3, ‘碑’: 4, ‘经’: 5, ‘回’: 6, ‘封’: 7, ‘银’: 8, ‘?’: 9, ‘补’: 10, ‘鸠’: 11, ‘联’: 12, ‘古’: 13, ‘爬’: 14, ‘怯’: 15,
chars: 我想要带你去浪漫的土耳其
indices: [361, 139, 266, 61, 270, 75, 187, 361, 139, 913, 567, 588]

时序数据采样

首先解释什么是时序数据,时序数据是说数据的出现时间是有顺序的。“我想要带你去浪漫的土耳其”这句话,‘想’是出现在‘我’后面的,‘土耳其’是出现在‘浪漫的’后面的,不然就不是这句话了。(这仿佛是句废话)
这里引出一个概念:时间步数。举个例子和好理解,当时间步数=5时,输入的字符就是5个,且是一个一个输入的,即‘我’、‘想’、‘要’、‘带’、‘你’。因为这是训练集嘛,训练集就是提前已经知道答案了。这时候输出的就是是‘去’,也可是’想’、‘要’、‘带’、‘你’、‘去’。这就源自于你的输出是多对多还是多对一的。这个在后面要介绍的RNN中会有样例。总之要输出什么,是看你的模型要做什么。
我们以输出’想’、‘要’、‘带’、‘你’、‘去’为例(多对多)。这时候上面的歌词会变成这样的训练集:
‘我’、‘想’、‘要’、‘带’、‘你’——>‘想’、‘要’、‘带’、‘你’、‘去’
‘想’、‘要’、‘带’、‘你’、‘去’——>‘要’、‘带’、‘你’、‘去’、‘浪’
‘要’、‘带’、‘你’、‘去’、‘浪’——>‘带’、‘你’、‘去’、‘浪’、‘漫’
此处省略…
‘浪’、‘漫’、‘的’、‘土’、‘耳’——>‘漫’、‘的’、‘土’、‘耳’、‘其’
可见这些样本有大量重合的序列,所以要用一定的采样方式来提高训练效率。

随机采样

特点:

  1. 训练数据中的每个字符最多可以出现在一个样本中
  2. 每个小批量包含的样本数是batch_size,每个样本的长度为num_steps
  3. 在一个样本中,前后字符是连续的

实现步骤

  1. 取小于时间步数的值,对序列进行分段。比如取corpus_indices=30,时间步数为num_steps=6,可通过如下方式计算。最后算出来为4。
num_examples = (len(corpus_indices) - 1) // num_steps  # 下取整,得到不重叠情况下的样本个数
print(num_examples )

4

  1. 计算每个样本的第一个字符在corpus_indices中的下标。就是分的这4段,每段从哪个位置开始。
example_indices = [i * num_steps for i in range(num_examples)]  
print(example_indices )

[0, 6, 12, 18]

  1. 体现随机性的操作来了
random.shuffle(example_indices)
  1. 取数据

def _data(i):
        # 返回从i开始的长为num_steps的序列
        return corpus_indices[i: i + num_steps]
for i in range(0, num_examples, batch_size):
        # 每次选出batch_size个随机样本
		# example_indices = [0, 6, 12, 18]
		# batch_size = 2
		# 所以实际取的值是example_indices[0,2] 
		# 集合操作左闭右开,实际取的是index=0和1的数,也就是batch_indices =[0, 6]
        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), torch.tensor(Y)

这个函数整体长这样

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]
    for i in range(0, num_examples, batch_size):
        # 每次选出batch_size个随机样本
        print(example_indices)
        print(i,batch_size)
        batch_indices = example_indices[i: i + batch_size]  # 当前batch的各个样本的首字符的下标
        print(batch_indices)
        X = [_data(j) for j in batch_indices]
        Y = [_data(j + 1) for j in batch_indices]
        yield torch.tensor(X), torch.tensor(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')

相邻采样

特点:前一个小批量数据和后一个小批量数据是连续的

  1. 裁剪数据,要能被batch-size整除。如果序列长度为30,batch-size=2,,30本来就能被2整除,所以不用裁剪。如果batch-size=11,那么只保留前22个字符。
corpus_len = len(corpus_indices) // batch_size * batch_size  # 保留下来的序列的长度
corpus_indices = corpus_indices[: corpus_len]  # 仅保留前corpus_len个字符
  1. 将序列resize成batch-size行,n列的矩阵。
indices = torch.tensor(corpus_indices)
print(indices)
indices = indices.view(batch_size, -1)  # resize成(batch_size, )
print(indices)

tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
resize后:
tensor([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
[15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29]])

  1. 上面已经将原序列分成了两段,接下来要根据num_steps计算每段能取多少次。(因为你不能取的比num_steps还要小吧)
batch_num = (indices.shape[1] - 1) // num_steps
print(batch_num )

2

说明每段可以取2次,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

这个函数整体长这样

def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
    corpus_len = len(corpus_indices) // batch_size * batch_size  # 保留下来的序列的长度
    corpus_indices = corpus_indices[: corpus_len]  # 仅保留前corpus_len个字符
    indices = torch.tensor(corpus_indices)
    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
        
for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=6):
    print('X: ', X, '\nY:', Y, '\n')

总结:我理解的采样就是尽量平均的覆盖到序列的各个位置。相邻采样和RNN网络有关,接下来会介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值