本文目录一览:

python gensim怎么用word2vect

词向量(word2vec)原始的代码是C写的,python也有对应的版本,被集成在一个非常牛逼的框架gensim中。

我在自己的开源语义网络项目graph-mind(其实是我自己写的小玩具)中使用了这些功能,大家可以直接用我在上面做的进一步的封装傻瓜式地完成一些操作,下面分享调用方法和一些code上的心得。

1.一些类成员变量:

[python] view plain copy

def __init__(self, modelPath, _size=100, _window=5, _minCount=1, _workers=multiprocessing.cpu_count()):

self.modelPath = modelPath

self._size = _size

self._window = _window

self._minCount = _minCount

self._workers = _workers

modelPath是word2vec训练模型的磁盘存储文件(model在内存中总是不踏实),_size是词向量的维度,_window是词向量训练时的上下文扫描窗口大小,后面那个不知道,按默认来,_workers是训练的进程数(需要更精准的解释,请指正),默认是当前运行机器的处理器核数。这些参数先记住就可以了。

2.初始化并首次训练word2vec模型

完成这个功能的核心函数是initTrainWord2VecModel,传入两个参数:corpusFilePath和safe_model,分别代表训练语料的路径和是否选择“安全模式”进行初次训练。关于这个“安全模式”后面会讲,先看代码:

[python] view plain copy

def initTrainWord2VecModel(self, corpusFilePath, safe_model=False):

'''''

init and train a new w2v model

(corpusFilePath can be a path of corpus file or directory or a file directly, in some time it can be sentences directly

about soft_model:

if safe_model is true, the process of training uses update way to refresh model,

and this can keep the usage of os's memory safe but slowly.

and if safe_model is false, the process of training uses the way that load all

corpus lines into a sentences list and train them one time.)

'''

extraSegOpt().reLoadEncoding()

fileType = localFileOptUnit.checkFileState(corpusFilePath)

if fileType == u'error':

warnings.warn('load file error!')

return None

else:

model = None

if fileType == u'opened':

print('training model from singleFile!')

model = Word2Vec(LineSentence(corpusFilePath), size=self._size, window=self._window, min_count=self._minCount, workers=self._workers)

elif fileType == u'file':

corpusFile = open(corpusFilePath, u'r')

print('training model from singleFile!')

model = Word2Vec(LineSentence(corpusFile), size=self._size, window=self._window, min_count=self._minCount, workers=self._workers)

elif fileType == u'directory':

corpusFiles = localFileOptUnit.listAllFileInDirectory(corpusFilePath)

print('training model from listFiles of directory!')

if safe_model == True:

model = Word2Vec(LineSentence(corpusFiles[0]), size=self._size, window=self._window, min_count=self._minCount, workers=self._workers)

for file in corpusFiles[1:len(corpusFiles)]:

model = self.updateW2VModelUnit(model, file)

else:

sentences = self.loadSetencesFromFiles(corpusFiles)

model = Word2Vec(sentences, size=self._size, window=self._window, min_count=self._minCount, workers=self._workers)

elif fileType == u'other':

# TODO add sentences list directly

pass

model.save(self.modelPath)

model.init_sims()

print('producing word2vec model ... ok!')

return model

首先是一些杂七杂八的,判断一下输入文件路径下访问结果的类型,根据不同的类型做出不同的文件处理反应,这个大家应该能看懂,以corpusFilePath为一个已经打开的file对象为例,创建word2vec model的代码为:

[python] view plain copy

model = Word2Vec(LineSentence(corpusFilePath), size=self._size, window=self._window, min_count=self._minCount, workers=self._workers)

其实就是这么简单,但是为了代码健壮一些,就变成了上面那么长。问题是在面对一个路径下的许多训练文档且数目巨大的时候,一次性载入内存可能不太靠谱了(没有细研究gensim在Word2Vec构造方法中有没有考虑这个问题,只是一种习惯性的警惕),于是我设定了一个参数safe_model用于判断初始训练是否开启“安全模式”,所谓安全模式,就是最初只载入一篇语料的内容,后面的初始训练文档通过增量式学习的方式,更新到原先的model中。

上面的代码里,corpusFilePath可以传入一个已经打开的file对象,或是一个单个文件的地址,或一个文件夹的路径,通过函数checkFileState已经做了类型的判断。另外一个函数是updateW2VModelUnit,用于增量式训练更新w2v的model,下面会具体介绍。loadSetencesFromFiles函数用于载入一个文件夹中全部语料的所有句子,这个在源代码里有,很简单,哥就不多说了。

3.增量式训练更新word2vec模型

增量式训练w2v模型,上面提到了一个这么做的原因:避免把全部的训练语料一次性载入到内存中。另一个原因是为了应对语料随时增加的情况。gensim当然给出了这样的solution,调用如下:

[python] view plain copy

def updateW2VModelUnit(self, model, corpusSingleFilePath):

'''''

(only can be a singleFile)

'''

fileType = localFileOptUnit.checkFileState(corpusSingleFilePath)

if fileType == u'directory':

warnings.warn('can not deal a directory!')

return model

if fileType == u'opened':

trainedWordCount = model.train(LineSentence(corpusSingleFilePath))

print('update model, update words num is: ' + trainedWordCount)

elif fileType == u'file':

corpusSingleFile = open(corpusSingleFilePath, u'r')

trainedWordCount = model.train(LineSentence(corpusSingleFile))

print('update model, update words num is: ' + trainedWordCount)

else:

# TODO add sentences list directly (same as last function)

pass

return model

简单检查文件type之后,调用model对象的train方法就可以实现对model的更新,这个方法传入的是新语料的sentences,会返回模型中新增词汇的数量。函数全部执行完后,return更新后的model,源代码中在这个函数下面有能够处理多类文件参数(同2)的增强方法,这里就不多介绍了。

4.各种基础查询

当你确定model已经训练完成,不会再更新的时候,可以对model进行锁定,并且据说是预载了相似度矩阵能够提高后面的查询速度,但是你的model从此以后就read only了。

[python] view plain copy

def finishTrainModel(self, modelFilePath=None):

'''''

warning: after this, the model is read-only (can't be update)

'''

if modelFilePath == None:

modelFilePath = self.modelPath

model = self.loadModelfromFile(modelFilePath)

model.init_sims(replace=True)

可以看到,所谓的锁定模型方法,就是init_sims,并且把里面的replace参数设定为True。

然后是一些word2vec模型的查询方法:

[python] view plain copy

def getWordVec(self, model, wordStr):

'''''

get the word's vector as arrayList type from w2v model

'''

return model[wordStr]

[python] view plain copy

def queryMostSimilarWordVec(self, model, wordStr, topN=20):

'''''

MSimilar words basic query function

return 2-dim List [0] is word [1] is double-prob

'''

similarPairList = model.most_similar(wordStr.decode('utf-8'), topn=topN)

return similarPairList

[python] view plain copy

def culSimBtwWordVecs(self, model, wordStr1, wordStr2):

'''''

two words similar basic query function

return double-prob

'''

similarValue = model.similarity(wordStr1.decode('utf-8'), wordStr2.decode('utf-8'))

return similarValue

上述方法都很简单,基本上一行解决,在源代码中,各个函数下面依然是配套了相应的model文件处理版的函数。其中,getWordVec是得到查询词的word2vec词向量本身,打印出来是一个纯数字的array;queryMostSimilarWordVec是得到与查询词关联度最高的N个词以及对应的相似度,返回是一个二维list(注释里面写的蛮清楚);culSimBtwWordVecs是得到两个给定词的相似度值,直接返回double值。

5.Word2Vec词向量的计算

研究过w2v理论的童鞋肯定知道词向量是可以做加减计算的,基于这个性质,gensim给出了相应的方法,调用如下:

[python] view plain copy

def queryMSimilarVecswithPosNeg(self, model, posWordStrList, negWordStrList, topN=20):

'''''

pos-neg MSimilar words basic query function

return 2-dim List [0] is word [1] is double-prob

'''

posWordList = []

negWordList = []

for wordStr in posWordStrList:

posWordList.append(wordStr.decode('utf-8'))

for wordStr in negWordStrList:

negWordList.append(wordStr.decode('utf-8'))

pnSimilarPairList = model.most_similar(positive=posWordList, negative=negWordList, topn=topN)

return pnSimilarPairList

由于用的是py27,所以之前对传入的词列表数据进行编码过滤,这里面posWordList可以认为是对结果产生正能量的词集,negWordList则是对结果产生负能量的词集,同时送入most_similar方法,在设定return答案的topN,得到的返回结果形式同4中的queryMostSimilarWordVec函数,大家可以这样数学地理解这个操作:

下面一个操作是我自创的,假设我想用上面词向量topN“词-关联度”的形式展现两个词或两组词之间的关联,我是这么做的:

[python] view plain copy

def copeMSimilarVecsbtwWordLists(self, model, wordStrList1, wordStrList2, topN_rev=20, topN=20):

'''''

range word vec res for two wordList from source to target

use wordVector to express the relationship between src-wordList and tag-wordList

first, use the tag-wordList as neg-wordList to get the rev-wordList,

then use the scr-wordList and the rev-wordList as the new src-tag-wordList

topN_rev is topN of rev-wordList and topN is the final topN of relationship vec

'''

srcWordList = []

tagWordList = []

srcWordList.extend(wordStr.decode('utf-8') for wordStr in wordStrList1)

tagWordList.extend(wordStr.decode('utf-8') for wordStr in wordStrList2)

revSimilarPairList = self.queryMSimilarVecswithPosNeg(model, [], tagWordList, topN_rev)

revWordList = []

revWordList.extend(pair[0].decode('utf-8') for pair in revSimilarPairList)

stSimilarPairList = self.queryMSimilarVecswithPosNeg(model, srcWordList, revWordList, topN)

return stSimilarPairList

这个操作的思路就是,首先用两组词中的一组作为negWordList,传入上面的queryMSimilarVecswithPosNeg函数,得到topN一组的中转词,在使用这些中转词与原先的另一组词进行queryMSimilarVecswithPosNeg操作,很容易理解,第一步得到的是一组词作为negWordList的反向结果,再通过这个反向结果与另一组词得到“负负得正”的效果。这样就可以通过一组topN的“词-关联度”配对List表示两组词之间的关系。

word2vec概述

文本表示是自然语言处理中的基础工作,文本表示的好坏直接影响到整个自然语言处理系统的性能。文本向量化是文本表示的一种重要方式。文本向量化就是讲文本表示成一系列能够表达文本语义的向量。

当前阶段,对文本向量化的大部分研究都是通过词向量化来实现的。与此同时,也有相当一部分研究者将文章或者句子作为文本基本处理单元,提出了doc2vec和ste2vec技术。

基于embedding的词表示,其核心思想是: 上下文相似的词,其语义也相似。 这就是著名的 词空间模型(word space model) ,词向量通常使用神经网络模型训练得到,神经网络模型就是根据上下文与目标词之间的关系进行建模。

Word2vec主要有CBOW和Skip-gram两种模式,其中CBOW是从原始语句推测目标字词,而Skip-gram是从目标字词推测出原始语句(滑动窗口范围内),其中CBOW对小型数据比较合适,Skip-fram在大型语料中表现得更好。

接下来介绍两种优化方法

在木有进行优化前,word2vec的隐藏层有 V*N 个参数,其中V是全局词的数量,比如10W个,N是预设的词嵌入向量维度,如300,那么这个计算量太大了,因此要进行优化。

先复习一下 霍夫曼树 ,这里我图省事,直接把 刘建平 老师的博客贴上来。

回顾下传统的神经网络词向量语言模型,里面一般有三层,输入层(词向量),隐藏层和输出层(softmax层)。里面最大的问题在于从隐藏层到输出的softmax层的计算量很大,因为要计算所有词的softmax概率,再去找概率最大的值。这个模型如下图所示。其中V是词汇表的大小。

 由于我们把之前所有都要计算的从输出softmax层的概率计算变成了一颗二叉霍夫曼树,那么我们的softmax概率计算只需要沿着树形结构进行就可以了。如下图所示,我们可以沿着霍夫曼树从根节点一直走到我们的叶子节点的词 w2 。

 如何“沿着霍夫曼树一步步完成”呢?在word2vec中,我们采用了二元逻辑回归的方法,即规定沿着左子树走,那么就是负类(霍夫曼树编码1),沿着右子树走,那么就是正类(霍夫曼树编码0)。判别正类和负类的方法是使用sigmoid函数,即:

      

 其中 xw 是当前内部节点的词向量,而θ则是我们需要从训练样本求出的逻辑回归的模型参数。

 使用霍夫曼树有什么好处呢?首先,由于是二叉树,之前计算量为V,现在变成了log2V。第二,由于使用霍夫曼树是高频的词靠近树根,这样高频词需要更少的时间会被找到,这符合我们的贪心优化思想。

 容易理解,被划分为左子树而成为负类的概率为 P(−)=1−P(+) 。在某一个内部节点,要判断是沿左子树还是右子树走的标准就是看 P(−),P(+) 谁的概率值大。而控制 P(−),P(+) 谁的概率值大的因素一个是当前节点的词向量,另一个是当前节点的模型参数 θ 。

 对于上图中的 w2 ,如果它是一个训练样本的输出,那么我们期望对于里面的隐藏节点 n(w2,1) 的 P(−) 概率大, n(w2,2) 的 P(−) 概率大, n(w2,3) 的 P(+) 概率大。

 回到基于Hierarchical Softmax的word2vec本身,我们的目标就是找到合适的所有节点的词向量和所有内部节点 θ , 使训练样本达到最大似然。那么如何达到最大似然呢?

