NLP--语言模型

5/15
改正困惑度的计算,新记录古德图灵和interpolation平滑

引入

语言模型(language model)在许多NLP任务里都有应用,比如分词,词性标注,拼写纠错…等等,所以一个好的语言模型是十分重要的。
首先,先来看一下设么是语言模型。
语言模型有一个输入一个输出,输入是一个句子,输出是这个句子的概率,即
P ( s e n t e n c e ) P(sentence) P(sentence)
比如,"我喜欢吃苹果"这句话,对于语言模型P就有
P ( 我 喜 欢 吃 苹 果 ) P(我喜欢吃苹果) P()

n-gram

如何构建出语言模型呢?首先的一种思路是直接统计每个句子的出现次数
比如给出语料库

我喜欢吃苹果
多么美好的一天
我喜欢吃苹果
我喜欢吃梨

我们就可以用统计的方法统计指定句子的出现概率
P ( s e n t e n c e ) = c o u n t ( s e n t e n c e ) N P(sentence)=\frac{count(sentence)}{N} P(sentence)=Ncount(sentence)
其中count(x)表示x的数目,N表示句子的总个数。
那么就有 P ( 多 么 美 好 的 一 天 ) = c o u n t ( 多 么 美 好 的 一 天 ) 4 = 1 4 P(多么美好的一天)=\frac{count(多么美好的一天)}{4}=\frac{1}{4} P()=4count()=41
但这么做显然比较蠢,因为语料库再大也无法收集到所有的句子,所以经常会出现概率为0的情况。

所以我们需要从更加细的角度出发来计算,我们可以从词甚至字的角度来进行计算,下面默认都以词为基元来计算。
假设 s e n t e n c e = ( w 1 , w 2 , . . . , w n ) sentence=(w_1, w_2, ...,w_n) sentence=(w1,w2,...,wn)其中w表示词,那么就有
P ( s e n t e n c e ) = P ( w 1 , w 2 , . . . , w n ) P(sentence) = P(w_1, w_2, ...,w_n) P(sentence)=P(w1,w2,...,wn)
根据条件概率就有
P ( s e n t e n c e ) = P ( w 1 , w 2 , . . . , w n ) = ∏ i = 1 n P ( w i ∣ w 1 , . . , w i − 1 ) P(sentence) = P(w_1, w_2, ...,w_n) = \prod_{i = 1}^nP(w_i|w_1,..,w_{i - 1}) P(sentence)=P(w1,w2,...,wn)=i=1nP(wiw1,..,wi1)
这样计算起来似乎更加的合理一点,但是仍然存在问题,比如当句子很长时 w 1 , . . , w i − 1 w_1,..,w_{i - 1} w1,..,wi1还是几乎不会在语料库出现,所以我们要进行进一步的改进。

马尔科夫假设说,如果在一连串事件中,每个事件的发生只依赖于前一个事件,那么这一连串的事件就构成了一个马尔科夫链。
根据马尔科夫假设,我们可以把我们的模型进行修改。
P ( s e n t e n c e ) = ∏ i = 1 n P ( w i ∣ w 1 , . . , w i − 1 ) = ∏ i = 1 n P ( w i ∣ w i − 1 ) P(sentence) = \prod_{i = 1}^nP(w_i|w_1,..,w_i - 1) = \prod_{i = 1}^nP(w_i|w_{i- 1}) P(sentence)=i=1nP(wiw1,..,wi1)=i=1nP(wiwi1)
这样我们的模型就不会那么容易计算起来找不到对应的语料了,因为从语料库里找特定两个连续出现的词还是比找一个特定的句子出线的概率大的。

