NLP --- >word2vec

概述

Word2vec在NLP里占有非常重要的地位,是很多深度学习技术的基础,那它解决了什么问题?是什么?为什么能解决呢?下面详细地介绍各个部分。

Word2vec解决什么问题?
我们知道,在一般的机器学习中,数据分为两大类,一类是数值型,另一类是字符型数据:对于数值型数据,能够有效地处理,可以直接用于数据拟合,回归分析中;对于字符型数据,如常见的性别(男,女),省份等,我们会把他们one-hot编码,这样由原来的一列数据增加若干列数据,当我们的一列数据具有大量不同的值时,扩增的列也将大幅增加,这在机器学习中会极大影响模型的学习,降低学习的效果。

而在NLP中,我们的数据全部是字符型数据,one-hot编码根本不现实!当然,也可以这样做,我们得到每个词的维度将是词汇表的大小,至少数万级别。

为此,我们需要找到其他的方法,将每个词的表示降维,如果从one-hot的结果出发,我们利用PCA, SGD等方法也可以降维,但代价是巨大的,不仅消耗大量计算资源,而且可扩展性差,上述降维技术要求把所有的数据一次性输入,若我们增加一些数据,则需要重新训练。

Word2vec怎么解决的
泛化地说:word2vec得到每个词的词向量并不是一步完成的,首先为每个词初始化一个随机向量,每次训练一个滑动窗口内的数据(一般是5个词),将滑动窗口内某个(些)词作为训练数据,其他词作为预测数据完成一次训练,更新词的向量参数,这样经过多次迭代后,每个词的向量会达到稳定,完成词向量的学习。

具体地说:

Word2vec分为两个模型:CBOW(Continuous Bag-of-Words Model) 模型和 Skip-gram模型,它们的区别如图:
在这里插入图片描述

CBOW模型

首先介绍CBOW模型,上图给出了模型的结构,包括三层:输入层,投影层和输出层;每次的输入和输出都是在一个滑动窗口内。举例而言, w w w为一个词, C o n t e x t c ( w ) Context_c(w) Contextc(w)为词前后长度为c的所有词,这2c+1个词组成一个滑动窗口;

  • 输入层:输入内容为中心词 w w w外的所有词,具体内容是把每个词转化为表示为one-hot编码,此时每个词的维度为词汇表的大小。
  • 投影层:输入层为 2 c ⋅ V 2c \cdot V 2cV的维度,输入到投影层有一个参数矩阵 V ⋅ m V\cdot m Vm(其中m为我们预设的词向量维度) ,经计算得到 2 c ⋅ m 2c \cdot m 2cm的矩阵,投影层将 2 c 2c 2c个向量做求和累加,即 x w = ∑ i = 1 2 c v w x_w=\sum_{i=1}^{2c}v_w xw=i=12cvw
  • 输出层:经过投影层后,我们得到 1 ∗ m 1*m 1m维的向量,这就是我们模型的输出,那怎么将他和真实的label(也即是 w w w)关联在一起,构造合适的损失函数进行学习?这里有三种思路:

传统做法

首先想到的是将得到的向量与新的矩阵M(维度为 V ∗ m V*m Vm)计算相似度,得到 1 ∗ V 1*V 1V个值,然后进行softmax处理,得到 1 ⋅ m 1\cdot m 1m维向量,将其与为真实词对应的向量 v m v_m vm比较设置目标函数,进行更新,即完成一次参数更新。

上述过程的简单代码表示即:

# 输入层-投影层
EMBEDDING_DIM = 5 # you can choose your own number
W1 = tf.Variable(tf.random_normal([vocab_size, EMBEDDING_DIM]))
b1 = tf.Variable(tf.random_normal([EMBEDDING_DIM])) #bias
hidden_representation = tf.add(tf.matmul(x,W1), b1)

# 输出层
prediction = tf.nn.softmax(tf.add( tf.matmul(hidden_representation, W1), b1))

# 设置目标函数
cross_entropy_loss = tf.reduce_mean(-tf.reduce_sum(y_label * tf.log(prediction), reduction_indices=[1]))

# 更新
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy_loss)

sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
sess.run(train_step, feed_dict={x: x_train, y_label: y_train})

注意此时,我们得到两个矩阵,前面为input_embedding, 后面为output_embedding,训练后输入和输出的向量效果几乎一样,而把这两个矩阵设为同样的权重则效果更差。

Hierarchical Softmax

  • 首先介绍Humman Tree

由名字可知,Huffman Tree是一种树结构,它的定义是:

给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,
称这样的二叉树为最优二叉 树,也称为霍夫曼树(Huffman Tree).

我们以每个词出现频率作为权重,构建Huffman Tree,具体过程如下[1]:

输入: 权值为 ( w 1 , w 2 , . . . , w n ) (w_1, w_2, ...,w_n) (w1,w2,...wn)的n个节点
输出:一颗Huffman 树

  1. ( w 1 , w 2 , . . . , w n ) (w_1, w_2, ...,w_n) (w1,w2,...wn)看做是有n棵树的森林,每个树仅有一个节点。
    2)在森林中选择根节点权值最小的两棵树进行合并,得到一个新的树,这两颗树分布作为新树的左右子树。新树的根节点权重为左右子树的根节点权重之和。
    3)将之前的根节点权值最小的两棵树从森林删除,并把新树加入森林。
  2. 重复步骤2)和3)直到森林里只有一棵树为止。

举个例子:
我们有(a,b,c,d,e,f)共6个节点,节点的权值分布是(20,4,8,6,16,3)。

首先是最小的b和f合并,得到的新树根节点权重是7.此时森林里5棵树,根节点权重分别是20,8,6,16,7。此时根节点权重最小的6,7合并,得到新子树,依次类推,最终得到下面的霍夫曼树。

  • 为什么使用Huffman tree

由传统做法虽然可以求得词向量,但计算量很大,耗资源严重,为了解决上述问题,避免计算所有词的softmax概率,引入Huffman Tree;具体的做法是:

对于我们要预测的标签,也就是中间的词,先将其进行Huffman编码,实际上事先需要对所有的词进行Huffman编码,这里对于所有词汇组成的Huffman Tree,我们可以约定左子树编码为1,右子树为0,这样我们可能得到中心词 w w w的编码为 010 010 010

由真实值的编码和隐藏层的向量,怎么利用设置目标函数呢?

这里,我们采用二元逻辑回归的方法,即如果编码为1,那就是负类,沿左子树走;如果编码为0,那沿着右子树走,就是正类,举例,当为1时:

P ( + ) = σ ( x w T θ ) = 1 1 + e − x w T θ P(+) = \sigma(x_w^T\theta) = \frac{1}{1+e^{-x_w^T\theta}} P(+)=σ(xwTθ)=1+exwTθ1
具体而言,即:
P ( d j w ∣ x w , θ j − 1 w ) = { σ ( x w T θ j − 1 w ) d j w = 0 1 − σ ( x w T θ j − 1 w ) d j w = 1 P(d_j^w|x_w, \theta_{j-1}^w)= \begin{cases} \sigma(x_w^T\theta_{j-1}^w)& {d_j^w=0}\\ 1- \sigma(x_w^T\theta_{j-1}^w) & {d_j^w = 1} \end{cases} P(djwxw,θj1w)={σ(xwTθj1w)1σ(xwTθj1w)djw=0djw=1

其中 x w x_w xw为隐层的词向量,而 θ \theta θ为需要求的模型参数。

具体举个例子,当我们要预测的词为 w 2 w_2 w2时,其编码为 110 110 110,那么我们需要最大整个路程的似然函数,即

∏ i = 1 3 P ( n ( w i ) , i ) = ( 1 − 1 1 + e − x w T θ 1 ) ( 1 − 1 1 + e − x w T θ 2 ) 1 1 + e − x w T θ 3 \prod_{i=1}^3P(n(w_i),i) = (1- \frac{1}{1+e^{-x_w^T\theta_1}})(1- \frac{1}{1+e^{-x_w^T\theta_2}})\frac{1}{1+e^{-x_w^T\theta_3}} i=13P(n(wi),i)=(11+exwTθ11)(11+exwTθ21)1+exwTθ31

后续可以使用梯度下降法来更新参数;

Huffman tree的好处:
第一, 由于是二叉树,之前的计算量是 V V V,现在是 l o g 2 V log_2V log2V
第二,由于建树时,高频的词靠近树根,这样高频词更少时间找到。

具体算法流程:

输入:基于CBOW的语料训练样本,词向量的维度大小M,CBOW的上下文大小2c,步长η
输出:霍夫曼树的内部节点模型参数θ,所有的词向量w

  1. 基于语料训练样本建立霍夫曼树
  2. 随机初始化所有的模型参数θ,所有的词向量w
  3. 进行梯度上升迭代过程,对于训练集中的每一个样本(context(w),w)做如下处理:
    a) e=0, 计算 x w = 1 2 c ∑ i = 1 2 c x i x_w= \frac{1}{2c}\sum\limits_{i=1}^{2c}x_i xw=2c1i=12cxi
    b) for j = 2 to l w l_w lw, 计算:
    f = σ ( x w T θ j − 1 w ) f = \sigma(x_w^T\theta_{j-1}^w) f=σ(xwTθj1w)
    g = ( 1 − d j w − f ) η g = (1-d_j^w-f)\eta g=(1djwf)η
    e = e + g θ j − 1 w e = e + g\theta_{j-1}^w e=e+gθj1w
    θ j − 1 w = θ j − 1 w + g x w \theta_{j-1}^w= \theta_{j-1}^w + gx_w θj1w=θj1w+gxw
    c) 对于context(w)中的每一个词向量 x i x_i xi(共2c个)进行更新:
    x i = x i + e x_i = x_i + e xi=xi+e
    d) 如果梯度收敛,则结束梯度迭代,否则回到步骤3继续迭代。

Negative Sampling

利用Huffman tree能够节省很多计算,但也有一些问题,如果我们的目标词是一个生僻的词,那么就需要在Huffman中走很久;另一种更简单的方法即是 Negative sampling,中文名就是负采样。

传统做法中,在巨大的词汇表里,我们有一个明确的正例,其他均是负例,softmax计算时会用到正例以及所有的负例,负采样简单的可以认为是从其他所有的负例中选取若干负例,与正例一起完成一次训练;

那么怎样通过一个正例和n个负例设置目标函数呢?又怎样从所有负例中采样呢?

  • 目标函数

根据逻辑斯特回归,模型的似然函数为:

∏ i = 0 n e g P ( c o n t e x t ( w 0 ) , w i ) = σ ( x w 0 T θ w 0 ) ∏ i = 1 n e g ( 1 − σ ( x w 0 T θ w i ) ) = ∏ i = 0 n e g σ ( x w 0 T θ w i ) y i ( 1 − σ ( x w 0 T θ w i ) ) 1 − y i \begin{aligned} \prod_{i=0}^{neg}P(context(w_0), w_i) & = \sigma(x_{w_0}^T\theta^{w_0})\prod_{i=1}^{neg}(1- \sigma(x_{w_0}^T\theta^{w_i})) \\ &= \prod_{i=0}^{neg} \sigma(x_{w_0}^T\theta^{w_i})^{y_i}(1- \sigma(x_{w_0}^T\theta^{w_i}))^{1-y_i} \end{aligned} i=0negP(context(w0),wi)=σ(xw0Tθw0)i=1neg(1σ(xw0Tθwi))=i=0negσ(xw0Tθwi)yi(1σ(xw0Tθwi))1yi

对数似然函数为

L = ∑ i = 0 n e g y i l o g ( σ ( x w 0 T θ w i ) ) + ( 1 − y i ) l o g ( 1 − σ ( x w 0 T θ w i ) ) L = \sum\limits_{i=0}^{neg}y_i log(\sigma(x_{w_0}^T\theta^{w_i})) + (1-y_i) log(1- \sigma(x_{w_0}^T\theta^{w_i})) L=i=0negyilog(σ(xw0Tθwi))+(1yi)log(1σ(xw0Tθwi))

计算 θ w i \theta^{w_i} θwi的梯度:
∂ L ∂ θ w i = y i ( 1 − σ ( x w 0 T θ w i ) ) x w 0 − ( 1 − y i ) σ ( x w 0 T θ w i ) x w 0 = ( y i − σ ( x w 0 T θ w i ) ) x w 0 \begin{aligned} \frac{\partial L}{\partial \theta^{w_i} } &= y_i(1- \sigma(x_{w_0}^T\theta^{w_i}))x_{w_0}-(1-y_i)\sigma(x_{w_0}^T\theta^{w_i})x_{w_0} \\ & = (y_i -\sigma(x_{w_0}^T\theta^{w_i})) x_{w_0} \end{aligned} θwiL=yi(1σ(xw0Tθwi))xw0(1yi)σ(xw0Tθwi)xw0=(yiσ(xw0Tθwi))xw0

计算 x w 0 x_{w_0} xw0的梯度:
∂ L ∂ x w 0 = ∑ i = 0 n e g ( y i − σ ( x w 0 T θ w i ) ) θ w i \frac{\partial L}{\partial x^{w_0} } = \sum\limits_{i=0}^{neg}(y_i -\sigma(x_{w_0}^T\theta^{w_i}))\theta^{w_i} xw0L=i=0neg(yiσ(xw0Tθwi))θwi

  • 负采样方法

采样方法并不复杂,希望高频词得到的概率大,低频词得到的概率更小;这里,我们将一个长度为1的线段分为V份,每份对应词汇表中的一个词。每个词的线段长度为
l e n ( w ) = c o u n t ( w ) ∑ u ∈ v o c a b c o u n t ( u ) len(w) = \frac{count(w)}{\sum\limits_{u \in vocab} count(u)} len(w)=uvocabcount(u)count(w)

在Word2Vec中,分子和分母都去了3/4次幂:

l e n ( w ) = c o u n t ( w ) 3 / 4 ∑ u ∈ v o c a b c o u n t ( u ) 3 / 4 len(w) = \frac{count(w)^{3/4}}{\sum\limits_{u \in vocab} count(u)^{3/4}} len(w)=uvocabcount(u)3/4count(w)3/4

在采样前,我们将这段长度为1的线段划分成M等份,这里M>>V(论文中 M = 1 0 8 M=10^8 M=108),这样可以保证每个词对应的线段都会划分成对应的小块。而M份中的每一份都会落在某一个词对应的线段上。在采样的时候,随机生成一个[1, M-1]的随机数m,然后看m对应到的线段所属的词就是我们的负例词,如果碰巧遇到目前词 w w w,跳过即可。

算法流程

输入:基于CBOW的语料训练样本,词向量的维度大小M,CBOW的上下文大小2c,步长η, 负采样的个数neg
输出:词汇表每个词对应的模型参数θ,所有的词向量 x w x_w xw

  1. 随机初始化所有的模型参数θ,所有的词向量w
  2. 对于每个训练样本 ( c o n t e x t ( w 0 ) , w 0 ) (context(w_0),w_0) (context(w0),w0),负采样出neg个负例中心词 w i , i = 1 , 2 , . . . n e g w_i,i=1,2,...neg wi,i=1,2,...neg
  3. 进行梯度上升迭代过程,对于训练集中的每一个样本 ( c o n t e x t ( w 0 ) , w 0 , w 1 , . . . w n e g ) (context(w_0), w_0,w_1,...w_{neg}) (context(w0),w0,w1,...wneg),做如下处理:
    a. e=0, 计算 x w 0 = 1 2 c ∑ i = 1 2 c x i x_{w_0}= \frac{1}{2c}\sum\limits_{i=1}^{2c}x_i xw0=2c1i=12cxi
    b. for i= 0 to neg, 计算:
    f = σ ( x w 0 T θ w i ) g = ( y i − f ) η e = e + g θ w i θ w i = θ w i + g x w 0 f = \sigma(x_{w_0}^T\theta^{w_i})\\ g = (y_i-f)\eta\\ e = e + g\theta^{w_i}\\ \theta^{w_i}= \theta^{w_i} + gx_{w_0}\\ f=σ(xw0Tθwi)g=(yif)ηe=e+gθwiθwi=θwi+gxw0
    c.对于context(w)中的每一个词向量 x k x_k xk(共2c个)进行更新:
    x k = x k + e x_k = x_k + e xk=xk+e
    d. 如果梯度收敛,则结束梯度迭代,否则回到步骤3继续迭代。

Skip-gram 模型

除了结构上不同,即我们从滑动窗口中选取1个中心词作为输入,2c个词作为预测的label,其建模的方式与CBOW类似,同样有hierachical softmax和negative sampling。
这里直接谢谢Negative sampling的算法流程

输入:基于Skip-gram的语料训练样本,词向量的维度大小M,skip-gram的上下文大小2c,步长η, 负采样的个数neg
输出:词汇表每个词对应的模型参数θ,所有的词向量 x w x_w xw

  1. 随机初始化所有的模型参数θ,所有的词向量w
  2. 对于每个训练样本 ( c o n t e x t ( w 0 ) , w 0 ) (context(w_0),w_0) (context(w0),w0),负采样出neg个负例中心词 w i , i = 1 , 2 , . . . n e g w_i,i=1,2,...neg wi,i=1,2,...neg
  3. 进行梯度上升迭代过程,对于训练集中的每一个样本 ( c o n t e x t ( w 0 ) , w 0 , w 1 , . . . w n e g ) (context(w_0), w_0,w_1,...w_{neg}) (context(w0),w0,w1,...wneg),做如下处理:
    a for i=1 to 2c:
      i) e=0, 计算 x w 0 = 1 2 c ∑ i = 1 2 c x i x_{w_0}= \frac{1}{2c}\sum\limits_{i=1}^{2c}x_i xw0=2c1i=12cxi
      ii) for i= 0 to neg, 计算:
    f = σ ( x w 0 T θ w i ) g = ( y i − f ) η e = e + g θ w i θ w i = θ w i + g x w 0 f = \sigma(x_{w_0}^T\theta^{w_i})\\ g = (y_i-f)\eta\\ e = e + g\theta^{w_i}\\ \theta^{w_i}= \theta^{w_i} + gx_{w_0}\\ f=σ(xw0Tθwi)g=(yif)ηe=e+gθwiθwi=θwi+gxw0
      iii)词向量更新:
    x k = x k + e x_k = x_k + e xk=xk+e
    b. 如果梯度收敛,则结束梯度迭代,否则回到步骤3继续迭代。

区别

CBOW和Skip-gram均是词向量化的方法,他们的差异在哪呢?

CBOW对周围词的调整是统一的,求出的梯度会同样地作用到每个周围词向量中,并且其预测行为的次数和文本的词数几乎相等,复杂度为O(V);

Skip-gram 的预测次数要多于CBOW, 因为每个词作为中心词时,都要对周围的词预测一次,这样时间复杂度为O(KV),训练上比CBOW要久。

在使用skip-gram时,每个词都要收到周围的词的影响,每个词在作为中心词的时候,都要进行K次的预测、调整。当数据量较小时,或者词相对生僻时,这样的调整会使得词向量更加准确。

Ref:

  1. https://www.cnblogs.com/pinard/p/7160330.html
  2. https://blog.csdn.net/itplus/article/details/37969519
  3. https://blog.csdn.net/Mr_tyting/article/details/80091842
  4. https://zhuanlan.zhihu.com/p/37477611
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值