在讲基于Negative Sampling的word2vec模型前,我们先看看Hierarchical Softmax的的缺点。的确,使用霍夫曼树来代替传统的神经网络,可以提高模型训练的效率。但是如果我们的训练样本里的中心词w是一个很生僻的词,那么就得在霍夫曼树中辛苦的向下走很久了。能不能不用搞这么复杂的一颗霍夫曼树,将模型变的更加简单呢?

Negative Sampling就是这么一种求解word2vec模型的方法,它摒弃了霍夫曼树,采用了Negative Sampling(负采样)的方法来求解,下面我们就来看看Negative Sampling的求解思路。

既然名字叫Negative Sampling(负采样),那么肯定使用了采样的方法。采样的方法有很多种,比如之前讲到的大名鼎鼎的MCMC。我们这里的Negative Sampling采样方法并没有MCMC那么复杂。

比如我们有一个训练样本,中心词是 w ,它周围上下文共有 2c 个词,记为 context(w) 。由于这个中心词 w ,的确和 context(w) 相关存在,因此它是一个真实的正例。通过 Negative Sampling 采样,我们得到 neg 个和 w 不同的中心词 wi,i=1,2,..neg ,这样 context(w) 和 wi 就组成了 neg 个并不真实存在的负例。利用这一个正例和 neg 个负例,我们进行二元逻辑回归,得到负采样对应每个词 wi 对应的模型参数 θi ,和每个词的词向量。

从上面的描述可以看出, Negative Sampling 由于没有采用霍夫曼树,每次只是通过采样 neg 个不同的中心词做负例,就可以训练模型,因此整个过程要比 Hierarchical Softmax 简单。

不过有两个问题还需要弄明白:1)如何通过一个正例和neg个负例进行二元逻辑回归呢? 2) 如何进行负采样呢?

一些细节:

在skip gram和CBOW中,中心词词向量在迭代过程中是不会更新的,只更新窗口词向量,这个中心词对应的词向量需要下一次在作为非中心词的时候才能进行迭代更新。

参考文章

1.刘建平老师的博客园, 地址

2. bitcarmanlee的CSDN博客

Word2vec原理详细解读

Softmax函数:

哈夫曼树(Huffman Tree)

     从图1可以看出Skip-gram就是用当前中心词 (banking)预测附近的词,图1中将窗口大小设为2,即需要预测左边的2个词和右边的2个词。

     对于每个位置 ,预测窗口大小为 的上下文,设当前中心词为 ,那么目标为最大化:

                                                                               (1)

其中 为模型的参数。

     为了将最大化转为最小化,可对 取负数,为了简化计算,可取对数:

                               (2)  

      现在问题的关键是如何计算 ,我们使用两个向量表示: 为中心词的表示, 为上下文词的表示。那么,计算中心词 c 和上下文词 o 的出现概率为:

                                                                                       (3)

其中,V为整个词表大小, 为中心词向量表示。其实式3就是softmax函数。

      图2展示了Skip-gram的计算过程,从图中可以看出Skip-gram预测的是 , , ,由于只预测前后两个单词,因此窗口大小为2。

输入层到隐藏层 :输入层的中心词 用one-hot向量表示(维度为V*1,V为整个词表大小),输入层到隐藏层的权重矩阵为中心词矩阵W(维度为V*d,d为词向量维度),设隐含向量为 (维度为d*1),那么:

                                                                                                             (4)

