本文主要学习神经网络语言模型,并在文末给出简单的代码demo,便于学习,整体上主要是从表示学习的发展方面展开;
一 .表示学习
数据表示
面对复杂的概念,可以找到一个表达,化繁从简。机器学习中,找到对于原始数据更好的表达,方便后续任务(分类、序列标注、匹配等)
特征学习(feature learning),又叫表示学习(representation learning)或者表征学习,一般指的是自动学习有用的数据特征,采用模型自动学习数据的隐式特征,不依赖专家经验,需要较大的数据训练集
特征工程(feature engineering),主要指对于数据的人为处理提取,依靠专家提取显示特征,特征选取的好坏直接决定数据表示质量,影响后续任务
one-hot编码
前期的统计学习方法中,较为常见的表示是使用离散的符号表示词信息,如独热表示(one-hot)
(1).简单直接,易于计算
(2).矩阵每一个维度的长度等于字典的长度,只有一个1,其他为0,非常稀疏,容易造成维度灾难
(3).只能表示词语本身 ,无法体现单词语义信息,以及单词之间的关系
词嵌入 (word-embedding)
word-embedding 是词的分布式嵌入表示,使用多维度信息联合表示一个词语
(1).多维度联合表示一个词语,每一个维度反应潜在语义信息
(2).语义相似的词在向量空间上也能表现出来
(3).缓解数据稀疏影响,获得更好的泛化能力
二 .语言模型
主要是用来计算一个句子出现的概率
通俗点说,就是计算一句话 看起来是否像人说的话,比如,“我喜欢读书”、“我读书喜欢”。那么后者看起来就不像是一句话,前者计算的概率值较大。上面的计算公式,依据的是条件概率的计算公式。
每一项的P(w)的计算使用的是单词的频率计算,后面会讲到
针对以上问题,引入马尔可夫假设,给定当前知识情况下,过去(即以前的历史状态)对于预测将来(即当前以后的未来状态)是无关的
1.一元语言模型(n = 1),也叫做unigram
当前的word不依赖任何word,每个word 都是独立
2.二元语言模型(n = 2),也叫做bigram
当前的word依赖前一个word
3.三元语言模型(n = 3),也叫做trigram
当前的word依赖前两个word
总结下:
总体来说,bigram 和trigram 语言模型使用较多,unigram 使用很少,基本不怎么使用,主要是一元语言模型相对二元和三元语言模型,很难度量不相似句子
例子:
sentence1 = 我想吃苹果,sentence2 = 我想喝苹果
一元语言模型,计算的 sentence1 结果为 P1= P(我) * P(想) * P(吃) * P(苹) * P(果)
计算的 sentence2 结果为 P2= P(我) * P(想) * P(喝) * P(苹) * P(果)
由于都是使用频次统计P的概率,所以最终 P1 和 P2 相等,其实大家知道,sentence1和sentence2的区别还挺大的
二元语言模型例子:
利用极大似然估计,得到条件概率计算公式如下:
二元语言模型和三元语言模型能比一元语言模型更好的get到两个词语之间的关系信息
三.神经网络语言模型(NNLM)
利用前n-1个词汇,预测第n个词汇
上图中的最右边的矩阵是通过参数学习获取的
具体的过程是以下几步:
(1). 输入n-1个词语,每一个词语进行one-hot 编码 1xV
(2).每一个词语(1xV) 与矩阵Q相乘得到 1xm 表示
(3).得到所有的词向量 (n-1) x m的矩阵,concat后得到 (n-1)*m x 1的向量x
(4).具体公式如下
四 代码示例
import torch
import torch.nn as nn
import torch.optim as optim
import pdb
def make_batch():
input_batch = []
target_batch = []
for sen in sentences:
word = sen.split() # space tokenizer
input = [word_dict[n] for n in word[:-1]] # create (1~n-1) as input
target = word_dict[word[-1]] # create (n) as target, We usually call this 'casual language model'
input_batch.append(input)
target_batch.append(target)
return input_batch, target_batch
# Model
class NNLM(nn.Module):
def __init__(self):
super(NNLM, self).__init__()
self.C = nn.Embedding(n_class, m) ### 矩阵Q (V x m) V 表示word的字典大小, m 表示词向量的维度
self.H = nn.Linear(n_step * m, n_hidden, bias=False) ###
self.d = nn.Parameter(torch.ones(n_hidden))
self.U = nn.Linear(n_hidden, n_class, bias=False)
self.W = nn.Linear(n_step * m, n_class, bias=False)
self.b = nn.Parameter(torch.ones(n_class))
def forward(self, X):
X = self.C(X) # X : [batch_size, n_step, m]
X = X.view(-1, n_step * m) # [batch_size, n_step * m]
tanh = torch.tanh(self.d + self.H(X)) # [batch_size, n_hidden]
output = self.b + self.W(X) + self.U(tanh) # [batch_size, n_class]
return output
if __name__ == '__main__':
n_step = 2 # number of steps, n-1 in paper,根据前两个单词预测第三个
n_hidden = 2 # number of hidden size, h in paper,隐层个数
m = 2 # embedding size, m in paper ,词向量的维度 m =2
sentences = ["i like dog", "i love coffee", "i hate milk"] ###训练数据
word_list = " ".join(sentences).split() ### 按照空格分词,统计 sentences的分词的个数
word_list = list(set(word_list)) ### 去重 统计词典个数
word_dict = {w: i for i, w in enumerate(word_list)} ### {word : index ,词典}
number_dict = {i: w for i, w in enumerate(word_list)} ### {index : word ,词典}
n_class = len(word_dict) # number of Vocabulary 词典的个数,也是softmax 最终分类的个数
# pdb.set_trace()
model = NNLM()
### 损失函数定义
criterion = nn.CrossEntropyLoss() ### 交叉熵损失函数
### 采用 Adam 优化算法 学习率定义为 0.001
optimizer = optim.Adam(model.parameters(), lr=0.001)
input_batch, target_batch = make_batch() ###构建输入数据和 target label
input_batch = torch.LongTensor(input_batch) ### 模型输入 tensor 形式
target_batch = torch.LongTensor(target_batch)
# Training 迭代 5000次
for epoch in range(5000):
optimizer.zero_grad() ###梯度归零
output = model(input_batch)
# output : [batch_size, n_class], target_batch : [batch_size]
loss = criterion(output, target_batch)
if (epoch + 1) % 1000 == 0:
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
loss.backward() ### 反向传播计算 每个参数的梯度值
optimizer.step() ### 每一个参数的梯度值更新
# Predict
predict = model(input_batch).data.max(1, keepdim=True)[1]
# Test
print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])
Reference: