Word2Vec

Word2Vec

Word2Vec是常用的词嵌入模型之一。Word2Vec实际是一种浅层的神经网络模型,它有两种网络结构,分别是CBOW(Continues Bag of Words)连续词袋和Skip-gram。 我们可以选择其中一个方法来获得文本的词向量结果。

《word2vec Parameter Learning Explained》:https://arxiv.org/pdf/1411.2738.pdf


Skip-gram

Skip-gram是通过当前词来预测窗口中上下文词出现的概率模型。如果滑动窗口为n,那么就是将中心词的前n个词和后n个词作为样本带入进行训练,每次迭代训练2*n个样本。(通常是该词前k/2个词,该词后k/2个词,但从推导来看由于权重不同,对于窗口和词位置的关系并没有太大关系,前后词数不一定要对称)

例如:

句子:我 下午 准备 去 打 篮球

设置滑动窗口为2,那么滑动窗口为:

第一次输入:准备 ->来预测"我 下午 去 打",

第二次输入: 去 ->来预测"下午 准备 打 篮球"

每次训练4个样本

原理如下

其中:V为词的数量;D为设定的词向量的维度;W为词的中心词矩阵;w*为词的周围词矩阵。

softmax

p(oc)=eu0Tvcw=1VeuwTvcp(o|c)=\frac{{e^{u^T_{0}v_{c}}}}{\sum_{w=1}^Ve^{u^T_wv_{c}}}

其中p(oc)p(o|c)为通过c预测词为 o\ o的概率, vc,u0uw\ v_c,u_0,u_w分别为窗口内上下文词向量和中心词向量

损失函数

J(θ)=1Tt=1Tmj<m,j=0logp(wt+jwt)J(\theta)=-\frac{1}{T}\sum_{t=1}^T\sum_{-m\le j<\le m,j\not=0}logp(w_{t+j|w_t})


总体流程如下:

  • 随机生成中心词矩阵w和周围词矩阵w*
  • 对词wi生成ont hot向量,将向量与中心词矩阵W相乘,获得中心词向量
  • 中心词向量周围词矩阵w相乘,作为周围词向量*
  • 周围词向量经过softmax作为输出向量也就是所预测的每个词的概率
  • 接下来就是反向传播来调整W和W’这两个矩阵数据进行优化,使得目标词的概率最大,损失函数最小
  • 最后将中心词矩阵W作为词的最终词向量
在这里插入图片描述

CBOW

CBOW原理与Skip-gram相反,是通过设置滑动窗口获得中间词两边的的上下文,然后用周围的词去预测中间的词。

softmax

p(co)=euoTvcj=1VeuoTvjp(c|o)=\frac{{e^{u^T_{o}v_{c}}}}{\sum_{j=1}^Ve^{u^T_ov_{j}}}

其中p(co)p(c|o)为通过中性词向量o预测词为 c\ c的概率, vc,vj\ v_c,v_j为中心词向量, uo\ u_o为窗口内上下文向量

损失函数

J(θ)=1Tt=1TlogP(co)J(\theta)=-\frac{1}{T}\sum_{t=1}^T\sum logP(c|o)


  • 随机生成中心词矩阵w*和窗口上下文词矩阵w中的参数
  • 当前词的上下文词语的one-hot编码输入到输入层。
  • 这些上下文词分别乘以同一个窗口上下文词矩阵W后分别得到各自的1*D向量。
  • 将这些1*D向量相加取平均作为窗口上下文向量
  • 将这个1*D向量乘 中心词矩阵w*,作为中心词向量
  • 中心词向量softmax归一化后输出取每个词的概率向量1*V。
  • 接下来就是反向传播来调整中心矩阵W上下文词矩阵w*这两个矩阵数据进行优化,使得目标词的概率最大,损失函数最小
  • 最后将中心词矩阵W作为词的最终词向量

Word2Vec优化策略

论文提供了对Skip-gram模型两种优化的策略,分别是分层softmax和负样本

