About Me

NLP问题特征表达基础 - 语言模型(Language Model)讨论

1. NLP问题简介

0x1:NLP问题都包括哪些内涵

人们对真实世界的感知被成为感知世界,而人们用语言表达出自己的感知视为文本数据。那么反过来,NLP,或者更精确地表达为文本挖掘,则是从文本数据出发,来尽可能复原人们的感知世界,从而表达真实世界的过程。这里面就包括如图中所示的模型和算法,包括:

1)文本层:NLP文本表示; 
(2)文本-感知世界:词汇相关性分析、主题模型、意见情感分析等; 
(3)文本-真实世界:基于文本的预测等;

显而易见,文本表示在文本挖掘中有着绝对核心的地位,是其他所有模型建构的基础。

0x2:为什么要进行文本表示

特征提取的意义在于把复杂的数据,如文本和图像,转化为数字特征,从而在机器学习中使用。

在机器学习项目中,不管是纯NLP问题还是NLP问题和非文本类混合数据的场景,我们都要面临一个问题,如何将样本集中的文本进行特征表征以及通过向量的方式表达出来。

这就要求我们对样本的原始空间进行抽象,将其映射到另一个向量化的特征空间中,笔者在这篇blog中对常用的编码和特征工程方式进行一个梳理总结。

Relevant Link:

https://blog.csdn.net/tiffanyrabbit/article/details/72650606
https://blog.csdn.net/IT_bigstone/article/details/80739807
http://www.cnblogs.com/robert-dlut/p/4371973.html

 

2. 语言模型(Language Model)简介

语言模型本质上是在回答一个问题:出现的语句是否合理(make sense)

0x1:语言模型发展历史

在历史的发展中,语言模型经历了专家语法规则模型(至80年代)统计语言模型(至00年)神经网络语言模型(till now)

1. 专家语法规则模型

专家语法规则模型在计算机初始阶段,随着计算机编程语言的发展,归纳出的针对自然语言的语法规则。但是自然语言本身的多样性,口语化,在时间,空间上的演化,及人本身强大的纠错能力,导致语法规则急剧膨胀,不可持续。

2. 统计语言模型

统计语言模型用简单的方式,加上大量的语料,产生了比较好的效果。统计语言模型通过对句子的概率分布进行建模,从统计来来说,概率高的语句比概率底的语句更为合理。

在实现中,通过给定的上文来预测句子的下一个词, 如果预测的词和下一个词是一致(该词在上文的前提下出现的概率比其它词概率要高),那么上文+该词出现的概率就会比上文+其他词词的概率要更大,上文+该词更为合理。

3. 神经网络语言模型

神经网络语言模型在统计语言模型的基础上,通过网络的叠加和特征的逐层提取,可以表征除了词法外,相似性,语法,语义,语用等多方面的表示。

Relevant Link:

https://zhuanlan.zhihu.com/p/28080127

 

3. 统计语言模型(Statistical Language Model, SLM)

统计语言模型是“单个字母/单词/词序列”的概率分布,假设有一个 m 长度的文本序列,统计语言模型的目的是建立一个能够描述给定文本序列对应的概率分布。

0x1:统计语言模型基本公式

统计语言模型从统计(statistic)的角度预测句子的概率分布,通常对数据量有较大要求。

对于句子w1,w2,...,wn, 计算其序列概率为P(w1,w2,...,wn),根据链式法则可以求得整个句子的概率: 

 

其中,每个词出现的概率通过统计计算得到: 

即第n个词的出现与前面N-1个词相关,整句的概率就是各个词出现概率的乘积

0x2:马尔科夫假设:有限前序字符依赖约束条件的统计语言模型

传统的统计语言模型存在几个问题:

1. 参数空间过大,语言模型由于语言的灵活性参数空间维度极高;举个直观的例子,我们有词表 V=10^5 的语料库,学习长度为 l=10 的序列出现概率,潜在参数空间大小为 100000^10=10^50;
2. 数据极度稀疏,长序列的出现频次较低,越长的序列的越不容易出现,整个参数矩阵上会有很多0值;

为了简化问题,我们引入马尔科夫假设:当前词出现的概率仅依赖前n1个词,在这种假设下,模型就大大减少了需要参与计算的先验参数。

马尔科夫假设的一个具体实现即 n-gram。

n=1时,我们称之为unigram(一元语言模型);

n=2时,我们称之为bigram(二元语言模型);

n=3时,我们称之为trigram(三元语言模型);

1. ngram统计语言模型基本定义

n-gram是最为普遍的统计语言模型。它的基本思想是将文本里面的内容进行大小为N的滑动窗口操作,形成长度为N的短语子序列,对所有短语子序列的出现频度进行统计。

举一个具体的例子:

from sklearn.feature_extraction.text import CountVectorizer

if __name__ == '__main__':
    corpus = [
        'This is the first document.',
        'This is the second second document.',
        'And the third one.',
        'Is this the first document?',
    ]
    bigram_vectorizer = CountVectorizer(ngram_range=(1, 2), token_pattern=r'\b\w+\b', min_df = 1)
    X_2 = bigram_vectorizer.fit_transform(corpus).toarray()
    print X_2
    print bigram_vectorizer.vocabulary_
'''' 
[[0 0 1 1 1 1 1 0 0 0 0 0 1 1 0 0 0 0 1 1 0]
 [0 0 1 0 0 1 1 0 0 2 1 1 1 0 1 0 0 0 1 1 0]
 [1 1 0 0 0 0 0 0 1 0 0 0 1 0 0 1 1 1 0 0 0]
 [0 0 1 1 1 1 0 1 0 0 0 0 1 1 0 0 0 0 1 0 1]]
{u'and': 0, u'the second': 14, u'is': 5, u'this the': 20, u'one': 8, u'and the': 1, u'second': 9, u'first document': 4, u'is the': 6, u'second document': 10, u'the third': 15, u'document': 2, u'the first': 13, u'is this': 7, u'third': 16, u'this': 18, u'second second': 11, u'third one': 17, u'the': 12, u'this is': 19, u'first': 3}

上面代码中,我们对一段语料进行了bingram处理,并将词汇表打印了出来。

我们看第一行的前3个:0 0 1

索引0的0代表词汇表中索引0对应的bingrame词组[start, and]没有出现在了第一个sentence中;
索引1的0代表词汇表中索引1对应的bingrame词组[and the]没有出现在了第一个sentence中;
索引2的1代表词汇表中索引2对应的bingrame词组[document end]没有出现在了第一个sentence中;
# start、end代表边界占位符

另外,可以看到,ngram编码后得到的向量长度取决于词集的长度。

2. ngram对文本的表征能力

gram并不关注词在样本中的具体位置。n-gram保留的词序列只是它自己的context上下文的词组序列。

因此n-gram编码后的vector丢失了原始sentence sequence的长序列特征,它只保存了N阶子序列的短程依赖关系,无法建立长期依赖。

同时ngram对不同的词组之间的先后信息也丢弃了,原始文本中不同词组出现的先后关系在ngram这里是忽略的。

当n过大时仍会被数据的稀疏性严重影响,实际使用中往往仅使用bigram或trigram。

附:谷歌的统计翻译系统罗塞塔率先使用n-gram=4获得当时市场上最好的性能。

3. n-gram中的平滑(smoothing)

从ngram的公式我们可以看到,随着n的提升,我们拥有了更多的前置信息量,可以更加准确地预测下一个词。但这也带来了一个问题,词组概率分布随着n的提升变得更加稀疏了,导致很多预测概率结果为0。

当遇到零概率问题时,我们可以通过平滑来缓解n-gram的稀疏问题。
n-gram需要平滑的根本原因在于数据的稀疏性,而数据的稀疏性则是由自然语言的本质决定的。当这种稀疏性存在的时候,平滑总能用于缓解问题提升性能。 直观上讲,如果你认为你拥有足够多的数据以至于稀疏性不再是一个问题,我们总是可以使用更加复杂、更多参数的模型来提升效果,如提高n的大小。当n提高,模型参数空间的指数级增加,稀疏性再度成为了所面临的问题,这时通过合理的平滑手段可以获得更优的性能。

参考Andrew Gelman的一句名言:样本从来都不是足够大的。如果 N 太大不足以进行足够精确的估计,你需要获得更多的数据。但当 N “足够大”,你可以开始通过划分数据研究更多的问题,例如在民意调查中,当你已经对全国的民意有了较好的估计,你可以开始分性别、地域、年龄进行更多的统计。N 从来都无法做到足够大,因为当它一旦大了,你总是可以开始研究下一个问题从而需要更多的数据。

1)什么时候需要概率平滑处理

在一个有限的数据集中,高频事件通过统计所求得的概率更加可靠,而出现频次越小的事件所得的概率越不可靠,这种现象在高维空间中表现更加突出。

如果将训练集的概率分布直接拿来作为测试集分布,那么训练集中的“未登录词”概率将会是0,这显然与我们的先验认知是不符合的,因为测试集中是有可能出现未登录词的,没见过的不代表不存在。

平滑解决的问题就是:根据训练集数据的频率分布,估计在测试集中“未登录词”的概率分布,从而在测试集上获得一个更加理想的概率分布预估 。也可以理解为在原始的样本驱动的概率分布之上附加了一个先验正则化,通过先验分布来干预侯后验概率分布。

接下来我们对常用的平滑方法进行介绍。

2)拉普拉斯平滑(Laplace Smoothing)

如公式所示,拉普拉斯平滑对所有事件的频次均进行了加1。

拉普拉斯平滑是最为直观、容易理解的平滑方式,对事件发生的频次加一对于高频事件的概率影响基本可以忽略,它也同时解决了未登录词的概率计算问题。
但直接加1的拉普拉斯平滑也是一种较为“糟糕”的平滑方式,简单粗暴的加1有时会对数据分布产生较大的影响,有时却又微不足道。拉普拉斯平滑对未知事件的概率预测进行了平等对待,这显然不合理!

3)加性平滑(Additive Smoothing)

在加性平滑中我们做出以下假设:我们假定每个n-gram事件额外发生过 δ 次;

一般而言0<δ<=1, 当δ=1时等价于拉普拉斯平滑;当δ<1δ<1时,表示对未登录词的概率分配权重减少。

这种方法提升了拉普拉斯平滑的泛用性。

4)古德图灵平滑(Good Turing Smoothing)

古德图灵平滑是一种非常”动态柔软“的平滑方式,笔者自己非常喜欢这类数学公式,他并不是生硬地增加或者减少某个具体的实数值,而是根据当前上下文动态决定调整的程度。

假定某个事件实际出现 r 次,调整后频次为 r∗ 次,n表示n-gram中恰好出现 r 次的不同序列数,nr+1类似。

这个公式的合理性在哪里呢?

直观上讲,它通过高频事件优化低频事件的概率表示,并最终通过层层迭代获取到未登录词的概率。根据Zipf's law,nr分布如下图所示(横轴为r,纵轴为nr),由图可知,通常而言 r<r

一般来说,出现一次的词的数量比出现两次的多,出现两次的比三次的多,高频词到低频次的出现频次下降趋势大致呈现上图所示规律,这种规律称为Zipf定律(Zipf’s Law)。

4. 基于n-gram的机器学习应用

1)基于ngram定义的两个字符串的距离

模糊匹配的关键在于如何衡量两个长得很像的单词(或字符串)之间的“差异”,这种差异通常又称为“距离”。除了可以定义两个字符串之间的编辑距离(通常利用Needleman-Wunsch算法或Smith-Waterman算法),还可以定义它们之间的Ngram距离。

假设有一个字符串S,那么该字符串的Ngram就表示按长度N切分原sentence得到的词段(长度为N),也就是S中所有长度为N的子字符串。设想如果有两个字符串,然后分别求它们的Ngram,那么就可以从它们的共有子串的数量这个角度去定义两个字符串间的Ngram距离。但是仅仅是简单地对共有子串进行计数显然也存在不足,这种方案显然忽略了两个字符串长度差异可能导致的问题。比如字符串girl和girlfriend,二者所拥有的公共子串数量显然与girl和其自身所拥有的公共子串数量相等,但是我们并不能据此认为girl和girlfriend是两个等同的匹配。为了解决该问题,有研究员提出以非重复的Ngram分词为基础来定义Ngram距离,公式表示如下:

|GN(S1)|+|GN(S2)|−2×|GN(S1)∩GN(S2)|

此处,|GN(S1)||GN(S1)|是字符串S1S1的Ngram集合,N值一般取2或者3。以N=2为例对字符串Gorbachev和Gorbechyov进行分段,可得如下结果

Go or rb ba ac ch he ev
Go or rb be ec ch hy yo ov

结合上面的公式,即可算得两个字符串之间的距离是8 + 9 − 2 × 4 = 9。显然,字符串之间的距离越小,它们就越接近。当两个字符串完全相等的时候,它们之间的距离就是0。可以看到,这种公式充分考虑到了字符串的长度区别和相同词组2者的共同作用

2)利用ngram模型评估语句是否合理

从统计的角度来看,自然语言中的一个句子S可以由任何词串构成,不过概率P(S)有大有小。例如:

S1 = 我刚吃过晚饭
S2 = 刚我过晚饭吃

显然,对于中文而言S1是一个通顺而有意义的句子,而S2则不是,所以对于中文来说P(S1)>P(S2)

假设我们现在有一个语料库如下,其中<s1><s2>是句首标记,</s2></s1>是句尾标记:

<s1><s2>yes no no no no yes</s2></s1>
<s1><s2>no no no yes yes yes no</s2></s1>

下面我们的任务是来评估如下这个句子的概率:

<s1><s2>yes no no yes</s2></s1>

我们利用trigram来对这句话进行词组分解

所以我们要求的概率就等于:1/2×1×1/2×2/5×1/2×1=0.05

3)在实际项目中需要注意的问题

词袋模型编码得到的词向量长度,等于词袋中词集的个数。

然而,通常在一个海量的document集合中,词集的个数是非常巨大的。如果全部都作为词集,那么最终编码得到的词向量会非常巨大,同时也很容易遇到稀疏问题。

因此,我们一般需要在特征编码前进行padding or truncating,具体的做法可以由以下两种:

1. 根据TOP Order排序获取指定数量的词集,例如取 top 8000高频词作为词集;
2. 针对编码后得到的词向量进行 padding or truncating

这两种做法的最终效果是一样的。

Relevant Link:

http://blog.csdn.net/lengyuhong/article/details/6022053
https://flystarhe.github.io/2016/08/16/ngram/
http://blog.csdn.net/baimafujinji/article/details/51281816
https://en.wikipedia.org/wiki/Bag-of-words_model
http://www.52nlp.cn/tag/n-gram
https://blog.csdn.net/tiffanyrabbit/article/details/72650606

0x3:独立同分布假设:所有字符间都独立同分布的的统计语言模型

独立同分布假设是对马尔科夫假设的进一步,即假设所有字符之间连前序关系都不存在,而是一个个独立的字符,这和朴素贝叶斯的独立同分布假设本质上在思想上是一致的,都是通过增加先验约束,从而显著减小参数搜索空间。

1. 词袋bow统计语言模型基本定义

词袋模型,顾名思义,把各种词放在一个文本的袋子里,即把文本看做是无序的词的组合。利用统计语言模型来理解词序列的概率分布。

文本中每个词出现的概率仅与自身有关而无关于上下文。这是对原始语言模型公式的最极端的简化,丢失了原始文本序列的前序依赖信息

对于一个文档(document),忽略其词序、语法、句法,将其仅仅看做是一个词集合,或者说是词的一个组合,文档中每个词的出现都是独立的,不依赖于其他词是否出,即假设这篇文章的作者在任意一个位置选择一个词汇都不受前面句子的影响而独立选择的。这是一种非常强的假设,比马尔科夫假设还要强。

2. 词库表示法合理性依据

词库模型依据是:存在类似单词集合的文章的语义同样也是类似的

3. 词库编码代码示例

from sklearn.feature_extraction.text import CountVectorizer

if __name__ == '__main__':
    corpus = [
        'This is the first document.',
        'This is the second second document.',
        'And the third one.',
        'Is this the first document?',
    ]
    vectorizer = CountVectorizer(min_df=1)
    X = vectorizer.fit_transform(corpus)
    print vectorizer.get_feature_names()
    print X.toarray()

''''
[u'and', u'document', u'first', u'is', u'one', u'second', u'the', u'third', u'this']
总体样本库中共有9个token word

[[0 1 1 1 0 0 1 0 1]
 [0 1 0 1 0 2 1 0 1]
 [1 0 0 0 1 0 1 1 0]
 [0 1 1 1 0 0 1 0 1]]
每一行代表一个sentence,共4行(4个sentence)
每一列代表一个token词的词频,共9列(9个token word)
''''

对运行的结果我们仔细观察下,第一个sentence和最后一个文本sentence分别表达了陈述和疑问两种句式(token word出现的位置不一样,造成表达意思的不同),但是因为它们包含的词都相同。所以经过词袋编码后,得到的特征向量都是一样的。

很显然,这个编码过程丢失了这个疑问句的句式信息,这就是词袋模型最大的缺点:不能捕获词间相对位置信息。

'This is the first document.',
'Is this the first document?',

[[0 1 1 1 0 0 1 0 1] 
 [0 1 1 1 0 0 1 0 1]]

Relevant Link:

http://www.cnblogs.com/platero/archive/2012/12/03/2800251.html
http://feisky.xyz/machine-learning/resources/github/spark-ml-source-analysis/%E7%89%B9%E5%BE%81%E6%8A%BD%E5%8F%96%E5%92%8C%E8%BD%AC%E6%8D%A2/CountVectorizer.html

0x4:局部与整体分布假设:TF-IDF(Term Frequency-Inverse Document Frequency)表示法

TF-IDF(term frequency–inverse document frequency)是一种用于信息检索与数据挖掘的常用加权技术

TF-IDF产生的特征向量是带有倾向性的,主要可以被用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。

字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。

TF-IDF属于BOW语言模型的一种,但是在基础的词频统计之上增加和单个词和全局词集的相对关系。同时,TF-IDF也不关注词序信息,TF-IDF同样也丢失了词序列信息

1. TF-IDF的加权词频统计思想

用通俗的话总结TF-IDF的主要思想是:

如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类,也即可以作为所谓的关键字。

假定现在有一篇长文《中国的蜜蜂养殖》,我们准备用计算机提取它的关键词,一个容易想到的思路,就是找到出现次数最多的词。如果某个词很重要,它应该在这篇文章中多次出现。

于是,我们进行"词频"(Term Frequency,缩写为TF)统计。

在统计结束后我们会发现,出现次数最多的词是 "的"、"是"、"在" 这一类最常用的词。它们叫做"停用词"(stop words),在大多数情况下这对找到结果毫无帮助、我们采取过滤掉停用词的策略。

假设我们把它们都过滤掉了,只考虑剩下的有实际意义的词。这样又会遇到了另一个问题,我们可能发现"中国"、"蜜蜂"、"养殖"这三个词的出现次数一样多。这是不是意味着,作为关键词,它们的重要性是一样的?

显然不是这样。因为"中国"是很常见的词(在日常的语料库中出现频率较高),相对而言,"蜜蜂"和"养殖"不那么常见(倾向于专有领域的词)。

如果这三个词在一篇文章的出现次数一样多,有理由认为,"蜜蜂"和"养殖"的重要程度要大于"中国",也就是说,在关键词排序上面,"蜜蜂"和"养殖"应该排在"中国"的前面。所以,我们需要一个重要性调整系数,衡量一个词是不是常见词。如果某个词比较少见,但是它在这篇文章中多次出现,那么它很可能就反映了这篇文章的特性,正是我们所需要的关键词

用统计学语言表达,就是在词频的基础上,要对每个词分配一个"重要性"权重

1. 最常见的词("""""")给予最小的权重;
2. 较常见的词("中国")给予较小的权重;
3. 较少见的词("蜜蜂""养殖")给予较大的权重;

这个权重叫做"逆文档频率"(Inverse Document Frequency,缩写为IDF),它的大小与一个词的常见程度成反比,它的作用是对原始词频权重进行反向调整。

知道了"词频"(TF)和"逆文档频率"(IDF)以后,将这两个值相乘,就得到了一个词的TF-IDF值。某个词对文章的重要性越高,它的TF-IDF值就越大。所以,排在最前面的几个词,就是这篇文章的关键词

2. TF-IDF算法流程

第一步 - 计算词频

考虑到文章有长短之分,为了便于不同文章的比较,进行"词频"归一化"

第二步 - 计算逆文档频率

这时,需要一个语料库(corpus),用来模拟语言的总体使用环境

如果一个词越常见,那么分母就越大,逆文档频率就越小越接近0。分母之所以要加1,是为了避免分母为0(即所有文档都不包含该词),log表示对得到的值取对数。

第三步 - 计算TF-IDF

可以看到,TF-IDF与一个词在文档中的出现次数成正比,与该词在整个语言中的出现次数成反比

以《中国的蜜蜂养殖》为例:

假定该文长度为1000个词,"中国"、"蜜蜂"、"养殖"各出现20次,则这三个词的"词频"(TF)都为0.02。然后,搜索Google发现,包含"的"字的网页共有250亿张,假定这就是中文网页总数包含"中国"的网页共有62.3亿张;包含"蜜蜂"的网页为0.484亿张;包含"养殖"的网页为0.973亿张。

则它们的逆文档频率(IDF)和TF-IDF如下

中国
IDF = math.log(250 / 63.3) = 1.37357558871
TF-IDF = IDF * 0.02 = 0.0274715117742

蜜蜂
IDF = math.log(250 / 63.3) = 5.12671977312 
TF-IDF = IDF * 0.02 = 0.102534395462

养殖
IDF = math.log(250 / 63.3) = 4.84190569082 
TF-IDF = IDF * 0.02 = 0.0968381138164

从上表可见,"蜜蜂"的TF-IDF值最高,"养殖"其次,"中国"最低。所以,如果只选择一个词,"蜜蜂"就是这篇文章的关键词

3. TF-IDF的sklearn封装实现

因为 tf–idf 在特征提取中经常被使用,所以有一个类: TfidfVectorizer 在单个类中结合了所有类和类中的选择:

from sklearn.feature_extraction.text import TfidfVectorizer

if __name__ == '__main__':
    corpus = [
        'This is the first document.',
        'This is the second second document.',
        'And the third one.',
        'Is this the first document?',
    ]
    vectorizer = TfidfVectorizer(min_df=1)
    tfidf = vectorizer.fit_transform(corpus)
    print vectorizer.vocabulary_
    print tfidf.toarray()
''''

{u'and': 0, u'third': 7, u'this': 8, u'is': 3, u'one': 4, u'second': 5, u'the': 6, u'document': 1, u'first': 2}
[[ 0.          0.43877674  0.54197657  0.43877674  0.          0.        0.35872874  0.          0.43877674]
 [ 0.          0.27230147  0.          0.27230147  0.          0.85322574    0.22262429  0.          0.27230147]
 [ 0.55280532  0.          0.          0.          0.55280532  0.        0.28847675  0.55280532  0.        ]
 [ 0.          0.43877674  0.54197657  0.43877674  0.          0.        0.35872874  0.          0.43877674]]

可以看到,document在整个语料库中都出现,所以权重被动态降低了

Relevant Link:

http://www.ruanyifeng.com/blog/2013/03/tf-idf.html
http://www.cnblogs.com/ybjourney/p/4793370.html
http://www.cc.ntu.edu.tw/chinese/epaper/0031/20141220_3103.html
https://nlp.stanford.edu/IR-book/html/htmledition/tf-idf-weighting-1.html
http://sklearn.lzjqsdd.com/modules/feature_extraction.htm

 

4. 语义分析模型语言模型

0x1:LSA(Latent Semantic Analysis)潜在语义分析表示法

笔者思考:LSA并不单纯地像词库表示法和TF-IDF一样,仅仅进行文档的向量化表征。相比之下,LSA算法在传统词频统计语言模型之上进行了语义分析。所以,严格来说,LSA可能并不能算是一个单纯的文本特征表征法,LSA产出的是包含语义特性的向量特征表征

1. LSA是什么

潜在语义分析LSA(Latent Semantic Analysis )也叫作潜在语义索引LSI( Latent Semantic Indexing ) 顾名思义是通过分析文章(documents )来挖掘文章的潜在意思或语义(concepts )。

不同的单词可以表示同一个语义,或一个单词同时具有多个不同的意思,这些的模糊歧义是LSA主要解决的问题。

例如,bank 这个单词如果和mortgage, loans, rates 这些单词同时出现时,bank 很可能表示金融机构的意思。可是如果bank 这个单词和lures, casting, fish一起出现,那么很可能表示河岸的意思。

2. LSA的基本假设

一篇文章(包含很多sentence的document)可以随意地选择各种单词来表达,因此不同的作者的词语选择风格都大不相同,表达的语义也因此变得模糊。

这种单词选择的随机性必然将噪声的引入到“单词-语义关系”(word-concept relationship)。

LSA能过滤掉一些噪声,同时能在语料库中找出一个最小的语义子集( to find the smallest set of concepts that spans all the documents)。所以本质上LSA也可以理解为一种信息压缩的算法。

LSA引入了一些重要的假设

1. 文章通过”bags of words”词频语言模型的形式来表示,也就是说单词的出现顺数并不重要,而与单词在文中出现的次数相关
2. 语义通过“一组”最有可能同时出现的单词来表示。例如”leash”, “treat”, “obey” 常出现在关于 dog training的文章里面,一组单词共同决定了一种语义。
3. 每个单词假设只有一个意思,当然这个假设在遇到““banks””(既表示河岸也表示金融银行)这种情况当然不合适,但是这个假设将有助于简化问题难度。

3. LSA的工作原理

1)词-文档矩阵(Occurences Matrix) - 将原始document转换为词频统计稀疏矩阵

LSA 使用词-文档矩阵来描述一个词语是否在一篇文档中。

词-文档矩阵式一个稀疏矩阵,其行代表词语,其列代表文档。

一般情况下,词-文档矩阵的元素是该词在文档中的出现次数(词库表示法),也可以是是该词语的TF-IDF。词-文档矩阵的本质就是传统的词频语言模型,后面的LSA语义分析在基于传统的词频语言模型进行了拓展。

2)SVD奇异值矩阵分解 - 降维

在构建好词-文档矩阵之后,LSA将对该矩阵进行降维,来找到词-文档矩阵的一个低阶近似。降维的原因有以下几点:

1. 原始的词-文档矩阵太大导致计算机无法处理,从此角度来看,降维后的新矩阵式原有矩阵的一个近似;
2. 原始的词-文档矩阵中有噪音,从此角度来看,降维后的新矩阵式原矩阵的一个去噪矩阵,即冗余信息压缩;
3. 原始的词-文档矩阵过于稀疏。原始的词-文档矩阵精确的反映了每个词是否“出现”或“出现的频次”于某篇文档的情况,然而我们往往对某篇文档“相关”的所有词更感兴趣,因此我们需要发掘一个词的各种同义词的情况;
4. 将维可以解决一部分同义词的问题,也能解决一部分二义性问题。具体来说,原始词-文档矩阵经过降维处理后,原有词向量对应的二义部分会加到和其语义相似的词上,而剩余部分则减少对应的二义分量;

降维的结果是不同的词或因为其语义的相关性导致合并,如下面将car和truck进行了合并:

{(car), (truck), (flower)} --> {(1.3452 * car + 0.2828 * truck), (flower)} 

3)SVD降维推导

假设 X 矩阵是词-文档矩阵,其元素(i,j)代表词语 i 在文档 j 中的出现次数,则 X矩阵看上去是如下的样子:

\begin{matrix}  & \textbf{d}_j \\ & \downarrow \\\textbf{t}_i^T \rightarrow &\begin{bmatrix} x_{1,1} & \dots & x_{1,n} \\\vdots & \ddots & \vdots \\x_{m,1} & \dots & x_{m,n} \\\end{bmatrix}\end{matrix}

可以看到,每一行代表一个词的向量,该向量描述了该词和所有文档的关系\textbf{t}_i^T = \begin{bmatrix} x_{i,1} & \dots & x_{i,n} \end{bmatrix}

相似的,一列代表一个文档向量,该向量描述了该文档与所有词的关系\textbf{d}_j = \begin{bmatrix} x_{1,j} \\ \vdots \\ x_{m,j} \end{bmatrix}

词向量\textbf{t}_i^T \textbf{t}_p的点乘可以表示这两个单词在文档集合中的相似性。矩阵X X^T 包含所有词向量点乘的结果,元素(i,p)和元素(p,i)具有相同的值,代表词 p 和词 i 的相似度。

类似的,矩阵X^T X包含所有文档向量点乘的结果,也就包含了所有文档总体的相似度。

现在假设存在矩阵X的一个分解,即矩阵X可分解成正交矩阵U和V,和对角矩阵\Sigma的乘积。

这种分解叫做奇异值分解(SVD),即:

\begin{matrix}X = U \Sigma V^T\end{matrix}

因此,词与文本的相关性矩阵可以表示为:

\begin{matrix}X X^T &=& (U \Sigma V^T) (U \Sigma V^T)^T = (U \Sigma V^T) (V^{T^T} \Sigma^T U^T) = U \Sigma V^T V \Sigma^T U^T = U \Sigma \Sigma^T U^T \\X^T X &=& (U \Sigma V^T)^T (U \Sigma V^T) = (V^{T^T} \Sigma^T U^T) (U \Sigma V^T) = V \Sigma^T U^T U \Sigma V^T = V \Sigma^T \Sigma V^T\end{matrix}

因为\Sigma \Sigma^T\Sigma^T \Sigma是对角矩阵,因此U 肯定是由X X^T的特征向量组成的矩阵,同理VX^T X特征向量组成的矩阵。这些特征向量对应的特征值即为\Sigma \Sigma^T中的元素。综上所述,这个分解看起来是如下的样子:
\begin{matrix}  & X & & & U & & \Sigma & & V^T \\ & (\textbf{d}_j) & & & & & & & (\hat{\textbf{d}}_j) \\ & \downarrow & & & & & & & \downarrow \\(\textbf{t}_i^T) \rightarrow &\begin{bmatrix} x_{1,1} & \dots & x_{1,n} \\\\\vdots & \ddots & \vdots \\\\x_{m,1} & \dots & x_{m,n} \\\end{bmatrix}&=&(\hat{\textbf{t}}_i^T) \rightarrow&\begin{bmatrix} \begin{bmatrix} \, \\ \, \\ \textbf{u}_1 \\ \, \\ \,\end{bmatrix} \dots\begin{bmatrix} \, \\ \, \\ \textbf{u}_l \\ \, \\ \, \end{bmatrix}\end{bmatrix}&\cdot&\begin{bmatrix} \sigma_1 & \dots & 0 \\\vdots & \ddots & \vdots \\0 & \dots & \sigma_l \\\end{bmatrix}&\cdot&\begin{bmatrix} \begin{bmatrix} & & \textbf{v}_1 & & \end{bmatrix} \\\vdots \\\begin{bmatrix} & & \textbf{v}_l & & \end{bmatrix}\end{bmatrix}\end{matrix}
 
\sigma_1, \dots, \sigma_l 被称作是奇异值,而 u_1, \dots, u_l 和v_1, \dots, v_l则叫做左奇异向量右奇异向量
通过矩阵分解可以看出,原始矩阵中的\textbf{t}_i 只与U矩阵的第 i 行有关,我们则称第 i 行为 \hat{\textrm{t}}_i
同理,原始矩阵中的\hat{ \textrm{d}}_j只与V^T中的第 j 列有关,我们称这一列为\hat{ \textrm{d}}_j
\textbf{t}_i\hat{ \textrm{d}}_j并非特征值,但是其由矩阵所有的特征值所决定。
当我们选择 k 个最大的奇异值,和它们对应的U与V中的向量相乘,则能得到一个X矩阵的 k 阶近似,此时该矩阵和X矩阵相比有着最小误差(即残差矩阵的Frobenius范数)。
但更有意义的是这么做可以将词向量和文档向量映射到语义空间。
向量\hat{\textbf{t}}_i与含有 k 个奇异值的矩阵相乘,实质是从高维空间到低维空间的一个变换,可以理解为是一个高维空间到低维空间的近似。
同理,向量 \hat{\textbf{d}}_j也存在这样一个从高维空间到低维空间的变化
这种变换用公式总结出来就是这个样子:X_k = U_k \Sigma_k V_k^T

4. LSA的应用

  • 在低维语义空间可对文档进行比较,进而可用于文档聚类和文档分类。
  • 在翻译好的文档上进行训练,可以发现不同语言的相似文档,可用于跨语言检索。
  • 发现词与词之间的关系,可用于同义词、歧义词检测。.
  • 通过查询映射到语义空间,可进行信息检索。
  • 从语义的角度发现词语的相关性,可用于“选择题回答模型”(multi choice qustions answering model)。

5. LSA算法局限性

  • 新生成的矩阵的解释性比较差。造成这种难以解释的结果是因为SVD只是一种数学变换,并无法对应成现实中的概念。
  • LSA无法扑捉一词多意的现象。在原始词-向量矩阵中,每个文档的每个词只能有一个含义。比如同一篇文章中的“The Chair of Board"和"the chair maker"的chair会被认为一样。在语义空间中,含有一词多意现象的词其向量会呈现多个语义的平均。相应的,如果有其中一个含义出现的特别频繁,则语义向量会向其倾斜。
  • LSA具有词袋模型的缺点,即在一篇文章,或者一个句子中忽略词语的先后顺序。
  • LSA的概率模型假设文档和词的分布是服从联合正态分布的,但从观测数据来看是服从泊松分布的。因此LSA算法的一个改进PLSA使用了多项分布,其效果要好于LSA。

Relevant Link: 

https://blog.csdn.net/roger__wong/article/details/41175967
https://blog.csdn.net/zhzhji440/article/details/47193731

 

5. 神经网络语言模型(neural network language model, NNLM)(2003年)

0x1:传统统计语言模型的问题

统计语言模型在学习单词序列的联合概率时,一个比较显然的问题是维度灾难,计算和存储参数巨多,在高维下,数据稀疏会导致统计语言模型存在很多为零的条件概率。

至于为什么会存在高维空间中,词向量会出现概率稀疏问题,笔者有自己的一个理解,我们可以从Johnson–Lindenstrauss定理中得到启发。简单说来,它的结论是这样的:一个一百万维空间里的随便一万个点,一定可以几乎被装进一个几十维的子空间里!

举一个形象的例子说明,即高维物体之间的“间隙”可能很大,换句话说高维空间是非常“空旷”、”稀疏“的。

从简单的2维开始举例:下图单位正方形内部有四个直径为0.5的圆把它充满,那么这些圆之间的空隙中能填上的最大小圆(红色那个)的直径约为0.21。

如果换成三维单位正方体,则情况如下图。此时红色小球的直径约为0.37,比2维时变大了。

再往高维走,这个间隙中的小球直径可以用如下方法计算:\frac{\sqrt{n}-1}{2} ,其中n为维度数。

我们会发现:这个“填缝隙的小球”的直径居然可以无限增大!事实上在4维的时候,小球的直径达到0.5,这就和周围那些球一样大了。而到了9维的时候,小球已经膨胀到能接触正方体的壁了。

形象点说:高维沙堆之间的空隙能轻易放下一堆高维大西瓜。
那这些和统计语言模型的高维稀疏问题有什么关系呢?
简单来说,由于自然语言本身的特性,词集空间越大,维度就越高,就越容易出现高维稀疏问题,一段自然语言句子常常会分布在高维空间的高维部分,而低维部分大量浪费了。
后面要介绍的词向量主要就是解决该问题,将每个词”相对均匀“地分布到一个维度相对适中的词向量空间中,避免了维度稀疏。
词向量的核心思想是,不从词频角度来对文本进行建模,而是将所有词都看作是一个向量空间中的一个向量,这就不存在”未登录词“的问题,所有的词都被映射到向量空间中某个向量点。

Relevant Link: 

https://blogs.scientificamerican.com/roots-of-unity/why-you-should-care-about-high-dimensional-sphere-packing/   
https://zh.wikipedia.org/zh-hant/%E7%BA%A6%E7%BF%B0%E9%80%8A-%E6%9E%97%E7%99%BB%E6%96%AF%E7%89%B9%E5%8A%B3%E6%96%AF%E5%AE%9A%E7%90%86
https://www.zhihu.com/question/32210107
http://imaginary.farmostwood.net/573.html 

0x2:分布式表征(distributed representation)词向量概念

1. word embedding词向量历史

NNLM 最早由Bengio系统化提出并进行了深入研究[Bengio, 2000, 2003]。当然分开来看,(1) distributed representation最早由Hinton提出(1985), (2)利用神经网络建模语言模型由Miikkulainen and Dyer,Xu and Rudnicky分别在1991和2000提出。

Bengio提出词向量的概念,代替ngram使用离散变量(高维),采用连续变量(具有一定维度的实数向量)来进行单词的分布式表示,解决了维度爆炸的问题,同时通过词向量可获取词之间的相似性。

2. 什么是分布式表征词向量?和传统的词频统计词向量有什么区别?

传统的词频统计模型是one-hot类型的,每个词都是一个 |V| 长度的且只有一位为1,其他位都为0的定长向量。每个词向量之间都是完全正交的,相关性为0。虽然可以实现对原始语料进行编码的目的,但是对不同的词之间的相似性、语法语义的表征能力都很弱。

为了解决这个问题,Hinton教授提出了分布式表征词向量,先来直观地看一下什么是分布式表征词向量,下面是一组将高维词向量通过t-SNE降维到2维平面上进行可视化展示的截图。

可以看到,在该二维图中,每一个词不仅对应了一个向量(词向量化编码)。同时相似的词之间,例如“are”和“is”是紧挨在一起的。

通过这个图,我们可以很直观地对这个所谓的分布式表征词向量的特性作一个猜想:

1. 首先,这个所谓的分布式表征词向量依然是一个定长的向量,每个词向量都位于同一个向量空间中;
2. 词向量之间具有明显的空间相关性(这里借用了线性先关的概念思想),即相似的词都明显聚集在某一个空间区域中,用其中一个词可以表达(代替)这个区域中的所有词。相对的,不相似的词分布在空间中的不同区域;

这么美妙的特性是怎么得到的呢?接下来我们来深入讨论。

笔者思考:在学习word embedding之前,读者朋友可以先进行一个前导思考,思考一下AutoEncoder的核心原理,或者更本质的,我们思考一下神经网络的全连接隐层到底代表着什么?

从线性代数角度来看,神经网络的隐藏层接受输入层向量,经过权重系数矩阵对应的线性变换(linear transformation)之后,原始的输入向量被变换到了另一个向量空间中。通过末端损失函数的BP负反馈,反过来不断调整隐层系数矩阵对应的向量空间的概率分布,最终当损失函数收敛时,隐藏层的系数矩阵对应的向量空间的概率分布是“最有利于”预测值靠近目标函数的。这点其实并不难理解,只是我们在学习神经网络初期可能更关注的是神经网络末端的输出层,毕竟输出层才是我们要的二分类/多分类/概率预测的有用结果,而对训练结束后,隐藏层到底长什么样并不十分关心。

可以看到,隐藏层会朝什么方向调整,取决于神经网络末端的损失函数。如果我们的用一个表达语法语义结构的概率似然函数来作为损失函数进行BP优化,则可以想象,训练收敛后,隐藏层的概率分布会趋向于和语料集中的语法语义结构同构

对类似的原理感兴趣的读者朋友可以google一些作CNN隐藏层可视化的文章和代码,通过将CNN的隐藏层进行反向激活后可视化,可以看到CNN的每一个隐藏层都代表了一种特定的细节滤波器,都具有明显的几何纹理特征

3. 用似然函数来表征语言模型的条件概率 - 语法语义结构表征

需要强调的是,本文介绍的所有语言模型都是在基本语言模型(language model)框架之下的,区别仅仅在于用于表达每个词向量的生成过程和表达方式不同。

语言模型本质上是一个概率分布模型 P ,对于语言里的每一个字符串 S 给出一个概率 P(S) 。假设有一个符号的集合 \{w\} ,我们不妨把每一个 w_i 称作一个“单词”,由零个或多个单词连接起来就组成了一个字符串 S = w_1w_2\cdots w_n ,字符串可长可短,例如实际语言中的句子、段落或者文档都可以看作一个字符串。

所有合法(例如,通过一些语法约束)的字符串的集合称作一个语言,而一个语言模型就是这个语言里的字符串的概率分布模型。

可实际上越远的词语其实对该词的影响越小,那么如果考虑一个n-gram, 每个词都只受其前面n-1个词的影响,所以我们加入了马尔科夫假设之后,则有:

上面公式定义非常清晰,但是不是非常直观,我们举一个例子:

假设我们的语料只有一句话:我爱祖国

如果用传统语言模型,对应的联合概率分布就是:P(我,爱,祖,国) = P(我 | start)+ P(爱 | 我)+ P(祖 | 爱,我)+ P(国 | 祖,爱,我)

加入马尔科夫假设后,即只考虑一个ngram范围内的前序词,对应的联合概率分布就是(以2-gram为例):P(我,爱,祖,国) = P(我 | start)+ P(爱 | 我)+ P(祖 | 爱)+ P(国 | 祖)

上述就是基本的语言模型(language model)。

接下来的问题,这个条件概率如何计算呢?

如果是统计语言模型,条件概率是通过统计对应的ngram词序列的出现次数来计算的,也就是词频,具体方法可以回到之前章节的讨论。

备注:我们熟悉的朴素贝叶斯判别器就是一个词频统计语言模型的应用

但我们知道,词频统计很容易遇到“未登录词”的问题,虽然有很多平滑手段,但是无法100%解决该问题。

神经网络不再使用词频来作为统计工具,而是直接基于似然函数(例如tahn、sigmoid)来计算条件概率。

同时,这个模型需要满足的概率累加和约束,即对于任意的,表示与上下文所有可能的组合的概率和为1。 

这看起来太美好了,我们可以有一个函数可以直接得到语句对应的【0,1】值域内的概率值!不用再保存大量的ngram词频统计结果。那接下来的问题就是,似然函数形式如何?似然函数值如何计算呢?显然不像词频统计那样直接进行一个 count/N 就可以的。

4. Bengio NIPS’03 Model - N-gram neural model

Bengio使用前馈神经网络(FFNN,Feed Forward Neural Network)表示f,其结构如下:

1)输入层

这里所谓的ngram neural,通俗的解释是说神经网络的输入层是ngram的形式。如上面流程图所示,最下方绿色方块代表了一组ngram词序列。这么做的原因和ngram统计语言模型是一样的,为了捕获词序列的短程依赖特征。

输入层的size由ngram序列长度决定。

2)全连接隐层 - 词向量矩阵层

接下来,我们需要定义词向量嵌入空间(embedding空间)的维度,读者朋友在TensorFlow编程中对这个参数应该非常熟悉了,就是output_dim参数,在NLP项目开发中,我们一般会设置64/128/256这样的值,这里记作 m。

这层隐层实际上构建了一个的矩阵C(m 行 |V| 列),每个词都对应一个m维的列向量,总共 |V| 个单词。这个矩阵C作为一个shared super parameters在整个隐层所有神经元中共享。

这个系数矩阵C非常重要,它既是训练过程中将每次词转换为一个m维向量的lookup-table,也是最终训练完成后,我们得到的”词嵌入向量表“。

通过训练的负反馈调整,矩阵C中初始的每一个m维向量都进行了调整,使得和训练预料和语法和语义最匹配。

映射矩阵将输入的每个单词映射到实数向量

如前所述,每次向神经网络输入一个ngram词序列,句子单词以onehot形式作为输入,与共享矩阵C做矩阵相乘得到索引位置的词向量(其实就是一个查表过程)。

拼接得到一个新的向量 e

3)非线性变换层

经过非线性层

4)softmax输出层 - 条件概率输出层

输出层为Softmax,保证满足概率分布累加和为1的约束,生成每一个(总共 |V| 个)目标词wt的概率为:

softmax输出的是一个长度为 |V|、且累加和为1的的向量,例如[0.01, ..., 0.05]

5)损失函数

在神经网络的损失计算中,我们需要计算按照神经网络的正向传播计算的分数S1,和按照正确标注计算的分数S2(目标值),之间的差距,计算Loss,才能应用反向传播。

整个网络的损失值(cost)为多类分类交叉熵(类型数为 |V|),用公式表示为

其中表示第 个样本第 类的真实标签(0或1),表示第 i 个样本第 k 类softmax输出的概率。

6)反向传播负反馈

模型的训练目标是最大化以下似然函数:

其中 θ 为模型的所有参数,R(θ为正则化项。

模型求解方式采用随机梯度上升(SGA,Stochastic Gradient Ascent),SGA类似于SGD,仅目标函数存在min和max的区别。

5. 词嵌入向量是神经网络训练的副产品

我们可以将这个网络理解为在传统ngram之上的一种stacking升级,在ngram之上加上了神经网络。通过神经网络,在大语料集上通过神经网络的梯度向上进行训练,即联合概率最大似然目标函数,当完成训练后,神经网络的副产品,也就是输入层的对应的”词汇表中每个词对应的分布式向量表示“就是我们要的词向量。

Relevant Link: 

https://www.zhihu.com/question/62060876
http://blog.pluskid.org/?p=352 
http://www.jmlr.org/papers/volume3/bengio03a/bengio03a.pdf
https://www.zhihu.com/question/23765351
http://www.paddlepaddle.org/documentation/docs/zh/0.14.0/new_docs/beginners_guide/basics/word2vec/index.html
https://blog.csdn.net/u010089444/article/details/52624964
http://www.flickering.cn/nlp/2015/03/%E6%88%91%E4%BB%AC%E6%98%AF%E8%BF%99%E6%A0%B7%E7%90%86%E8%A7%A3%E8%AF%AD%E8%A8%80%E7%9A%84-3%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E8%AF%AD%E8%A8%80%E6%A8%A1%E5%9E%8B/

0x3:embedding词向量的一个示例 

# -*- coding: utf-8 -*-

texts = [
    'This is a text',
    'This is not a text',
    'This is an text'
]

from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences 
from keras.utils import to_categorical
from keras.models import Sequential
from keras.layers import Embedding

max_review_length = 6         # maximum length of the sentence
embedding_vecor_length = 3    # embedding dimension
top_words = 10

# num_words is tne number of unique words in the sequence, if there's more top count words are taken
tokenizer = Tokenizer(top_words)
tokenizer.fit_on_texts(texts)
sequences = tokenizer.texts_to_sequences(texts)
word_index = tokenizer.word_index
print('Found %s unique tokens.' % len(word_index))
print "word_index: ", word_index

#max_review_length is the maximum length of the input text so that we can create vector [... 0,0,1,3,50] where 1,3,50 are individual words
data = pad_sequences(sequences, max_review_length)

# 原始语料经过embedding编码得到的词向量联合结果
print('shape of data tensor:', data.shape)
print(data)

 
model = Sequential()
model.add(Embedding(top_words, embedding_vecor_length, input_length=max_review_length,mask_zero=True))
model.compile(optimizer='adam', loss='categorical_crossentropy')
output_array = model.predict(data)
print "output_array: ", output_array

# 打印出embedding层每一个神经元(即每一个词)对应的词嵌入向量(这里是3维)
print "model.layers[0].get_weights(): ", model.layers[0].get_weights()

Relevant Link:  

https://keras.io/layers/about-keras-layers/
https://stackoverflow.com/questions/45649520/explain-with-example-how-embedding-layers-in-keras-works
https://keras.io/zh/layers/embeddings/ 

 

6. RNN长序列语言模型(2010年)

0x1:传统ngram统计语言模型和NNLM语言模型的局限

ngram词频统计语言模型的问题在于捕捉句子中长期依赖的能力非常有限,目前在工业界的使用范围已经比较小,ngram词频统计模型的意义主要在于学术研究。

改进后的NNLM引入了神经网络结构,通过隐藏层的的线性/非线性变换得到一定的抽象表征能力,但是本质上就是使用神经网络编码的n-gram模型,而且对输入数据要求固定长度(一般取5-10),因为本质上遵从了Markov Assumption,所以同样也无法解决长期依赖的问题。 

RNN长序列语言模型的提出,主要就是为了解决如何学习到长距离的依赖关系的问题。

0x2:Recurrent Neural Networks Pipeline

新的语言模型是利用RNN对序列建模,复用不同时刻的线性非线性单元及权值,理论上每一步 t 预测的单词,依赖于 t 之前所有的单词。

给定一个词向量序列,序列按照时间点 t 逐个输入,每个时间点都会得到一个对应的输出

在时刻 时输出的整个词表 |V上的概率分布,是给定上文和最近的单词预测的下一个单词。其中

隐藏层神经元的特征表示:

:是时间 时输入的单词的词向量。

:输入词向量的的权值矩阵。

:前一个时间节点隐藏层特征表示的权值矩阵。

:前一个时间点 t1 的非线性激活函数的输出。

σ():非线性激活函数(sigmoid)。

关于RNN神经网络的更多讨论,可以参阅另一篇blog

0x3:RNN语言模型

回顾一下最本质的语言模型(language model)

总结一句话:通过联合概率和条件概率的形式来数值化定义一段文本序列的似然概率

在ngram词频统计语言模型中,通过词频统计的方式进行贝叶斯条件概率计算,得到似然条件概率值,例如著名的朴素贝叶斯算法。同时因为保存了词汇表,因此可以得到每个文本序列的词向量表示(向量空间由词表决定),这算一个副产品;

在NNLM语言模型中,通过神经网络的线性/非线性转换以及激活函数(例如softmax)得到似然条件概率值。同时因为保存了隐层的系数矩阵,因此可以得到每个文本序列以及每个词的词向量表示(向量空间维数由开发者指定),这算一个副产品;

在RNN语言模型中,通过循环神经网络的隐状态神经元以及激活函数得到似然条件概率。同时因为每一个输入词x都会得到一个对应的输入y,因此可以得到每个文本序列的词向量表示(向量空间由词表决定),这算一个副产品。只是大部分时候我们不太关注这个词向量,对于RNN语言模型,我们多数时候用到的是它的序列预测能力;

Relevant Link:  

https://www.cnblogs.com/LittleHann/p/6627711.html
http://www.hankcs.com/nlp/cs224n-rnn-and-language-models.html
https://www.cnblogs.com/rocketfan/p/5052245.html 

 

7. word2vec语言模型(CBOW&Skip-gram)(2013年)

0x1:word2vec提出的历史背景 - 传统NNLM语言模型存在的问题

学术研究就是在不断提出问题和解决问题的螺旋中前进,Bengio在提出NNLM模型后,大家发现NNLM存在几个问题:

1. 一个问题是,同Ngram模型一样,NNLM模型只能处理定长的序列。在03年的论文里,Bengio等人将模型能够一次处理的序列长度N提高到了5,虽然相比bigram和trigram已经是很大的提升,但依然缺少灵活性。因此,Mikolov等人在2010年提出了一种RNNLM模型,用递归神经网络代替原始模型里的前向反馈神经网络,并将Embedding层与RNN里的隐藏层合并,从而解决了变长序列的问题;
2. 另一个问题就比较严重了。NNLM的训练太慢了。即便是在百万量级的数据集上,即便是借助了40个CPU进行训练,NNLM也需要耗时数周才能给出一个稍微靠谱的解来。显然,对于现在动辄上千万甚至上亿的真实语料库,训练一个NNLM模型几乎是一个impossible mission;

Mikolov发现,原始的NNLM模型的训练其实可以拆分成两个步骤

1. 用一个简单模型训练出连续的词向量;
2. 基于词向量的表达,训练一个连续的Ngram神经网络模型。而NNLM模型的计算瓶颈主要是在第二步,简单说就是其计算量仍过大,主要计算量集中在非线性层h和输出层z的矩阵W,U运算和softmax计算;

word2vec的主要思想就是:既然我们只是想得到word的连续特征向量,完全可以对第二步里的神经网络模型进行简化。

Mikolov在2013年推出了两篇paper,并开源了一款计算词向量的工具,word2vec。

他对原始的NNLM模型做如下改造:

1. 移除前向反馈神经网络中非线性的hidden layer,直接将中间层的Embedding layer与输出层的softmax layer连接;
2. 忽略上下文环境的序列信息(没有隐层对接上下文环境的词序列):输入的所有词向量均汇总到同一个Embedding layer;
3. 将future words(当前词的前序和后序字符)都纳入上下文环境

0x2:word2vec语言模型结构

严格来说,word2vec不是一个具体的语言模型,它是2013年Google团队发表的一个。word2vec工具主要包含两个模型:跳字模型(skip-gram)连续词袋模型(continuous bag of words CBOW),以及两种高效训练的方法:负采样(negative sampling)层序softmax(hierarchical softmax)

平时业内大家交流时直接简称都叫word2vec了,并没有什么太大问题,读者朋友只要知道这点即可。

1. CBoW模型(Continuous Bag-of-Words Model)

CBOW的思想是从一个词序列中抠掉一个词,用这个词的上下文去预测这个词。

整体架构逻辑图如下:

 

注意输入层中词序列输入的时刻 t 前序和后序的词。 

数据结构图如下:

 

1)输入层

输入层和NNLM没有太大区别,本质上还是一个ngram序列。

上下文词序列以onehot形式输入,单词向量空间dimension为V,上下文单词个数为C。

2)隐藏层 - embedding layer

输入序列的所有onehot分别乘以共享的输入权重系数矩阵W(V*N矩阵,N为自己设定的词嵌入向量维度)。矩阵W就是最终训练结束后得到的词向量矩阵,也叫做word vector look-up table

所得的向量相加求平均作为隐层向量(相加求平均提高了网络的计算性能),size为1N。所以有些paper上把这层也叫projection layer,理解意思即可。

3)输出层,

乘以输出权重矩阵W'(N*V),得到向量(1V)。输入激活函数处理得到V-dim概率分布,每一维代表一个单词的似然概率。

概率最大的index所指示的单词为预测出的中间词(target word)与true label的onehot做比较,进行反向反馈训练。

2. Skip-gram模型

Skip-Gram模型和CBOW的思路是反着来的,即输入是特定的一个词的词向量,而输出是特定词对应的上下文词向量。

1)输入层

和CBow是一样的,区别在于单词数为1。

2)隐藏层 - embedding

3)输出层

上下文context的序列长度由开发者通过skip_window参数指定。

例如我们设定skip_window=2代表着选取左input word左侧2个词和右侧2个词进入我们的窗口,所以整个窗口大小span=2x2=4。

