本文主要学习神经网络语言模型,并在文末给出简单的代码demo,便于学习,整体上主要是从表示学习的发展方面展开;

一 .表示学习

数据表示

面对复杂的概念,可以找到一个表达,化繁从简。机器学习中,找到对于原始数据更好的表达,方便后续任务(分类、序列标注、匹配等)

大语言模型预训练的损失函数 语言模型lm_自然语言处理

 特征学习(feature learning),又叫表示学习(representation learning)或者表征学习,一般指的是自动学习有用的数据特征,采用模型自动学习数据的隐式特征,不依赖专家经验,需要较大的数据训练集

特征工程(feature engineering),主要指对于数据的人为处理提取,依靠专家提取显示特征,特征选取的好坏直接决定数据表示质量,影响后续任务

大语言模型预训练的损失函数 语言模型lm_大语言模型预训练的损失函数_02

 one-hot编码

前期的统计学习方法中,较为常见的表示是使用离散的符号表示词信息,如独热表示(one-hot)

大语言模型预训练的损失函数 语言模型lm_数据_03

 (1).简单直接,易于计算

维度灾难

单词之间的关系

词嵌入  (word-embedding)

word-embedding 是词的分布式嵌入表示,使用多维度信息联合表示一个词语

 

大语言模型预训练的损失函数 语言模型lm_词向量_04

大语言模型预训练的损失函数 语言模型lm_大语言模型预训练的损失函数_05

(1).多维度联合表示一个词语,每一个维度反应潜在语义信息

(2).语义相似的词在向量空间上也能表现出来

(3).缓解数据稀疏影响,获得更好的泛化能力

二 .语言模型

主要是用来计算一个句子出现的概率

大语言模型预训练的损失函数 语言模型lm_自然语言处理_06

通俗点说,就是计算一句话 看起来是否像人说的话,比如,“我喜欢读书”、“我读书喜欢”。那么后者看起来就不像是一句话,前者计算的概率值较大。上面的计算公式,依据的是条件概率的计算公式。

 每一项的P(w)的计算使用的是单词的频率计算,后面会讲到

大语言模型预训练的损失函数 语言模型lm_自然语言处理_07

引入马尔可夫假设,给定当前知识情况下,过去(即以前的历史状态)对于预测将来(即当前以后的未来状态)是无关的

1.一元语言模型(n = 1),也叫做unigram

当前的word不依赖任何word,每个word 都是独立

大语言模型预训练的损失函数 语言模型lm_数据_08

2.二元语言模型(n = 2),也叫做bigram

当前的word依赖前一个word

大语言模型预训练的损失函数 语言模型lm_语言模型_09

3.三元语言模型(n = 3),也叫做trigram

当前的word依赖前两个word

大语言模型预训练的损失函数 语言模型lm_自然语言处理_10

总结下:

大语言模型预训练的损失函数 语言模型lm_语言模型_11

总体来说,bigram 和trigram 语言模型使用较多,unigram 使用很少,基本不怎么使用,主要是一元语言模型相对二元和三元语言模型,很难度量不相似句子

例子:

sentence1 = 我想吃苹果,sentence2 = 我想喝苹果

一元语言模型,计算的 sentence1 结果为 P1= P(我) *  P(想) * P(吃) * P(苹) * P(果)

计算的 sentence2 结果为 P2= P(我) *  P(想) * P(喝) * P(苹) * P(果)

由于都是使用频次统计P的概率,所以最终 P1 和 P2 相等,其实大家知道,sentence1和sentence2的区别还挺大的

二元语言模型例子

利用极大似然估计,得到条件概率计算公式如下:

大语言模型预训练的损失函数 语言模型lm_数据_12

大语言模型预训练的损失函数 语言模型lm_自然语言处理_13

二元语言模型和三元语言模型能比一元语言模型更好的get到两个词语之间的关系信息

三.神经网络语言模型(NNLM)

利用前n-1个词汇,预测第n个词汇

大语言模型预训练的损失函数 语言模型lm_数据_14

上图中的最右边的矩阵是通过参数学习获取的

具体的过程是以下几步:

(1). 输入n-1个词语,每一个词语进行one-hot 编码 1xV

(2).每一个词语(1xV) 与矩阵Q相乘得到 1xm 表示

(3).得到所有的词向量 (n-1) x m的矩阵,concat后得到 (n-1)*m x 1的向量x

(4).具体公式如下

大语言模型预训练的损失函数 语言模型lm_大语言模型预训练的损失函数_15

四 代码示例

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()])