隐藏层到输出层: 隐藏层到输出层的上下文权重矩阵为U(维度为d*V),输出层为y(维度为V*1),那么:

                                                                                          (5)

注意 ,输出层的向量 y 与输出层的向量 虽然维度一样,但是 y 并不是one-hot向量,并且向量 y 的每一个元素都是有意义的。如,假设训练样本只有一句话”I like to eat apple”,此时我们正在使用eat去预测to,输出层结果如图3所示。

     向量y中的每个元素表示用 I、like、eat、apple 四个词预测出来的词是对应的词的概率,比如是like的概率为0.05,是to的概率是0.80。由于我们想让模型预测出来的词是to,那么我们就要尽量让to的概率尽可能的大,所以我们将式子(1)作为最大化函数。

Continuous Bag-of-Words(CBOW),的计算示意图如图4所示。从图中可以看出,CBOW模型预测的是 ,由于目标词 只取前后的两个词,因此窗口大小为2。假设目标词 前后各取 个词,即窗口大小为 ,那么CBOW模型为:

                         (6)

输入层到隐藏层: 如图4所示,输入层为4个词的one-hot向量表示,分别为 , , , (维度都为V*1,V为整个词表大小),记输入层到隐藏层的上下文词的权重矩阵为W(维度为V*d,d是词向量维度),隐藏层的向量h(维度为d*1),那么:

                                          (7)

这里就是把各个上下文词的向量查找出来,再进行简单的加和平均。

隐藏层到输出层: 记隐藏层到输出层的中心词权重矩阵为U(维度d*V),输出层的向量y(维度V*1),那么:

                                                                                                    (8)

注意 ,输出层的向量 与输入层的 虽然维度一样,但是 并不是one-hot向量,并且向量 的每个元素都是有意义的。CBOW的目标是最大化函数:

     (9)

由于softmax的分母部分计算代价很大,在实际应用时,一般采用层次softmax或者负采样替换掉输出层,降低计算复杂度。

层次softmax(Hierarchical Softmax)是一棵哈夫曼树,树的叶子节点是训练文本中所有的词,非叶子节点是一个逻辑回归二分类器,每个逻辑回归分类器的参数都不同,分别用 表示,假定分类器的输入为向量h,记逻辑回归分类器输出的结果为 将向量h传递给节点的左孩子概率为 ,否则传递给节点的右孩子概率为 。重复这个传递流程直到叶子节点。

从图5和图6可以看出,我们就是将隐藏层的向量h直接传递到了层次softmax,层次softmax的复杂度为O(log(V)),层次softmax采样到每个词的概率如下:

对于CBOW或者skip-gram模型,如果要预测的词是to,那么我们就让 尽量大,所以我们将任务转换成训练V-1个逻辑分类器。CBOW模型和skip-gram模型训练目标函数和之前形式一样,为:

                                                             (10)

  (11)

负采样实际上是采样负例来帮助训练的手段,其目的与层次softmax一样,是用来提升模型的训练速度。我们知道,模型对正例的预测概率是越大越好,模型对负例的预测概率是越小越好。负采样的思路就是根据某种负采样的策略随机挑选一些负例,然后保证挑选的这部分负例的预测概率尽可能小。所以,负采样策略是对模型的效果影响很大,word2vec常用的负采样策略有均匀负采样、按词频率采样等等。

以“I like to eat apple”为例子,假设窗口的大小是2,当中心词为like时,即我们会用 I to 来预测like,所以在这里我们就认为(I,like)和(to,like)都是正例,而(I,apple)、(to,apple)就是负例,因为(I,apple)、(to,apple)不出现在当前正例中。用NEG(w)表示负样本,有:

                                                                   (12)

                (13)

这里的 是词*的中心词向量表示,h为隐藏层的输出向量。我们只需要最大化目标函数:

                      (14)

这个损失函数的含义就是让正例概率更大,负例的概率更小。

以“I like to eat apple”为例子,假设窗口的大小是1,即我们会用 like 来预测 I to,所以在这里我们就认为(like,I)和(like,to)都是正例,而(like,apple)就是负例,因为(like,apple)不会出现在正例中。那么,对于给定的正样本(w,context(w))和采样出的负样本(w,NEG(w)),有:

                                                                                  (15)

                                                        (16)

这里的 是词*的中心词向量表示,h为隐藏层的输出向量。我们只需要最大化目标函数:

                                         (17)

word2vec常用的负采样策略有均匀负采样、按词频率采样等等。比较常用的采样方法是一元分布模型的3/4次幂。该方法中,一个词被采样的概率,取决于这个词在语料中的词频 ,其满足一元分布模型(Unigram Model).

                                                                                         (18) 

其中V为整个词表大小,    为词 的词频。

至于为什么选择3/4呢?其实是由论文作者的经验所决定的。

假设由三个词,,”我“,”和平“,”觊觎“ 权重分别为 0.9 ,0.01,0.003;经过3/4幂后:

我: 0.9^3/4 = 0.92

和平:0.01^3/4 = 0.03

觊觎:0.003^3/4 = 0.012

对于”觊觎“而言,权重增加了4倍;”和平“增加3倍;”我“只有轻微增加。

可以认为:在保证高频词容易被抽到的大方向下,通过权重3/4次幂的方式, 适当提升低频词、罕见词被抽到的概率 。如果不这么做,低频词,罕见词很难被抽到,以至于不被更新到对应的Embedding。

QuestionAnswer

Question1:  如图7中,skip-gram模型中,从隐藏层到输出层,因为使用权值共享,所以会导致输出的几个上下文词向量总是完全一样,但网络的目的是要去预测上下文会出现的词,而实际中给定中心词的情况下上下文的词会五花八门。怎么解释skip gram的这种输出形式?

Answer1: 网络的目的不是要预测上下文会出现啥词,这只是一个fake task。实际上这个loss就是降不下来的,所以本来就不能用于真正预测上下文,而初衷也不是用于预测上下文,只是利用上下文信息去实现嵌入。

       如果你的100W个句子都是”I really love machine learning and deep learning“,加上权值共享,结果就是给定machine以后,它输出really,love,learning,and这四个词的概率完全相同,这就意味着这四个词的词向量也是差不多的。正因为这样,语义相近的词,他们在空间上的映射才会接近。

Question2 : Word2Vec哪个矩阵是词向量?

Answer2: 如图7所示,中心词矩阵W,上下文矩阵W' 可以任意选一个作为词向量矩阵。但是,如果采用优化后(层次softmax)的模型,那么将不存在W',这种情况下只能选矩阵W。

三千多字,码字不易,如果大家发现我有地方写得不对或者有疑问的,麻烦评论, 我会回复并改正 。对于重要问题,我会持续更新至 QuestionAnswer。

参考:

[1] skip-gram的关键术语与详细解释

[2] 一篇浅显易懂的word2vec原理讲解

[3] CS224n:深度学习的自然语言处理(2017年冬季)1080p

[4] Stanford  CS224N: NLP with Deep Learning | Winter 2019 | Lecture 2 – Word Vectors and

Word Senses

[5] 关于skip gram的输出?

[6] Le, Quoc V , and T. Mikolov . "Distributed Representationsof Sentences and Documents." (2014).

[7] Mikolov, T. . "Distributed Representations of Words andPhrases and their Compositionality." Advances in Neural InformationProcessing Systems 26(2013):3111-3119.

[8] Mikolov, Tomas , et al."Efficient Estimation of Word Representations in Vector Space." Computerence (2013).

[9] Goldberg, Yoav , and O. Levy . "word2vec Explained:deriving Mikolov et al.'s negative-sampling word-embedding method." arXiv(2014).

第二篇: 词向量之Spark word2vector实战

word2vector 是google开源的一个生成词向量的工具,以语言模型为优化目标,迭代更新训练文本中的词向量,最终收敛获得词向量。词向量可以作为文本分析中重要的特征,在分类问题、标注问题等场景都有着重要的应用价值。本文总结下了spark word2vector使用过程中遇到的问题,以及给出word2vector使用所需的参数配置,希望能够减少新手在使用过程中遇到的坑,希望有所帮助。

from pyspark.ml.feature import Word2Vec

from pyspark.sql import SQLContext

from pyspark import SparkConf, SparkContext

from pyspark.sql import Row

conf = (SparkConf().set("spark.driver.maxResultSize","2g"))

sc = SparkContext(conf=conf)

sqlContext = SQLContext(sc)

text = sc.textFile("yourfilepath")

documentDF = text.map(lambda x : Row(text=x.split(" "))).toDF()