即已知,预测出现的对数似然函数为

通过softmax取前max的context序列,之后和目标值进行对比,计算损失并反向梯度反馈。

举一个Skip-gram的例子说明上述过程:

我们选定句子“The quick brown fox jumps over lazy dog”,设定我们的窗口大小为2(window_size=2),也就是说我们仅选输入词前后各两个词和输入词进行组合。下图中,蓝色代表input word,方框内代表位于窗口内的单词。Training Samples(输入, 输出)

3. CBow和Skip-gram的优点

这两种方式与NNLM有很大的不同,NNLM的主要任务是预测句子概率,词向量只用到了上文信息,是其中间产物。

word2vec是为获得单词的分布式表征而生,根据句子上下文学习单词的语义和语法信息。除了网络结构更加结合上下文信息, word2vec的另一大贡献在计算优化上。word2vec去掉了非线性隐层,CBOW将输入直接相加通过softmanx进行预测,减少了隐层的大量计算,同时通过分层softmax (hierarchical Softmax)和负采样(Negative sampling)对softmax的计算也做了很大优化。

0x3:Hierarchical Softmax

1. 为什么会提出hierarchical softmax

原始的word2vec模型从隐藏层到输出的softmax层的计算量很大,因为要计算所有词的softmax概率,再去找概率最大的值。这个模型如下图所示。其中V是词汇表的大小。

为此,Mikolov引入了两种优化算法:层次Softmax(Hierarchical Softmax)负采样(Negative Sampling),我们接下来逐个讨论。

笔者思考:本质上,h-softmax解决的问题是“Massive multi-label(海量多分类)”的问题,当待分类或待预测的label数量很多时,利用二叉树这种数据结构,可以将O(N)的复杂度显著降低到O(logN)的对数复杂度,大大提高运算效率

2. 通过二叉二叉霍夫曼树代替输出层神经元

与传统的神经网络softmax输出不同,为了避免要计算整个词表每一个词的softmax概率,word2vec的hierarchical softmax结构把输出层改成了一颗Huffman树。

1)图中叶子节点

图中白色的叶子节点表示词汇表中所有的 |V| 个词,Huffman树并没有改变整个模型的基础架构,对于输出层来说,每一个词依然会有一个对应的似然概率计算路径。

接下来我们讨论在神经网络训练过程中,是如何调整Huffman树节点对应的权重系数的。

2)图中黑色非叶子节点

Huffman树中每一叶子结点代表一个label,在每一个非叶子节点处都需要作一次二分类,走左边的概率和走右边的概率,这里用逻辑回归(sigmoid函数)的公式表示:

向左走(正类):

向右走(负类):

其中 xi 是当前节点中的词向量,θ 是要学习的模型参数。

3)计算输入条件概率

每一个叶子节点都对应唯一的一条从root节点出发的路径,以上图为例,当我们计算w2输出概率时,我们需要从根节点到叶子结点计算概率的乘积。

4)更新路径上节点的权重向量

我们的目的是使得这条路径的概率最大,即最大。

通过累乘路径上所有节点的似然概率公式,然后通过随机梯度上升进行BP反馈,不断更新从根结点到叶子结点的路径上面节点的向量即可,而不需要更新所有的词的出现概率,这样大大的缩小了模型训练更新的时间。

0x4:Negative Sampling

1. 为什么会提出Negative sampling

在NNLM和word2ve的方法输出层的维度均为词表大小|V|,从而输出权重矩阵为dim × |V|,矩阵高维稀疏。

在训练神经网络时,每当接受一个训练样本,然后调整所有神经单元权重参数,来使神经网络预测更加准确。换句话说,每个训练样本都将会调整所有神经网络中的参数,非常耗时。

回到语言模型训练的核心任务,当训练一个样本时,我们主要目标是让预测这个训练样本(正样本)更准,对负样本只需要进行微调即可。最终让正负样本在概率空间有足够的区分性。

negative sampling的核心思想就是:每次让一个训练样本仅仅更新一小部分神经元的权重参数,从而显著降低梯度下降过程中的计算量

举一个例子,如果 vocabulary 大小为1万时, 当输入样本 ( "fox", "quick") 到神经网络时, “ fox” 经过 one-hot 编码,在输出层我们期望对应 “quick” 单词的那个神经元结点输出 1,其余 9999 个都应该输出 0。

在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们为 negative word candidate set,即可能成为Negative sampling selection的候选词。

negative sampling 的具体做法是 ,将根据词频正序排列选择一定数量的 negative words,比如选 10个 negative words 来更新对应的权重参数。

在论文中作者指出指出对于小规模数据集,建议选择 5-20 个 negative words,对于大规模数据集选择 2-5个 negative words.

如果使用了 negative sampling 仅仅去更新positive word- “quick” 和选择的其他 10 个negative words 的结点对应的权重,共计 11 个输出神经元,相当于每次只更新 dim x 11 个权重参数。对于 dim * |V| 的权重来说,计算效率就大幅度提高。

2. Negative Sampling负采样方法说明

接下来讨论具体的 negative word 是根据什么标准选出来的。

负采样的选择利用带权采样,即根据出现概率来选,经常出现的词更容易被选为负例样本。公式如下

这里的0.750.75是一种平滑策略,让低频词出现的机会更大一些。

Relevant Link:  

https://rare-technologies.com/word2vec-tutorial/
https://arxiv.org/pdf/1301.3781.pdf
https://arxiv.org/pdf/1310.4546.pdf
https://www.jianshu.com/p/471d9bfbd72f
https://radimrehurek.com/gensim/models/word2vec.html
http://www.cnblogs.com/pinard/p/7160330.html
https://www.cnblogs.com/guoyaohua/p/9240336.html
https://www.cnblogs.com/eniac1946/p/8818892.html
http://www.cnblogs.com/pinard/p/7160330.html
https://blog.csdn.net/itplus/article/details/37969979
http://www.cnblogs.com/pinard/p/7249903.html
https://www.jianshu.com/p/ed15e2adbfad

 

8. GloVe(Global Vectors for Word Representation)(2014年)

0x1:GloVe的语言模型假设

Glove在语言模型的表征上做了很多的创新,但其本质并没有跳出语言模型的范畴。

语言模型的核心任务用一句话概括就是:将一段词序列(一段文本、基因序列、音频序列)表示为所有词(文字、单基因碱基对、单波峰音频)和其所在上下文的条件概率的累乘,即联合条件概率

具体用公式表示如下:

各个语言模型的区别就在于计算得到这个条件概率。我们来回顾一下文章此前讨论的语言模型:

1. Ngram Frequency Aspect Language Model:P(wi | wj)= Count(wi,wj) / N:
直接统计所有ngram词序列出现的频率作为概率值,这是频率派的观点,即在大数据情况下,频率统计 = 概率真值。在这个基础上,有一些衍生的所谓平滑方法,本质是加入了一些先验因子,用极大后验概率统计代替极大似然统计,但是这些不重要,读者朋友要理解的是其核心思想,即频率派思维;

2. Neural-Network Likelihood Function Aspect:
NNLM、RNN、word2vec各自的细节有区别,笔者这里概括如下:
input(Central words,单个词/词序列) word vector -> Neural-Network -> Likelihood Function(softmax)-> output(most likely context words,极大后验概率估计得到可能性最大的context上下文词/词序列) -> Loss Function(基于预测结果和目标结果的diff计算损失) -> BP反向调整神经网络的参数(这里hierarchical softmax的huffman tree我们也认为是神经网络的一部分);

 

 

Relevant Link:   

https://nlp.stanford.edu/projects/glove/

https://medium.com/m/global-identity?redirectUrl=https%3A%2F%2Ftowardsdatascience.com%2Femnlp-what-is-glove-part-i-3b6ce6a7f970

https://en.wikipedia.org/wiki/GloVe_(machine_learning) 

posted @ 2017-08-03 15:56 Andrew.Hann 阅读(...) 评论(...) 编辑 收藏