我们把符合马尔科夫假设的语法模型叫做二元语言模型,即binary-gram简称bi-gram
接下来我们看一下二元语言模型如何训练,我们通过统计语料库里的词语,使用极大似然估计来训练二元语法模型
P ( s e n t e n c e ) = ∏ i = 1 n P ( w i ∣ w i − 1 ) = ∏ i = 1 n c o u n t ( w i − 1 , w i ) c o u n t ( w i ) P(sentence) = \prod_{i = 1}^nP(w_i|w_{i- 1}) = \prod_{i = 1}^n \frac{count(w_{i -1}, w_i)}{count(w_i)} P(sentence)=i=1nP(wiwi1)=i=1ncount(wi)count(wi1,wi)
其中我们给每个句子都加上个标志,即BOS
比如(我 喜欢 吃 苹果 。)->(BOS 我 喜欢 吃 苹果 。)
他代表句子的开始,添加它是为了不让 w 0 w_0 w0没有对应的值。

我们用代码来实现一下,首先准备一个语料库

corpus = ['我 喜欢 吃 苹果 。',
          '我 喜欢 吃 香蕉 。',
          '香蕉 有助于 消化 。',
          '多么 美好 的 一 天 。',
          '你 喜欢 出去 玩 么 ?']

然后我们定义一个二元语言模型的类

class bi_gram:
    def __init__(self):
        pass

然后就是train方法

class bi_gram:
    def __init__(self):
        pass

    def train(self, corpus):
        corpus = [['BOS'] + sentence.split() for sentence in corpus]
        self.bigram, self.unigram = {}, {}
        for sentence in corpus:
            for i in range(len(sentence)):
                if not sentence[i] in self.unigram.keys():
                    self.unigram[sentence[i]] = 1
                else:
                    self.unigram[sentence[i]] += 1
                if not i:
                    continue
                t = (sentence[i - 1], sentence[i])
                print(t)
                if not t in self.bigram.keys():
                    self.bigram[t] = 1
                else:
                    self.bigram[t] += 1

接下来是预测方法

class bi_gram:
    def __init__(self):
        pass

    def train(self, corpus):
        corpus = [['BOS'] + sentence.split() for sentence in corpus]
        self.bigram, self.unigram = {}, {}
        for sentence in corpus:
            for i in range(len(sentence)):
                if not sentence[i] in self.unigram.keys():
                    self.unigram[sentence[i]] = 1
                else:
                    self.unigram[sentence[i]] += 1
                if not i:
                    continue
                t = (sentence[i - 1], sentence[i])
                if not t in self.bigram.keys():
                    self.bigram[t] = 1
                else:
                    self.bigram[t] += 1
    
    def get(self, a, b):
        return self.bigram[(a, b)] / self.unigram[b]
    
    def predict(self, sentence):
        sentence = ('BOS ' + sentence).split()
        pro = 1
        for i in range(1, len(sentence)):
            pro *= self.get(sentence[i - 1], sentence[i])
        return pro

我们来测试一下

model = bi_gram()
model.train(corpus)
print(model.predict('你 喜欢 吃 香蕉 。'))
print(model.predict('我 喜欢 吃 香蕉 。'))
"""
0.03333333333333333
0.06666666666666667
"""

可以发现,“你喜欢吃香蕉”这个句子没在语料库中出现过,但是我们依然计算出了它的概率。

我们对二元语言模型进行推广,就得到了n元语言模型,即n-gram所谓的n元语言模型即每个词都与前n - 1个词相关,其中一元语言模型我们叫做unigram,即每个词都只与自身相关,此时语言模型就变成了
P ( s e n t e n c e ) = ∏ i = 1 n P ( w i ) P(sentence) = \prod_{i = 1}^nP(w_i) P(sentence)=i=1nP(wi)

性能评价

知道了语言模型,我们就需要评价模型的好坏,语言模型的好坏可以用交叉熵和困惑度来评价。

交叉熵

交叉熵衡量的是一个模型和真实模型之间的差距,设真实模型是 X − P ( x ) X-P(x) XP(x)我们的模型是 q ( x ) q(x) q(x),那么他们的交叉熵就是
H ( X , q ) = H ( X ) + D ( p ∣ ∣ q ) H(X, q) = H(X) + D(p||q) H(X,q)=H(X)+D(pq)
其中D为KL-Divergence,交叉熵还有另一种形式
H ( X , q ) = H ( X ) + D ( p ∣ ∣ q ) = − ∑ x p ( x ) l o g   q ( x ) H(X, q) = H(X) + D(p||q) = -\sum_{x}p(x)log~q(x) H(X,q)=H(X)+D(pq)=xp(x)log q(x)
对此我们也可以估计我们语言模型的交叉熵,设 L − p ( x ) L-p(x) Lp(x)是真实的语言模型,而我们所训练的得到的是 q ( x ) q(x) q(x),设有句子 x n = w 1 , w 2 , . . . w n x^n=w_1,w_2, ...w_n xn=w1,w2,...wn
那么我们就有
H ( L , q ) = − lim ⁡ n − > ∞ 1 n ∑ x n p ( x n ) l o g   q ( x n ) H(L, q)= -\lim_{n->\infty}\frac{1}{n}\sum_{x^n}p(x^n)log~q(x^n) H(L,q)=n>limn1xnp(xn)log q(xn)
然而,我们无法知道真实的语言模型,所以我们需要对这个式子进行改进,根据布莱曼进军分性定理,和辛钦大数定理,在n足够大时可以得到
H ( L , q ) ≈ − 1 n ∑ x n l o g   q ( x n ) H(L, q)\approx -\frac{1}{n}\sum_{x^n}log~q(x^n) H(L,q)n1xnlog q(xn)
如果一个模型的交叉熵越小,那么说明它和真实模型越接近,说明模型越好。

困惑度

我们在评估模型时也常使用困惑度(perplexity)来评价模型,困惑度的定义是
P P q = 2 H ( L , q ) = 2 − 1 n ∑ x n l o g   q ( x n ) PP_q=2^{H(L, q)} = 2^{-\frac{1}{n}\sum\limits_{x^n}log~q(x^n)} PPq=2H(L,q)=2n1xnlog q(xn)
与交叉熵一样,对于一个语言模型来说,困惑度越小越好。
理解困惑度可以从交叉熵的角度来看。

也可以从语言模型本身来看,语言模型预测的是一个句子的出现的概率,对于我们测试语料中给出的合法的句子,自然是出现概率高的(想想MLE的思想),所以语言模型预测测试预料的概率越高说明他对这些句子越不迷惑,也说明我们语言模型越好,我们困惑度是在语言模型预测的概率上加了符号然做幂,于是就有困惑度越低语言模型越好。

我们用python实现一下,其中单个句子的困惑度在下面平滑中有实现。

def perplexity(model, corpus):
    pp = 0
    for i, sentence in enumerate(corpus):
        pp += model.predict(sentence, True) # 这里我们对模型的predict进行了改进,可以直接计算出单个句子的困惑度。
    
    return pp / len(corpus) # 多个句子取平均

平滑

我们可以注意到,即使是二元语言模型也无法避免出现找不到对应的连续出现的词的情况。对于这种情况我们一方面可以增加语料库,但是也没法穷举所有的词的组合,所以我们还需要另寻一种方法来进行弥补,而平滑就是一种这样的方法。

平滑所采用的方法就是“劫富济贫 ”,即减少那些非零的 P ( w i ∣ w i − 1 ) P(w_i|w_{i - 1}) P(wiwi1)的概率,其中平滑有很多方法,这里只详细说一个。

加法平滑法

加法平滑法的计算公式如下
P ( w i ∣ w i − 1 . . w i − k + 1 ) = δ + c o u n t ( w 1 w 2 . . w i ) δ ∣ V ∣ + c o u n t ( w i ) P(w_i| w_{i - 1}..w_{i - k + 1}) = \frac{\delta + count(w_1w_2..w_{i})}{\delta|V|+count(w_i)} P(wiwi1..wik+1)=δV+count(wi)δ+count(w1w2..wi)
其中 δ \delta δ是一个在0到1之间的参数,使用了加法平滑法,就会使所有的已出现的词段的频率下降一定程度,而未出现的词段的频率提升。
我们用python实现一下
首先修改一下init函数

class bi_gram:
    def __init__(self, delta=0.8):
        self.delta = delta

然后调整一下get和predict

    def get(self, a, b):
        return (self.bigram.get((a, b), 0) + self.delta) / (self.unigram.get(b, 0) + (len(self.unigram)) * self.delta)
    
    def predict(self, sentence, perp=False): # 如果等于True就返回困惑度指数项,而不是概率
        sentence = ('BOS ' + sentence).split()
        pro = 0
        for i in range(1, len(sentence)):
            tmp = self.get(sentence[i - 1], sentence[i])
            if perp:
                tmp = -np.log2(tmp)
            pro += tmp
        pro = pro / (len(sentence) - 1)
        return 2 ** pro

对于多个句子,我们取他们的均值作为困惑度,我们测试一下这个模型的困惑度,首先使用的是msr数据集,训练时用的是msr_training,测试用的是msr_test_gold。

train = processing(train)
test = processing(test)
model = bi_gram()
model.train(train)
print(perplexity(model, test))

我们降低delta,看看困惑度如何变化

train = processing(train)
test = processing(test)
model = bi_gram(delta=1)
model.train(train)

print(perplexity(model, test))
model.delta = 0.01
print(perplexity(model, test))
model.delta = 0.0001
print(perplexity(model, test))
model.delta = 0.000001
print(perplexity(model, test))
model.delta = 0.000000001
print(perplexity(model, test))
"""
7158.226991758361
409.5074302753495
137.38950771122668
124.46948113442818
124.44796658768492
"""

可以看到,delta越小越好,但是当delta太小时困惑度也不会发生改变了,这里一部分是因为我使用的测试语料也是msr的,所以生词比较少导致平滑的效果不明显。

于是我在其中混入了一些PKU的语料,然后再次测试一下各个delta下的困惑度,结果如下,可以发现差别很大,这也说明我们只训练单一的语料会导致语言模型的鲁棒性极差。

train = processing(train)
test = processing(test)
model = bi_gram(delta=1)
model.train(train)

print(perplexity(model, test))
model.delta = 0.01
print(perplexity(model, test))
model.delta = 0.0001
print(perplexity(model, test))
model.delta = 0.000001
print(perplexity(model, test))
model.delta = 0.000000001
print(perplexity(model, test))
"""
7888.699267720896
1767.678913110591
297.1513158742319
1973.4768710204119
168619.7138282547
"""

good-turning 平滑

good-turning的效果十分不错,在很多情况下对未知词的预测效果也能近似其真实值。它的思想是先对所有的词出现的次数进行分类,然后调整每个词的出现的次数最后再归一化。

首先,good-turning的做法是先统计出现次数为r的词的个数记,为 N r N_r Nr,比如有语料库

我 喜欢 吃 冰棍
她 喜欢 看 书
我 喜欢 吃 葡萄
琪露诺 喜欢 冰棍

那么就可以统计出 N 1 = 5 ( 她 , 看 , 书 , 葡 萄 , 琪 露 诺 ) , N 2 = ( 冰 棍 , 我 ) , N 3 = 0 , N 4 = 1 ( 喜 欢 ) N_{1}=5(她,看,书,葡萄,琪露诺),N_2=(冰棍,我),N_3=0,N_4=1(喜欢) N1=5(),N2=(),N3=0,N4=1()
然后,当我们遇到词典里没出现的词时,就意味着这个词第一次出现,所以我们能不能用第一次出现词的概率来代替未知词出现的概率呢?
P ( U N K ) = N 1 N P(UNK)=\frac{N_1}{N} P(UNK)=NN1
其中N为 ∑ r = 1 ∞ r ∗ N r \sum\limits_{r = 1} ^\infty r*N_r r=1rNr,UNK为未知词。按照上面的预料计算的的话就是 P ( U N K ) = 5 14 P(UNK)=\frac{5}{14} P(UNK)=145
那么多出来了个UNK的概率,那么其他词的概率就会减少。对于其他的词,他们的概率可以由 P r = ( r + 1 ) N r + 1 N r ∗ N P_r=\frac{(r + 1) N_{r + 1}}{N_r*N} Pr=NrN(r+1)Nr+1计算得来,这么计算其中的一个原因就是为了满足概率的约束。
但是这个公式计算起来可能会有问题,比如 N 3 = 0 N_3=0 N3=0这就会导致 N 3 , N 2 N_3,N_2 N3,N2没法计算。
此时可以有一个补救的方法,比如我们使用回归算法比如LASSO,Ridge甚至决策树,DNN之类的来对其进行一个预测,这样就可以让我们大概估计出它的值。

interpolation

在前面进行平滑时,我们都把所有的未知词记为UNK,然他们的概率一样,但是这样其实是不对的,因为有一些词可能计算 P ( w i ∣ w i − 1 , w i − 2 ) P(w_i|w_{i - 1},w_{i - 2}) P(wiwi1,wi2)时会出出现找不到 ( w i , w i − 1 , w i − 2 ) (w_i, w_{i - 1}, w_{i - 2}) (wi,wi1,wi2)从而导致概率为0,但是我们找不到 ( w i , w i − 1 , w i − 2 ) (w_i, w_{i - 1}, w_{i - 2}) (wi,wi1,wi2)不意味着我们找不到 ( w i , w i − 1 ) (w_i, w_{i - 1}) (wi,wi1)找不到 ( w i ) (w_i) (wi)那么interpolation的思想就来了。
我们在计算概率时,我们把多个模型进行混合。即,我们计算 P ( w i ∣ w i − 1 , w i − 2 ) P(w_i|w_{i - 1},w_{i - 2}) P(wiwi1,wi2)时可以混合bigram和unigram
P ′ ( w i ∣ w i − 1 , w i − 2 ) = λ 1 P ( w i ∣ w i − 1 , w i − 2 ) + λ 2 P ( w i ∣ w i − 1 ) + λ 3 P ( w i ) P'(w_i|w_{i - 1},w_{i - 2}) = \lambda_1 P(w_i|w_{i - 1},w_{i - 2}) + \lambda_2 P(w_i|w_{i - 1}) + \lambda_3 P(w_i) P(wiwi1,wi2)=λ1P(wiwi1,wi2)+λ2P(wiwi1)+λ3P(wi)
其中 λ 1 + λ 2 + λ 3 = 1 \lambda_1 + \lambda_2 + \lambda_3=1 λ1+λ2+λ3=1这样,我们就可以降低计算出概率为0的词的数目。
对于这三个参数的求法,我们可以以perplexity为优化函数,然后以三个参数为变量进行求最优解,使用梯度下降之类的方法求解。

其它的平滑方法还有很多,这里就不说了,想知道的可以看《统计自然语言处理》这本书

Neural Network Language Model

神经网络模型(Neural Network Language Model),简称NNLM是基于神经网络的一个语言模型,它的好处是可以进行自动的平滑,也能更好的捕捉到前后之间的信息。

其神经网络的结构如图
在这里插入图片描述
首先我们先把输入的X通过一个embedding层
在这里插入图片描述
然后把通过embedding的输出喂给一个全连接层
在这里插入图片描述
然后把全连接层的输出和原本embedding的输出,分别通过一个变化,转换为 v o c a b u l a r y vocabulary vocabulary大小相加,最后输出。

具体的过程如下:
首先是对数据进行处理,对于一个句子 ( w 1 , w 2 , . . . , w n ) (w_1, w_2, ... ,w_n) (w1,w2,...,wn)
我们取其中的所有的 ( w a , w a + 1 , . . . , w a + k − 1 ) (w_a, w_{a + 1}, ..., w_{a + k - 1}) (wa,wa+1,...,wa+k1)
然后用前 k − 1 k - 1 k1个词预测下一个词,即k元语言模型那种形式。
那么我们就得把训练数据处理成
x = ( w a , w a + 1 , . . . , w a + k − 2 ) , y = w a + k − 1 x = (w_a, w_{a + 1}, ..., w_{a + k - 2}), y = w_{a + k -1} x=(wa,wa+1,...,wa+k2)y=wa+k1
加上batch,就有X和y,其形状分别是(batch, k -1), (batch)

然后我们把X转化为one-hot形式再给通过一个输出为embedding_size的embedding层,就得到X的形状为(batch, k - 1, embedding_size)
然后我们把后面的两个维度通过展开的方式合并,就得到了X的形状为(batch, (k -1) * embedding_size)

然后我们让它先通过一个全连接层,并且经过一个tanh激活函数。
h i d = t a n h ( F F 1 ( X ) ) hid = tanh(FF_1(X)) hid=tanh(FF1(X))
FF即一个全连接层,即
h i d = t a n h ( X W F F 1 + b F F 1 ) hid = tanh(XW_{FF_1} + b_{FF_1}) hid=tanh(XWFF1+bFF1)
其中W和b都是FF的参数,是通过训练得来的
然后我们把X再通过一个全连接层,然后不通过激活函数,得到
x ′ = F F 2 ( X ) x' = FF_2(X) x=FF2(X)
最后我们把hid通过一个矩阵进行线性变换(其实就是通过一个没有bias的FF),再和x’相加得到最后输出
o u t p u t = U ⋅ h i d + x ′ output = U\cdot hid + x' output=Uhid+x
其输出的形状是(batch, vocabulary_size),即每个样本对应一个词典大小的向量,我们可以直接对这个向量进行查询得到某个对应的词的预测的概率,不过在得到概率之前还需要进行一下softmax否则得到的可能不是概率。
具体的网络结构长这样,我的模型是与前面两个相关的

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as Data
import Main # 我把上面的perplexity的计算给写到这里了,懒得改名字了
class NNLM(nn.Module):
    def __init__(self, voc_size, embedding_size, hidden_size, word2idx):
        super(NNLM, self).__init__()
        self.embedding_size = embedding_size
        self.word2idx = word2idx
        self.embedding = nn.Embedding(voc_size, embedding_size)
        self.H = nn.Linear(embedding_size * 2, hidden_size)
        self.U = nn.Linear(hidden_size, voc_size, bias=False)
        self.W = nn.Linear(embedding_size * 2, voc_size)
        self.actfun = nn.Tanh()
    
    def forward(self, X):
        X = self.embedding(X)
        X = X.view(-1, self.embedding_size * 2)
        hid = self.actfun(self.H(X))
        X = self.W(X) + self.U(hid)
        return X
    
    def predict(self, sentence, w):
        sentence = ['BOS1', 'BOS2'] + sentence.split()
        res = 1
        with torch.no_grad():
            for i in range(2, len(sentence)):
                x = torch.LongTensor([self.word2idx.get(sentence[i - 2], self.word2idx['UNK']), 
                                    self.word2idx.get(sentence[i - 1], self.word2idx['UNK'])]).to(device)
                y_hat = nn.functional.softmax(self.forward(x), dim=1)[0][self.word2idx.get(sentence[i], self.word2idx['UNK'])].item()
                res *= pow(y_hat, -1/len(sentence))
        return res

然后这边我简单给出一个数据处理的代码

def getContentAndVoc(corpus): # corpus是语料库的路径
    content, voc = None, None
    with open(corpus, encoding='utf-8', mode='r') as rs:
        tmp = rs.read()
        content = [sentence.strip().split() for sentence in tmp.split('\n')]
        voc = set(tmp.split())
        if '\n' in voc:
            voc.remove('\n')
        if ' ' in voc:
            voc.remove(' ')
        voc.update(['BOS2', 'BOS1', 'UNK']) # BOS1和BOS2是两个开始的标记
    voc = sorted(list(voc))
    idx2word = voc
    word2idx = {word: i for i, word in enumerate(voc)}

    with open('./voc.txt', encoding='utf-8', mode='w') as ws:
        ws.write('\n'.join(idx2word))
    return content, word2idx, idx2word, voc

def getDataset(word2idx:dict, content:list):
    data, target = [], []
    for sentence in content:
        sentence = ['BOS1', 'BOS2'] + sentence
        for i in range(2, len(sentence)):
            data.append([word2idx.get(sentence[i - 2], word2idx['UNK']), word2idx.get(sentence[i - 1], word2idx['UNK'])])
            target.append(word2idx.get(sentence[i], word2idx['UNK']))
    return data, target
    
class dataset(torch.utils.data.DataLoader):
    def __init__(self, data, target):
        self.data = data
        self.target = target

    def __getitem__(self, idx):
        return torch.LongTensor(self.data[idx]), torch.tensor(self.target[idx])
    
    def __len__(self):
        return len(self.data)

然后就是训练和测试

content, word2idx, idx2word, voc = getContentAndVoc('./test.txt')
data, target = getDataset(word2idx, content)
print('--------------------data load done-------------------')
model = NNLM(len(voc), 150, 200, word2idx).to(device)
ds = dataset(data, target)
dataloader = torch.utils.data.DataLoader(ds, batch_size=200)
optm = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.2)
loss_fun = torch.nn.functional.cross_entropy

print(len(ds))
t = Main.processing('msr_test_gold.utf8') # 这个函数其实就是加载测试预料,输出为一个列表,其中每一个元素都是一个句子,以空格分隔

for epoch in range(20):
    loss_ = 0

    for i, X in enumerate(dataloader):
        optm.zero_grad()
        x, y = X
        x = x.to(device)
        y = y.to(device)

        y_hat = model(x)
        loss = loss_fun(y_hat, y)

        loss_ += loss.item()
        loss.backward()
        optm.step()
        if i % 60 == 0:
            print(i, 'loss', loss.item())
    print('epoch: {} >> loss >> {}'.format(epoch, loss_))
    print('epoch: {} >> perplexity: {}'.format(epoch, Main.perplexity(model, t)))
    loss_ = 0
""""
145167
0 loss 10.12985897064209
60 loss 8.820554733276367
120 loss 8.701559066772461
180 loss 8.356273651123047
240 loss 7.988142013549805
300 loss 8.282853126525879
360 loss 8.663858413696289
420 loss 7.5130391120910645
480 loss 7.522820949554443
540 loss 7.705265998840332
600 loss 7.512140274047852
660 loss 7.832237720489502
720 loss 8.047532081604004
epoch: 0 >> loss >> 5951.350144863129
epoch: 0 >> perplexity: 655.0402109527666
0 loss 7.413693904876709
60 loss 6.5933661460876465
120 loss 7.059969902038574
180 loss 7.220620632171631
240 loss 6.721376419067383
300 loss 7.2974090576171875
360 loss 7.920412063598633
420 loss 6.803381443023682
480 loss 6.94309139251709
540 loss 7.2869038581848145
600 loss 6.975142955780029
660 loss 7.490360736846924
720 loss 7.585268974304199
epoch: 1 >> loss >> 5189.5808029174805
epoch: 1 >> perplexity: 453.0476346452168
"""

注意这里其实与我们上面写的的二元语法模型没有可比性,因为我为了节约时间,缩小了数据集和测试集,如果相比较他们的perplexity就要使用完整的msr数据。

RNN language model

RNN语言模型则是利用了RNN的记忆功能,从而使我们可以利用一个词前面所有词的信息。
RNN可以记录前面所输入的所有的信息,所以我们依次的输入每个词同时预测下一个词,这样我们某种意义样得到了一个 ∞ − g r a m \infty-gram gram,不过因为RNN的依赖能力有限所以实际得到的并是不无穷的,不过我们也可以改用LSTM这种依赖性更好的模型来代替。
比如我们想计算 P ( w r e c k   a   n i c e   b e a c h ) P(wreck~a~nice~beach) P(wreck a nice beach),我们就可以这么做
在这里插入图片描述
这样就可以得到对应的预测结果,就可以计算出 P ( w r e c k   a   n i c e   b e a c h ) P(wreck~a~nice~beach) P(wreck a nice beach)
某种意义上来说,RNNLM的计算式类似于
P ( s e n t e n c e ) = ∏ i = 1 n P ( w i ∣ w 1 , . . , w i − 1 ) P(sentence) = \prod_{i = 1}^nP(w_i|w_1,..,w_i - 1) P(sentence)=i=1nP(wiw1,..,wi1)
与NNLM相比,主要的大改变就是网络模型发生了改变,其他部分差别不大。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值