(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

时间:2023-02-03 08:14:47

(接上一期。。。)

我们可以将这种共现关系表示成以下形式:

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

这使得该特征矩阵可与其转置互换。
该算法中还包含了一个加法偏移,防止log中的xik=0:

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

其在计算该共现矩阵时,在避免分歧的同时又维持了 X 的稀疏性。

然后定义了一个新的损失函数

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

其中:1f(0)=0;2f(x)应该为非递减;3f(x)的值相对于x要小的多。

并且提了一个常用的,效果比较好的加权函数。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

其实在了解了skip-gram模型后发现其与skip-gram模型还是有点相像的,尤其是来斯惟在其博士论文的附录证明了两者的损失函数其实是一样的,感觉还是很神奇的。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

来斯惟的一个总结的表格,来博士的博士论文总结的非常好,推荐阅读一下。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

3.2.c源码及结合gensim实战

 

首先在源码目录下make

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec) 

首先查看readme文档,文中提到他们提供了一个glove模型学习词向量的方法,并且包里还带了其他的工具可以生成大规模预料的词-词共现矩阵。

下面是对包里几个工具的介绍:

1)vocab_count统计语料中一元词频,选择一个阈值基于结果的整个词汇表的尺寸或最小频率数(阈值应该是筛掉一些词的)。这篇文档应该已经用空格讲单词隔开了(已分过词)。

2)cooccur语料的词-词共现统计,用户提供一个词汇表文件,是通过vocab_count生成的。该工具可能需要明确一些参数,运行的话‘./coocur’,生成二进制文件。

3)shuffle对cooccur产生的文件进行再处理,也可能要明确一些参数,运行‘./shuffle’。

4)glove训练glove模型基于3生成的文件,用户同时还要提供词汇表文件,由vocab_count生成的,也可能需要明确几个参数,运行‘./glove’

这是提供的几个方法。然后先小试牛刀一下,demo人家已经写好。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

这回使用的和word2vec的text8是相同的语料。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

然后运行demo。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

然后就开始运行啦。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

在即将完成的时候报了一个错

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

修改后发现还有别的问题,最后发现人家的代码用python2写的。。。我的运行环境是python3.。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

同时glove的词向量已经生成。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

于是使用anaconda 的多python环境管理,conda create -n py2 python=2.7 anaconda

conda info --envs列出所有的环境

切换环境activate/deactivate

source activate py2

source deactivate

删除一个环境conda remove -n py2 –all

然后在python2中运行。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 运行后结果如上图。

评测用到的数据如下:

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

并且我也成功的使用gensim中的word2vec对glove生成的词向量进行评测。Glovetest.py源码

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

最相似的单词:

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

单词间相似性:

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

4.word2vec同glove的对比测试

使用text8语料,测试集使用glove自带的测试集。

word2vec的评测结果:其中使用cbow,200维向量,窗口为8,负抽样25k,sample1e-4,迭代次数15次,线程数20。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)glove的评测结果:150维向量,窗口15,迭代次数15次,线程8

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 这样的结果同w2c比起来还是太弱,所以我对参数进行了调整。demo中的参数迭代15次才到0.036cost,用了新的参数只要5次迭代就能达到0.035的cost。

其中200维向量,窗口20,迭代次数15次,线程8

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

虽然稍有提升但是语义准确度已经高w2v不少了,继续调参。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

下降的速度非常快。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

最终评测结果:

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

可以看到,性能有了明显的提升已经追上word2vec。实际上在2014年Jeffrey等提出的glove在,词分析任务上达到了75%的水平比word2vec要高11%。

但是yoav goleberg2015

(https://levyomer.files.wordpress.com/2015/03/improving-distributional-similarity-tacl-2015.pdf)这篇文章中指出,glove并不比word2vec好。

word2vec仍然是state-of-the-art的。然而在某些场景中glove的效果要比word2vec好,这个地方人们争论不休,我觉得讨论谁好目前对我们小白来说没必要,直接用,哪个效果好就用哪个。

来斯惟博士在它的博士论文附录部分,证明了 Skip-gram 模型和 GloVe 的 cost fucntion 本质上是一样的。是不是一个很有意思的结论? 所以在实际应用当中,这两者的差别并不算很大,尤其在很多 high-level 的 NLP 任务(如句子表示、命名体识别、文档表示)当中,经常把词向量作为原始输入,而到了 high-level 层面,差别就更小了。

5.其他粒度文本转化向量

5.1.paragraph2vec

无监督学习算法构成,可以是不同长度文本学习到向量表征,le and mikolov2014。已经有gensim(doc2vec)及fastext实现。

5.1.1.原理简析

5.1.1.1.doc2vec

之前的word2vec让我们看到了词的分布式表示表现和处理语义上的强大能力,在2014le and mikolov2014的论文上,扩展了这种方法,使得它能够表示句子、段落甚至是文档的分布式表示。是一种很有意思的方法,虽然mikolov2014上说对IMDB情感分类效果达到92.6%,但是在mikolov2015上的论文说因为试验方法不严谨导致结果失误,实际应该在87%左右。。。但也不妨碍这是一个很好的方法。

句子的向量表示一直都有人尝试做,如有人把句子里所有的词向量求平均,或者使用句法树的方式将词向量按照一定顺序结合起来。然而前者丢失了词顺序信息,后者不能扩展到句子以外的场景。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

由该结构图我们可以知道,其方法与w2v的方法唯一不同的地方就是将每一段唯一的paragraph id作为一个向量添加进去,对于同一段落中,不同窗口用的paragraph向量一样,不同段落不一样。

训练完成后,有两个matrix,一个是paragraph nxp(n是para的个数,p是维度),另一个是word mxq(m是字典尺寸,q是维度),两种向量可以average或concatenate。

pragaraph的范围放在句子,就是sent2vec;范围是段落就是paragraph2vec;范围是文章就是doc2vec。

5.1.1.2.fasttext

Enriching Word Vectors with Subword Information 

这篇论文提出了用 word n-gram 的向量之和来代替简单的词向量的方法,以解决简单 word2vec无法处理同一词的不同形态的问题。fastText 中提供了 maxn 这个参数来确定 word n-gram的 n 的大小。

Bag of Tricks for Efficient Text Classification 

这篇论文提出了 fastText 算法,该算法实际上是将目前用来算 word2vec 的网络架构做了个小修改,原先使用一个词的上下文的所有词向量之和来预测词本身(CBOW 模型),现在改为用一段短文本的词向量之和来对文本进行分类。

这两篇奠定了facebook的fasttext的基础。

fastText的架构和word2vec中的CBOW的架构类似,因为它们的作者都是Facebook的科学家Tomas Mikolov,而且确实fastText也算是words2vec所衍生出来的。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

fastText的模型架构类似于CBOW,两种模型都是基于Hierarchical Softmax,都是三层架构:输入层、 隐藏层、输出层。 

CBOW模型又基于N-gram模型和BOW模型,此模型将W(tN+1)……W(t−1)作为输入,去预测W(t) 。
fastText的模型则是将整个文本作为特征去预测文本的类别。

将输入层中的词和词组构成特征向量,再将特征向量通过线性变换映射到隐藏层,隐藏层通过求解最大似然函数,然后根据每个类别的权重和模型参数构建Huffman树,将Huffman树作为输出。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

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

正类别的概率:

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

负类别的概率:

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

  很明显,是不是觉得很熟悉?是的你没有看错,目前感觉和w2v没什么差别,就是预测变成了类别。

  在用n个训练样本进行训练时,根据每个类别出现的次数作为权重来建Huffman树,出现次数多的类别的样本,路径就短,出现次数少的类别的样本,路径就长。经过计算,单个样本在经过训练时所需的时间复杂度应该是 hlog2(k) 。 
  但fastText在预测时,计算复杂度仍然是O(kh),因为预测时计算量确实很小,所以这可能也不是fastText的初衷所在。

  在作者的paper中讲到,当类别的数量巨大时,计算线性分类器的计算量相当高,更准确的说,计算复杂度是 O(kh) ,k 是类别的数量,h 是文本特征的维度数。基于 Huffman 树的hierarchical softmax,可以将计算复杂度降到 O(hlog2(k)) 。

5.1.2.gensim(doc2vec)实战

Doc2vec是Mikolov在word2vec基础上提出的另一个用于计算长文本向量的工具。它的工作原理与word2vec极为相似——只是将长文本作为一个特殊的token id引入训练语料中。在Gensim中,doc2vec也是继承于word2vec的一个子类。因此,无论是API的参数接口还是调用文本向量的方式,doc2vec与word2vec都极为相似。

主要的区别是在对输入数据的预处理上。Doc2vec接受一个由LabeledSentence对象组成的迭代器作为其构造函数的输入参数。其中,LabeledSentence是Gensim内建的一个类,它接受两个List作为其初始化的参数:word list和label list。

from gensim.models.doc2vec import LabeledSentence

sentence = LabeledSentence(words=[u'some', u'words', u'here'], labels=[u'SENT_1'])

类似地,可以构造一个迭代器对象,将原始的训练数据文本转化成LabeledSentence对象:

class LabeledLineSentence(object):

    def __init__(self, filename):

        self.filename = filename

        

    def __iter__(self):

        for uid, line in enumerate(open(filename)):

#enumerate函数可以将列表、字符串转化成索引序列,可以同时得到索引和值。

            yield LabeledSentence(words=line.split(), labels=['SENT_%s' % uid])

#split不带参数时以空格进行分割,yield有点像return,是迭代的用法

准备好训练数据,模型的训练便只是一行命令:

from gensim.models import Doc2Vec

model = Doc2Vec(dm=1, size=100, window=5, negative=5, hs=0, min_count=2, workers=4)

该代码将同时训练word和sentence label的语义向量。如果我们只想训练label向量,可以传入参数 train_words=False 以固定词向量参数。更多参数的含义可以参见官网的API文档。

我们使用Cornell IMDB movie review corpus预料来做情感分析。该代码demo地址如下:

https://github.com/linanqiu/word2vec-sentiments

下图是训练预料pos的文本,从文本结构可以看到,和之前我用过的imdb里面分散开并且每一条评价以%num_%num命名保存的一样,只不过把所有的评价合并在了一起、进行了清洗、去除标点符号。并且保证了每一行只有一条评价,这个很重要。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

首先run生成模型。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

然后使用word2vec-sentiments.py进行语义分析(我对这个源码进行了更改,运行liu.py)。

源码如下:加载迭代了50次的模型。

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

结果如下:

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

仅仅只是用了线性SVM和一个很浅的神经网络,便将情感分析结果达到了87.4%的水平!

demo源码分析:

run.py

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

主要过程还是先对文本进行标注,然后生成字典,然后训练,最后保存模型。迭代次数是50次有点长。

喂养数据到doc2vec

doc2vec原作者提供了一个类样例LabelledLineSentence来实现LabeledSentence,这里就能看到doc2vec与word2vec的不同之处了。w2v只是将词转化成向量,d2v不仅仅这样做,而且还把一句话里所有的单词以一种集成的方式转化为向量,为了做到这个,他仅仅是简单的把句子标签当做一个特别的单词。所以我们要把句子格式化成:[[‘w1’,’w2’,’w3’],[‘label’]]

LabelledLineSentence很容易的实现了这个目标,我们不需要关心他是怎么做到的,偶们只需要知道他存储的东西是一个单词列表以及一个标签。然而这个样例只能一次处理一篇文档,对于我们的预料存在于多篇文档,这么用确实很麻烦,所以对LabelledLineSentence进行了一些修改。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

 

该类输入是一个字典,定义了要读取的文件列表以及对应文件的前缀标签。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

sources定义了一个字典键-值对,键key是文档名,值value是文档前缀标签。

doc2vec要求我们在训练之前建立词汇表,就是对预料出现过的单词进行统计。因为model 的build_vocab输入应该是一个数组,新定义的LabelledLineSentence有这样一个方法可以实现。

 

(二)词、句(段、文档)向量实战(word2vec&glove&dssm&graph2vec)

(微信也太恶心了。。。一篇文章能放的图片有限。。。所以未完。。。待续。。。。)