word2Vec = Word2Vec(vectorSize=200, minCount=5, numPartitions=100,inputCol="text", outputCol="result")

model = word2Vec.fit(documentDF)

vector_model = model.getVectors()

vector_model.saveAsParquetFile("modelpath")

spark-submit

--master yarn-client

--executor-cores 2

--executor-memory 14g

--queue your-queue

--num-executors 100

--driver-memory 10g

--conf spark.ui.port=$RANDOM

--conf spark.shuffle.manager=SORT

--conf spark.shuffle.memoryFraction=0.2

--conf spark.yarn.executor.memoryOverhead=2048

--conf spark.core.connection.ack.wait.timeout=300

--conf spark.akka.frameSize=600 ./word2vector_training.py

word2vec目标函数的参数问题?输入的向量为什么是参数

model=Word2Vec(setsomeparams)model.build_vocab(sentences)model.train(sentences)model.save(fielname)

word2vec 之 skip-gram

Word2vec 主要有两种形式,CBOW 和Skip-gram,其中CBOW是通过上下文context来预测当前位置词,SKip-gram则是通过当前词来预测上下文

Fake Task

word2vec 实际上分为两部分,1,建立模型,2,通过模型获取词的嵌入向量(隐层参数)。整个过程与自编码器的思想类似,即基于训练数据训练一个神经网络,模型训练好后,并不会用这个模型处理后续的任务,真正需要的是这个模型学到的参数,如隐层的权重矩阵,基于训练数据建模的过程叫“Fake Task”,意味着建模并不是我们最终的目的。

Train

如何训练我们的神经网络模型?假如我们有一个句子“ The dog barked at the mailman”。

首先,我们选择句子中一个词作为我们的input word, 如 dog

然后,我们需要定义一个skip_window参数,来指定上下文的大小,即input word 一侧选取词的数量,假如skip_window=2,那将从dog出发向左右两个方向取最近的两个word,即(the, dog,barked,at),此时的span = skip_window * 2 + 1 = 5

另一个需要定义的参数是num_skips,即从上下文中选取多少个word来作为output word,这个参数应该小于等于2 * skip_window,即最多将所有上下文都作为output,但是不能重复。如设置num_skips = 2,此时从上下文选取2个词作为output,如(the, barked),最终我们将得到两组训练数据(dog, the) (dog, barked)

神经网络将基于这些训练数据输出一个概率分布,这个概率分布代表着在输入数据下,词典中每个词是output的概率。如拿数据(dog, barked)来训练,则模型将会告诉我们每个单词是’barked’的概率大小。

模型的输出概率代表着词典中每个单词有多大可能性跟input word同时出现。举个栗子,如果我们向神经网络模型中输入一个单词“Soviet“,那么最终模型的输出概率中,像“Union”, ”Russia“这种相关词的概率将远高于像”watermelon“,”kangaroo“非相关词的概率。因为”Union“,”Russia“在文本中更大可能在”Soviet“的窗口中出现。

我们将通过给神经网络输入文本中成对的单词来训练它完成上面所说的概率计算。下面的图中给出了一些我们的训练样本的例子。我们选定句子“The quick brown fox jumps over lazy dog”,设定我们的窗口大小为2(window_size = 2),也就是说我们仅选输入词前后各两个词和输入词进行组合。下图中,蓝色代表input word,方框内代表位于窗口内的单词。

模型将会从每队单词出现的次数中习得统计结果,模型可能会得到更多的(’soviet’, ‘union’)样本对,而(soviet, dog)这样的组合看到的很少。因此,当模型训练完成后,给定一个单词 soviet,输出结果中union 或者russia会比dog有更高的概率。

输入

常用做法是用训练文档构建词汇表,然后再对单词进行0ne-hot编码。

编码后的向量,形如dog = [0, 0, 1, 0, …0], 如果词汇表大小为10000, 那这个向量包含了10000的概率,即为当前词为输入的概率

下图是神经网络结构:

我们基于成对的单词来对神经网络进行训练, 训练样本是(input word, output word)这样的单词对,input word 和 output word都是one-hot编码的向量,最终的模型输出是一个概率分布。

隐层