同时也可以运用在CBOW模型中

也告诉了我们在训练词向量的时候的关键超参数:choice of the model architecture, the size of the vectors, the subsampling rate and the size of the training window.(模型的选择,向量的维度,子采样率和滑动窗口的大小)

层次Softmax

层次softmax将softmax变为多个sigmod函数,进行多分类而减少softmax的计算,提升的效率。

σ(x)=11+ex\sigma(x)=\frac{1}{1+e^{-x}}

层次softmax首先利用树结构的叶节点表示词库中的每一个词,从根节点到叶节点的路径,每一个中间路径都对应着一个概率,某个词出现的概率的计算方式改为路径上所有中间路径概率的乘积

每一个中间路节点(上图中实心球)对应一个D*1的向量,然后上面得到的1*D的向量与中间路径向量相乘,得到一个数值z,z输入到sigmod函数得到一个0-1的数值,就是该节点到下一节点向左的概率,另外一侧概率是1-sigmod(z),这样就得到了中间路径的概率。最后某个词的概率就是从根节点到叶子节点中间路径概率的乘积。

同时层次softmax的目标函数也做出了改变为了最大化目标词的路径概率,就是只关注目标词所在路径的概率,其他路径不再关注,也无需计算。

具体

通过词出现在语料库中的频率进行排序,构建出哈夫曼树,频率越高则离根节点越近

输入词c而输出为i的概率为:

层次Softmax构建公式

