目录
1.2 TF-IDF(term frequency–inverse document frequency)
5.NNLM(Neural Network Language model)
1. Bag-of-words
1.1 类似于one-hot的词袋模型
Bag of words模型最初被用在文本分类中,将文档表示成特征矢量。它的基本思想是假定对于一个文本,忽略其词序和语法、句法,仅仅将其看做是一些词汇的集合,而文本中的每个词汇都是独立的。简单说就是将每篇文档都看成一个袋子(因为里面装的都是词汇,所以称为词袋,Bag of words即因此而来),然后根据袋子里装的词汇对其进行分类。如果文档中猪、马、牛、羊、山谷、土地、拖拉机这样的词汇多些,而银行、大厦、汽车、公园这样的词汇少些,我们就倾向于判断它是一篇描绘乡村的文档,而不是描述城镇的。
基于文本的BoW模型的一个简单例子,首先给出两个简单的文本文档如下:
文档1: John likes to watch movies. Mary likes too.
文档2: John also likes to watch football games.
基于上述两个文档中出现的单词,构建如下一个词典 (dictionary):
Vocabulary= {"John": 1, "likes": 2,"to": 3, "watch": 4, "movies": 5,"also": 6, "football": 7, "games": 8,"Mary": 9, "too": 10}
上面的词典中包含10个单词, 每个单词有唯一的索引, 那么每个文本我们可以使用一个10维的向量来表示。(用整数数字0~n(n为正整数)表示某个单词在文档中出现的次数):
文档1: [1, 2, 1, 1, 1, 0, 0, 0, 1, 1]
文档2: [1, 1,1, 1, 0, 1, 1, 1, 0, 0]
从上述的表示中,可以很清楚地看出来,在文档表示过程中并没有考虑关键词的顺序,而是仅仅将文档看成是一些关键词出现的概率的集合(这是Bag-of-words模型的缺点之一),每个关键词之间是相互独立的,这样每个文档可以表示成关键词出现频率的统计集合,类似于直方图的统计表示。
优点:简单,直观
缺点:所有的词一视同仁,没有考虑词的重要度,没有考虑词的上下文,没有考虑词与词之间的关系。
1.2 TF-IDF(term frequency–inverse document frequency)
TF-IDF依旧属于词袋模型,相比与1.1节的,这种方式考虑了词对于一篇文章的重要度。
TF(Term Frequency,词频)
TF(Term Frequency,词频)表示一个给定词语t在一篇给定文档d中出现的频率。TF越高,则词语t对文档d来说越重要,TF越低,则词语t对文档d来说越不重要。那是否可以以TF作为文本相似度评价标准呢?答案是不行的,举个例子,常用的中文词语如“我”,“了”,“是”等,在给定的一篇中文文档中出现的频率是很高的,但这些中文词几乎在每篇文档中都具有非常高的词频,如果以TF作为文本相似度评价标准,那么几乎每篇文档都能被命中。
对于在某一文档 里的词语 来说, 的词频可表示为:
其中 ni,j 是词语 ti 在文档 dj 中的出现次数,分母则是在文件 dj 中所有词语的出现次数之和。
注意,这里除了文档总词数作为分母,只是做了一个标准化,因为有的文章长,有的文章短,出现100次的不一定算多,出现3次的不一定就少。有时候也用其他作为分母进行标准化。
IDF(Inverse Document Frequency,逆向文件频率)
IDF(Inverse Document Frequency,逆向文件频率)的主要思想是:如果包含词语t的文档越少,则IDF越大,说明词语t在整个文档集层面上具有很好的类别区分能力。IDF说明了什么问题呢?还是举个例子,常用的中文词语如“我”,“了”,“是”等在每篇文档中几乎具有非常高的词频,那么对于整个文档集而言,这些词都是不重要的。对于整个文档集而言,评价词语重要性的标准就是IDF。
某一特定词语的IDF,可以由总文件数除以包含该词语的文件数,再将得到的商取对数得到:
其中 |D| 是语料库中所有文档总数,分母是包含词语 ti 的所有文档数。
有时分母为0,需要平滑,
以1.1的例子,来计算两个文档中tf-idf(未平滑)。
import math
d1 = [1, 2, 1, 1, 1, 0, 0, 0, 1, 1]
d2 = [1, 1,1, 1, 0, 1, 1, 1, 0, 0]
assert len(d1) == len(d2)
contain_document_num=[0] * len(d1)
for i in range(len(d1)):
if(0 != d1[i]):
contain_document_num[i] += 1
if(0 != d2[i]):
contain_document_num[i] += 1
tf_1 = [i/float(sum(d1)) for i in d1]
tf_2 = [i/float(sum(d2)) for i in d2]
idf = [math.log(2 / float(i),math.e) for i in contain_document_num]
tf_idf_1 = [round(tf_1[i] * idf[i],5) for i in range(len(d1))]
tf_idf_2 = [round(tf_2[i] * idf[i],5) for i in range(len(d2))]
结果如下:
tf_idf_1 [0.0, 0.0, 0.0, 0.0, 0.08664, 0.0, 0.0, 0.0, 0.08664, 0.08664]
tf_idf_2 [0.0, 0.0, 0.0, 0.0, 0.0, 0.09902, 0.09902, 0.09902, 0.0, 0.0]
优点:相比one-hot的表示方法,对词语的重要程度的特征能够体现
缺点:没有考虑词的上下文,没有了考虑顺序,没有考虑词与词之间的关系。不能解决一词多义问题。
2.Bi-gram和N-gram
N-Gram是一种基于统计语言模型的算法。它的基本思想是将文本里面的内容按照字节进行大小为N的滑动窗口操作,形成了长度是N的字节片段序列。
每一个字节片段称为gram,对所有gram的出现频度进行统计,并且按照事先设定好的阈值进行过滤,形成关键gram列表,也就是这个文本的向量特征空间,列表中的每一种gram就是一个特征向量维度。
该模型基于这样一种假设,第N个词的出现只与前面N-1个词相关,而与其它任何词都不相关,整句的概率就是各个词出现概率的乘积。这些概率可以通过直接从语料中统计N个词同时出现的次数得到。常用的是二元的Bi-Gram和三元的Tri-Gram。
如果我们有一个由 m 个词组成的序列(或者说一个句子),我们希望算得概率 ,根据链式规则,可得
这个概率显然并不好算,不妨利用马尔科夫链的假设,即当前这个词仅仅跟前面几个有限的词相关,因此也就不必追溯到最开始的那个词,这样便可以大幅缩减上述算式的长度。即
这个马尔科夫链的假设为什么好用?我想可能是在现实情况中,大家通过真实情况将n=1,2,3,....这些值都试过之后,得到的真实的效果和时间空间的开销权衡之后,发现能够使用。
下面给出一元模型,二元模型,三元模型的定义:
当 n=1, 一个一元模型(unigram model)即为 :
当 n=2, 一个二元模型(bigram model)即为 :
当 n=3, 一个三元模型(trigram model)即为
然后下面的思路就很简单了,在给定的训练语料中,利用贝叶斯定理,将上述的条件概率值(因为一个句子出现的概率都转变为右边条件概率值相乘了)都统计计算出来即可。下面会给出具体例子讲解。这里先给出公式:
对于bi-gram model:
对于N-gram model:
优点:在较短的时间步考虑了词的顺序
缺点:词表的大小随着N的大小呈指数膨胀。不能解决一词多义的问题。
3.离散表示存在的问题
无论是词袋模型,还是N-gram都是属于离散表示方法,他们都会存在下列一些问题:
一、无法衡量词与词之间的关系,在编码的过程中信息就已经丢失。
二、词表维度随着语料库增长膨胀;
三、数据稀疏问题。
4.共现矩阵
局域窗中的Word - Word 共现矩阵可以挖掘语法和语义信息。
window length设为1(一般设为5~10,使用对称的窗函数(左右window length都为1)。
以下有文档:
D1 : I like deep learning.
D2 : I like NLP.
D3: I enjoy flying.
counts | I | like | enjoy | deep | learning | NLP | flying | . |
I | 0 | 2 | 1 | 0 | 0 | 0 | 0 | 0 |
like | 2 | 0 | 0 | 1 | 0 | 1 | 0 | 0 |
enjoy | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
deep | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 0 |
learning | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 |
NLP | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 1 |
flying | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 1 |
. | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
如果直接将共现矩阵行(列)作为词向量,会有以下问题:向量维数随着词典大小线性增长,存储整个词典的空间消耗非常大
,一些模型如文本分类模型会面临稀疏性问题,模型会欠稳定。所以通常使用SVD降维,构造低维稠密向量作为词的分布式表示 (25~1000维)!
将这些词的词向量,第一维度,和第二维度,用图画出来,代码如下:
import numpy as np
import matplotlib.pyplot as plt
la = np.linalg
words = ["I","like","enjoy","deep","learning","NLP","flying","."]
X = np.array([
[0,2,1,0,0,0,0,0],
[2,0,0,1,0,1,0,0],
[1,0,0,0,0,0,1,0],
[0,1,0,0,1,0,0,0],
[0,0,0,1,0,0,0,1],
[0,1,0,0,0,0,0,1],
[0,0,1,0,0,0,0,1],
[0,0,0,0,1,1,1,0]
])
U,s,Vh = la.svd(X,full_matrices = False)
plt.axis([-0.65,0,-1,1])
for i in range(len(words)):
plt.text(U[i,0],U[i,1],words[i])
SVD降维可获得稠密的,词向量,但是依旧有一些难以解决的问题。
一、计算量随语料库和词典增长膨胀太快, 对X(n,n)维的矩阵, 计算量O(n^3)。 而对大型的语料库,n~400k, 语料库大小1~60B token
二、难以为词典中新加入的词分配词向量
三、与其他深度学习模型框架差异大
5.NNLM(Neural Network Language model)
直接从语言模型出发, 将模型最优化过程转化为求词向量表示的过程。
目标函数
一、 使用了非对称前向窗口,窗口长度为n-1;
二、滑动窗口遍历整个语料库求和,计算量正比于语料库的大小;
三、概率满足归一化的条件, 这样不同位置t处的概率才能相加, 即
5.1 NNLM的结构
如图一个浅层的神经网络,用tanh双曲正切作为激活函数。
训练完成后,投影矩阵C的每一列,就是一个词向量
6.CBOW 和 Skip-Gram
如下图,CBOW 通过上下文词汇,预测中间词汇;Skip-gram通过中间词汇,预测上下文词汇。
以CBOW为例,计算过程如下:
对于CBOW,上下文窗口长度为2,其工作过程如下
一、,,,是四个词的索引,通过 (维度V*N,V是词表大小,N是词向量维度)矩阵,得到四个词向量 , , , 。并求和。(v.shape = (1*N))。
二、向量和矩阵做矩阵乘法,得到output(output.shape = V*1)。
三、对output做softmax,反向传播,求导。
最终得到, (维度V*N)就是词向量。
6.1 使用层次softmax进行优化
当上下文窗口长度为2时,在中,需要更新(4*N个参数),在中需要对(300*V)参数更新。这样不利于更新的效率。
解决方案,构建一颗哈夫曼树,可以使得每次更新的参数降低。
如何“沿着霍夫曼树一步步完成”呢?在word2vec中,我们采用了二元逻辑回归的方法,即规定沿着左子树走,那么就是负类(霍夫曼树编码1),沿着右子树走,那么就是正类(霍夫曼树编码0)。判别正类和负类的方法是使用sigmoid函数,即:
其中是当前内部节点的词向量,而则是我们需要从训练样本求出的逻辑回归的模型参数。
使用霍夫曼树有什么好处呢?首先,由于是二叉树,之前计算量为V,现在变成了。第二,由于使用霍夫曼树是高频的词靠近树根,这样高频词需要更少的时间会被找到,这符合我们的贪心优化思想。
容易理解,被划分为左子树而成为负类的概率为。在某一个内部节点,要判断是沿左子树还是右子树走的标准就是看P(−),P(+)谁的概率值大。而控制P(−),P(+)谁的概率值大的因素一个是当前节点的词向量,另一个是当前节点的模型参数。
在“足球”的例子中,假设N=300,V=6。如果不使用层次softmax,那么一次更新的参数量是。使用层次softmax更新的参数量为。并且,和的差距随着V变大会越来越大。
6.2使用负例采样进行优化
当不进行优化时,相当于再对1个正例结果和V-1个负例结果对应的参数矩阵同时做更新。其实负例样本可以不用那么多。设定一个参数,比如500,从V-1中采样出500作为负例(通常情况下,),来更新参数,这时每次更新的参数量。
关于采样,是按照词出现的频率是进行采样。即:
每一个负样本被采样的概率: ,但是实际中,需要取,即:
7.总结
本文回顾了,对文本的表示,从one-hot到word2vec的进化过程和方式,如有错误,请指出,一起交流学习。