如果我们想要用300个特征来表示一个词(即每个词是300维的向量),即隐层有300个神经元,隐层的权重为10000 * 300的矩阵,下图中的左右两个图代表了不同角度看隐层权重,左图中每列代表一个10000维的词向量与隐层单个神经元的连接权重,右图每行代表了一个单词的词向量。

我们最终的目标就是学习这个隐层权重矩阵。

输入被one-hot编码后,实际上只有一个位置不为0,所以这个向量相当稀疏,那如果我们将1 10000的向量与10000 300的矩阵相乘,相当消耗计算资源,为了高效计算,仅仅会选择矩阵中对应的向量中纬度为1的索引行

即实际不会进行矩阵乘法计算,而是根据输入向量中不为0 的维度去索引。这样模型中的隐层权重矩阵便成了一个查找表(lookup table),输出就是输入单词的嵌入词向量

输出层

隐层的输出是一个1*300的向量,而输出层是一个softmax回归分类器,他的每个结点将会输出一个0-1之间的值(概率),而结点的概率之和为1.

我们会发现Word2Vec模型是一个超级大的神经网络(权重矩阵规模非常大)。

举个栗子,我们拥有10000个单词的词汇表,我们如果想嵌入300维的词向量,那么我们的输入-隐层权重矩阵和隐层-输出层的权重矩阵都会有 10000 x 300 = 300万个权重,在如此庞大的神经网络中进行梯度下降是相当慢的。更糟糕的是,你需要大量的训练数据来调整这些权重并且避免过拟合。百万数量级的权重矩阵和亿万数量级的训练样本意味着训练这个模型将会是个灾难(太凶残了)。

Word2Vec的作者在它的第二篇论文中强调了这些问题,下面是作者在第二篇论文中的三个创新:

事实证明,对常用词抽样并且对优化目标采用“negative sampling”不仅降低了训练过程中的计算负担,还提高了训练的词向量的质量。

word pairs and phases

一些单词组合的含义和拆开以后具有完全不同的意义,比如 New York,单独的New 和York无法表达这个词组的含义。因此,应该把New York作为一个单独的词组来生成其词向量。

对高频词抽样

对于高频词,如 the ,按上面的处理方式会有两个问题:

如果直接删除掉这些高频词,会有两个问题

1.删除后,the这个单词永远也不会出现在我们的上下文窗口

2.训练样本会减少

所以word2vec 采用抽样的方式来解决这种高频词问题。他的基本思想是:对于我们在训练原始文本中遇到的每一个单词,他们都有一定概率被我们从文本中删除掉,而这个被删除的概率与单词的频率有关。

wi 是一个单词,Z(wi)是这个单词在所有预料中出现的频次。P(wi)是被保留的概率。

负采样

训练一个神经网络意味着要输入训练样本并且不断的调整神经元的权重,不断提高对目标的准确预测。而vocabulary的大小决定了skip-gram神经网络将拥有大规模的权重矩阵,所有的这些权重需要通过我们数以亿计的样本来训练调整,非常消耗计算资源,并且实际中会非常慢。

负采样解决了这个问题,不同于原本每个训练样本更新所有权重,负采样每次让一个训练样本仅仅更新一部分权重,减小计算量。

对于训练样本(fox,quick),都是经过one-hot编码的,当vocabulary的大小为10000时,我们期望输出对应的quick单词的那个神经元的输出是1,其余9999个都是0,这9999个输出为0的神经元所对应的单词称为negative word

隐层-输出层拥有300 10000的权重,而负采样时,我们仅仅更新quick 和我们选择的其他5个negative word的结点对应的权重,共6个神经元,300 6 = 1800 个权重,相当于只计算了0.06%的权重,计算效率大大提高。

其中f(wi)代表每个单词出现的频次,p(wi)代表被选中的概率。

负采样的C语言实现非常的有趣。unigram table有一个包含了一亿个元素的数组,这个数组是由词汇表中每个单词的索引号填充的,并且这个数组中有重复,也就是说有些单词会出现多次。那么每个单词的索引在这个数组中出现的次数该如何决定呢,由公式P(wi) * table_size,也就是说计算出的负采样概率*1亿=单词在表中出现的次数。

有了这张表以后,每次去我们进行负采样时,只需要在0-1亿范围内生成一个随机数,然后选择表中索引号为这个随机数的那个单词作为我们的negative word即可。一个单词的负采样概率越大,那么它在这个表中出现的次数就越多,它被选中的概率就越大。