(1)p(wwI)=j=1L(w)1σ([[n(w,j+1)=ch(n(w,j))]]vn(w,j)Tvwt)n(w,j)wj,L(w)ch(n(w,j))vwIvn(w,j)Twj[[x]]={1,x==True1,x==Falsep(w|w_I)=\prod_{j=1}^{L(w)-1}\sigma([[n(w,j+1)=ch(n(w,j))]]\cdot v'_n(w,j)^Tv_{wt})\\ n(w,j)表示词w在树的第j个节点,L(w)表示树的高度\\ ch(n(w,j))\\v_{wI}表示中心词向量\\ v'^T_{n(w,j)}表示词w在树的第j个节点的向量\\ [[x]]=\begin{cases} 1,\quad x==True\\ -1, \quad x==False \end{cases} \tag{1}

损失函数

J(θ)=logp(wwI)J(\theta)=-logp(w|w_I)

负采样

负采样是将层次softmax进一步优化,从多分类变为了二分类

负采样每次训练样本仅仅更新一小部分的权重,这样就会极大梯度下降过程中的计算量。

举个例子

训练样本 ( input word: "fox",output word: "quick")

训练我们的神经网络时,“ fox”和“quick”都是经过one-hot编码的。如果我们的语料库有10000个词,在输出层,我们期望对应“quick”单词的输出概率为1,其余9999个都应该输出0。在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们称为 "negative"word

再选择一小部分的negative words和目标词来更新对应的权重。

在论文中,作者指出:

对于小规模数据集,选择5-20个negative words会比较好,

对于大规模数据集可以仅选择2-5个negative words

如果隐层-输出层拥有300 x 10000的权重矩阵。如果使用了负采样的方法我们仅仅去更新我们的positive word-“quick”的和我们选择的其他5个negative words的结点对应的权重,共计6个输出神经元,相当于每次只更新300×6=1800个权重。对于3百万的权重来说,相当于只计算了0.06% 的权重,这样计算效率就大幅度提高。

具体

负样本抽取公式为

P(W)=U(w)34ZP(W)=\frac{U(w)^\frac{3}{4}}{Z}

就是对词w的出现频率进行3/4次方再进行归一化操作

举个例子:

U(a)=0.01,U(b)=0.99p(a)=0.013/40.013/4+0.993/4=0.03p(b)=0.993/40.013/4+0.993/4=0.97假设U(a)=0.01,U(b)=0.99\\ p(a)=\frac{0.01^{3/4}}{0.01^{3/4}+0.99^{3/4}}=0.03\\ p(b)=\frac{0.99^{3/4}}{0.01^{3/4}+0.99^{3/4}}=0.97\\

一个单词的出现频次低,则增加其作为负采样的概率,使单词越容易作为负样本。

一个单词的出现频率高,则减少其作为负采样的概率,使单词不容易作为负样本

因为一些重要的词出现的频率较少,但信息量很大;而不重要的词出现的频率高,信息量却少

损失函数

J(θ)=logσ(u0Tvc)k=1KEkp(w)[logσ(ukTvC)]vcu0ukJ(\theta)=-log\sigma(u_0^Tv_c)-\sum_{k=1}^KE_{k-p(w)}[log\sigma(-u_k^Tv_C)]\\ 其中v_c是中心词向量,u_0是周围词向量,u_k是负采样周围词向量

实现方式

gensim库

Gensim是在做自然语言处理时较为经常用到的一个工具库 它支持包括TF-IDF,LSA,LDA,和word2vec在内的多种主题模型算法。

对于word2vec模型,模型的输入是分完词的语料库,形式类似于list

class gensim.models.word2vec.Word2Vec(sentences=None, corpus_file=None, vector_size=100, alpha=0.025, window=5, min_count=5, max_vocab_size=None, sample=0.001, seed=1, workers=3, min_alpha=0.0001, sg=0, hs=0, negative=5, ns_exponent=0.75, cbow_mean=1, hashfxn=<built-in function hash>, epochs=5, null_word=0, trim_rule=None, sorted_vocab=1, batch_words=10000, compute_loss=False, callbacks=(), max_final_vocab=None)
  • sentences: 训练语料库,也可以通过LineSentence(分词后语料库)来读取。

  • vector_size: 生成词向量的维度,默认值是100。

  • window: 滑动窗口的大小。

  • sg: 即我们的word2vec两个模型的选择了。如果是0, 则是CBOW模型;是1则是Skip-Gram模型;默认是CBOW模型。

  • hs: 即我们的word2vec两个优化策略的选择。如果是0, 则是层次Softmax;是1的话并且负采样个数negative大于0, 则是负采样。默认是负采样。

  • negative: 即选取负采样词的个数,默认是5。

  • alpha: 在随机梯度下降法中迭代的初始步长。默认是0.025。

实例

from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence

def A():
    #首先打开需要进行训练的提前进行分词后的语料库
    shuju = open('./gushi.txt', 'rb')
    #通过Word2vec进行训练
     model = Word2Vec(LineSentence(shuju), vector_size=60, window=5,sg=1, hs=1,negative=5,alpha=0.025)
    #保存训练好的模型
    model.save('./SanGuoYanYiTest.word2vec')

    print('训练完成')

if __name__ == '__main__':
    A()
    model=Word2Vec.load('./SanGuoYanYiTest.word2vec')
    print(model.wv['张飞'])
E:\python310\python.exe E:\word2vec\run.py 
[ 0.40019155  0.15926215 -0.17247036  0.41934988  0.08891562 -0.3912016
  0.36244896 -0.13611287 -0.04305122 -0.05948525  0.6624021   0.19892757
 -0.26073712  0.18728831 -0.07860013 -1.0155997   0.30067584  0.19669221
 -0.29462233 -0.4473715   0.01844584  0.22937016  0.30038676  0.37411225
  0.56089747 -0.03240279 -0.06159927  0.07859133  0.30643627  0.18028829
 -0.19900236 -0.05217438  0.03655851 -0.7016526   0.11351124 -0.28412226
 -0.27930066 -0.51100993  0.10102482  0.00141967  0.04916065 -0.46889248
 -0.46942952 -0.15252821  0.13466035 -0.32120392  0.06884138  0.47682145
  0.22563507  0.4833642  -0.44313872 -0.05569039 -0.10548913  0.34712958
  0.30353147  0.14394882  0.07643879  0.3689174   0.4029759  -0.04832706]

进程已结束,退出代码0