Word2vec之Skip-gram理解

此文章参考多位大佬所总结出来的,如涉及侵权,请告之!

Word2vec

定义

将文本语料以无监督的方式来进行语义知识的学习工具, 主要包含 skip-gram,**CBOW(Continuous bag of word)**二种模型, 本质是通过文本学习来用词向量表征词的语义信息,通过嵌入空间使词语义相似的词距离相近, Embedding其实是一种映射,即把词从原有的空间中嵌入到新的维度空间中

The Model - 模型

Skip-gram

模型结构

Word2vec之Skip-gram理解
  • 模型共分为三层:input Layer(输入层)、Hidden Layer(隐层)、ouput Layer(输出层)
  • 隐层没有使用任何**函数,但是输出层使用了 softmax **函数,因此最终模型的输出是一个概率分布
  • The Fake Task - 伪任务,我们构建并运行神经网络,但需要的并不是概率分布,我们需要的是获得到的隐层的权重

1.1 Input Layer - 输入层

实质就是一个 one-hot word symbol (编码)

  • 由于计算机不能识别字符,神经网络只能接受数值,因此我们需要基于训练数据建立自己的词汇表 vocabulary, 再对其进行 one-hot 编码

  • 如何使用

    • 假如一个词典中有 N 个词,我们就创建 N 维的向量,其中单词出现的位置为 1,那么对应的向量就生成了,举个例子:[“the”, “man”, “loves”, “his”, “son”],其中 “the” 就可以编码为[1,0,0,0,0], "man"可以编码为[0,1,0,0,0],今次类推
  • 存在问题

    • 无法用 one-hot 来表示词之间的相似度
    • 每个单词之间是正弦相交,余弦相似度为0,彼此之间没有任何联系
  • 我们的最终目的是用一个稠密的向量表示一个单词,并且单词能在空间中表征准确含义的,比如 “man” 和 “woman” 在空间中距离比较近,那么我们就只能通过词嵌入的方式来解决

1.2 The Hidden Layer 隐藏层

可以看作一种 Lookup Table(查表)方式

  • 为了用一个稠密的向量来表示一个单词,使它们语义相近,那么我们必须初始化一个向量,然后通过学习的方式来更新向量的值(权重),最终得到我们想要的向量

  • 既然有了这个想法,因此我们就将上述的 one-hot 编码用一个稠密的向量来表示,也就是进行一个映射

  • 这个映射过程,就被称作嵌入(Embedding),由于是单词的嵌入,也被叫做词嵌入(Word Embedding),既然我们要做一个 Embedding,那么又应该怎样做呢

  • 假设我们要把一个单词映射到一个300维(这个300维是经过大量的实验得出的,一般的映射范围是200-500维)的向量中,那么我们直观的做法就是:矩阵运算。因为我们每个单词现在是一个N维的向量,如果映射到300维,需要的权重矩阵就是N∗300,其中N 代表的就是词汇表中的单词个数,300代表的是每个隐层的神经元:
    Word2vec之Skip-gram理解

  • 我们将一个向量映射到一个 300 维的矩阵中,会得到的矩阵就是一个1∗300的矩阵,可以理解为向量。以下图所示(假设N=5)。

Word2vec之Skip-gram理解
  • 矩阵运算,对应元素相乘再加得到,比如:10 = 0 * 17 + 0 * 23 + 0 * 4 + 1 * 10 + 0 * 11,但是这么小的矩阵,也运算了 5*3=15 次,

    如果我们的词汇表中 N=10000 , 词向量维度为 300 我们就需要计算 300W 次,但是实际情况是,词汇表大小很可能更大,这样计算量就显示十分庞大

  • 不难发现,上图存在着一个规律

    • 计算出来的这组向量,与我们的单词在 one-hot 编码的位置存在着关系
    • 如果单词的 column=3 那么就会选取权重矩阵中的 index=3 的位置
    • 因此就可以将这种索引对应的关系,将单词映射到任意维度中了
    • 上面这种通过索引对应的映射方式,就叫做查表(Lookup Table)了,不需要计算,仅仅通过索引就可以进行映射了
  • 过程就是经过 one-hot 编码后的词向量,通过神经网络中的隐藏层后,映射到一个低维度的空间中,并且没有任何**函数

  • 目前已经完成了二步,one-hot —> lookup table, 也就完成了词向量的初始化,后面要做的就是进行训练了

1.3 Output Layer 输出层

数学原理

Word2vec之Skip-gram理解
  • 我们现在已经从 Hidden Layer 到 Output Layer 了,简单来看就是隐藏层和输出层进行一个全连接,然后进行一个 Softmax 归一化,输出概率

  • 过程实际上就是一个 Forward Propagation(正向传播),一个 Backward Propagation(反向传播) ,完成参数更新

  • 首先我们先举个例子

    • 假设我们的文本序列中有5个词:[“the”, “man”, “loves”, “his”, “son”]

    • 假设我们的 window_size=2,中心词为 “loves”, 那么上下文为 “the”, “man”, “his”, “son”

    • Skip-gram 帮我们做的是,通过中心词 “loves”,生成与不超过 windows_size 的词的概率,用公式表示即为:

      P(“the",“man",“his",“son"∣“loves").

    • 进一步,假设给定中心词的情况下,背景之间是相互独立的,公式近一步得到一个联合概率
      P(the"loves")P(man"loves")P(his"loves")P(son"loves") P(“the"∣“loves")⋅P(“man"∣“loves")⋅P(“his"∣“loves")⋅P(“son"∣“loves")
      即为:
      P(Wi2Wi)P(Wi1Wi)P(Wi+1Wi)P(Wi+2Wi) P(W_{i-2}|W_i)\cdot P(W_{i-1}|W_i)\cdot P(W_i+1|W_i)\cdot P(W_{i+2}|W_i)

Word2vec之Skip-gram理解
  • 在 skip-gram 模型中,每个词表示为二个 d 维的向量,用来计算条件概率,假设这个词在词典中的索引为 i,当它为中心词时向量表示为viRdv_i∈R^d(RdR^d表示为二个向量之间的距离), 当它为背景词时(window_size内的所有词称之为背景词)向量表示为 uiRdu_i∈R^d,设中心词向量 wcw_c 索引为 c,背景词向量 wow_o 索引为 o,在给定中心词预测背景词的概率可以对向量内积做 softmax 计算得到,如下工式:

P(wowc)=exp(uouc)iVexp(uivc) P(w_o|w_c) = \dfrac{exp(u_o^⊤u_c)}{\sum_{i\in V}exp(u_i^⊤v_c)}

​ 为了更方便的理解,我们可以参考下图:
Word2vec之Skip-gram理解

  • 上述有中心词推断背景词,值得注意的是,整个过程的输入是 中心词向量、背景词向量,假设上述提到的 背景词之间是相互独立的,因此可以把背景词出现的概率 P(Wi2Wi)P(Wi1Wi)P(Wi+1Wi)P(Wi+2Wi)P(W_{i-2}|W_i)\cdot P(W_{i-1}|W_i)\cdot P(W_i+1|W_i)\cdot P(W_{i+2}|W_i) 写成连乘的形式:

P(Wi2Wi)P(Wi1Wi)P(Wi+1Wi)P(Wi+2Wi)=t=1Tmjm,j0P(w(t+1)w(t)) P(W_{i-2}|W_i)\cdot P(W_{i-1}|W_i)\cdot P(W_i+1|W_i)\cdot P(W_{i+2}|W_i) = \prod_{t=1}^{T}\prod_{-m\leq j\leq m,j\neq 0}P(w^{(t+1)}|w^{(t)})

  • 上述 T 表示中心词的位置,m 表示 window_size 的大小,这样我们就能计算出每个中心词出现背景词的概率,在输入中我们已经给出了背景词的向量,因此我们的目标就是最大化背景词的输出概率,这很容易让人想起最大似然估计方式,但是一个函数的最大值一般很难计算,因此我们将以上函数进行一个变化,从而改变函数的增减性,以便优化:
    t=1Tmjm,j0logP(w(t+1)w(t)) -\sum_{t=1}^{T}\sum_{-m\leq j\leq m,j\neq 0}logP(w^{(t+1)}|w{(t)})

    • 上述函数变化应该非常容易理解,就是一个对数似然变化,对数是单调递增,不会影响函数的单调性,
    • “-”负号使函数的单调性对调,也就是求最大值变化为求最小值的问题
  • 最小值问题我们一般采用梯度下降法,在使用梯度下降之前,我们应该先定义损失函数,毕竟上边的公式只是一个概率,下面把 softmax 计算值代入:
    logP(wowc)=log(exp(uouc)iVexp(uivc)) logP(w_o|wc) = log\left(\dfrac{exp(u_o^⊤u_c)}{\sum_{i\in V}exp(u_i^⊤v_c)}\right)

  • 将 log 除法变为减法, 并且log和e 可以相互抵消,就可以得到

logP(wowc)=uouclog(iVexp(uivc)) logP(w_o|wc) = u_o^⊤u_c - log\left(\sum_{i\in V}exp(u_i^⊤v_c)\right)

  • 求导计算梯度:

logP(wowc)vc=uojvexp(ujvc)ujivexp(uivc)=uojv(exp(ujvc)ivexp(uivc))uj=uoivP(wjwc)uj\begin{aligned} \dfrac{\partial logP(w_o|wc)}{\partial v_c} =& u_o - \dfrac{\sum_{j\in v}exp(u_j^⊤v_c)u_j}{\sum_{i\in v}exp(u_i^⊤v_c)}\\ =& u_o - \sum_{j \in v}{\left( \dfrac{exp(u_j^⊤v_c)}{\sum_{i\in v}exp(u_i^⊤v_c)} \right)u_j}\\ =& u_o - \sum_{i\in v}{P(w_j|w_c)u_j} \end{aligned}

  • 这就计算出了中心词的梯度,可以按照这个梯度来迭代更新,背景词也按同样的方式进行更新,要注意的是背景词的个数不是唯一的,因此要逐一更新,如下图:中心词为“The”,对应的背景词就有2个“quick”、“brown”

    Word2vec之Skip-gram理解
  • 训练结束后,我们对于下标为 i 的词,均得到其作为中心词和背景词的词向量 viv_iuiu_i

总结

Word2vec之Skip-gram理解
  1. One-hot 编码,形成 1 * V 的向量,整个词汇表就是 V*V 的矩阵
  2. Lookup Table查表降维,通过索引映射,将单个词映射到 d 维空间,通过这种方式可以将整个词汇表映射到矩阵 W 中(矩阵的形状为 V * d)并且单个词与矩阵中的某一行对应
  3. Skip-gram 模型训练,我们要初始化一个空间矩阵作为权重矩阵 ww^‘ ,该矩阵的形状为 d * V,值得注意的是目前已经存在二个矩阵,一个作为中心词时的向量 viv_i,另 一个作为背景词向量 uiu_i ,每个词都有机会成为中心词,同时也有机会成为其它中心词的背景词,因为窗口在变动
  4. 取出中心词向量 viv_i (它的形状为 1* V)与权重矩阵中的每个词做内积,此时会得到每个词的计算结果(本质就是矩阵的乘法运算),即 viuoTv_i u_o^T(此时背景词的索引为 o)
  5. Softmax映射,上一步计算出 V 个数字,此时再计算每个词的概率,即:exp(uouc)iVexp(uivc)\dfrac{exp(u_o^⊤u_c)}{\sum_{i\in V}exp(u_i^⊤v_c)}.大家要注意的是 i 表示词汇表中任意一个词,因此计算量较大
  6. 概率最大化,我们期望将 window_size 内单词出现的概率最大,因此也就变成了使目标函数极大化概率 P(wowc)P(w_o|w_c),在不影响函数单调性的情况下,我们变为极大化函数:logP(wowc)logP(w_o|w_c)(对数似然),但是,我们明白,计算函数的最大值不如计算一个函数的最小值来的方便,因此我们对这个函数进行单调性变化:logP(wowc)-logP(w_o|w_c)
  7. 极小化目标函数,通过上面的变化,就变成了数学优化问题,梯度下降法更新参数
  8. 更新中心词向量 viv_i,上面已经推导出结果:vc=vcαP(wowc)v_c = v_c - \alpha * \nabla P(w_o|w_c) , 后面减去的即为梯度P(wowc)=uojv(exp(uouc)iVexp(uivc))uj\nabla P(w_o|w_c)= u_o - \sum_{j\in v}{\left( \dfrac{exp(u_o^⊤u_c)}{\sum_{i\in V}exp(u_i^⊤v_c)}\right)}u_j
    • 简单分析一下:大括号里面的值是一个概率,乘上一个矩阵向量 uiu_i,仍是一个矩阵,相当于对矩阵进行了加权,然后 uou_o这个背景矩阵减去这个加权矩阵,就得到了ucu_c的梯度值,如此就可以对上一个学习率进行迭代更新了
  9. 有同学可能注意到了,这里面为什么又多了个下标,这个下标代表的背景词,的取值范围是−m≤j≤m, j不能为0,这么做才能与我们最初的设定相对应吧!一个中心词,推断多个背景词。
  10. 在对词汇表中所有的词进行训练后,我们就会得到每个词对应的二个词向量 viv_iuiu_iviv_i表示作为中心词时的词向量,同时也是我们最终需要的结果,uiu_i对应的就是我们的词作为背景词时的词向量了

Negative Sampling 负采样

  • 由于 skip-gram 采用的是随机梯度下降,也就是说每次完成计算,都会对所有权重进行更新,但是有二个问题,决定了模型的效率低下

    1. 我们了解到 word2vec 是一个庞大的神经网络,例如,有10000个词的词汇表,向量特征为300维,因此就会有二个 300*10000=3000000的矩阵,在如此大的神经网络上进行梯度下降是非常缓慢的,更严重的是,我们还要根据训练数据去调整 weights
    2. 我们使用的目标函数是 softmax, 即 exp(uouc)iVexp(uivc)\dfrac{exp(u_o^⊤u_c)}{\sum_{i\in V}exp(u_i^⊤v_c)}, 我们可以观察分母,需要把所有单词的词向量算出来再求和,计算量非常大
  • Negative Sampling 让一个训练样本仅更新一部分的权重参数,从而降低梯度下降过程中的计算量

  • 如果我们的 vocablary 的大小为 10000 时,当样本(“fox”, “quick”)输入到神经网络时,”fox“经过 one-hot 编码后,我们期望”quick“对应的神经元输出为1,其余9999个输出为0, 在这里,这9999个输出为0的神经元对应的单词我们称为 negative word,negative sampling 的想法也很想象,我们随机从 negative word中抽取一部分单词,比如随机抽取10个来更新其权重

  • 在论文出指出,对于小指数据,我们随机抽取 5-20 个,对于大指数据选择 2-5 个

  • 如果使用 negative sampling 仅仅去更新 中心词和其它10个 negative word的结点对应的权重,共计11个神经元,相当于每次只更新 300 * 11 = 3300 个权重参数,相对于 300W 的权重参数来说,只更新了其千分之一,这样计算效率就大幅度的提高了

Selecting Netative Sampling
  • 使用 一元模型分(unigram distribution)来选择 negative words,一个词被造作 negative words 的概率跟它出现的频率有关,频率越高越容易被造作 negative words ,经验公式为:
    Word2vec之Skip-gram理解
  • f(w) 代表 每个单词的一个权重,即它单词出现的词频,分母代表所有单词的权重和。公式中3/4完全是基于经验的,论文中提到这个公式的效果要比其它公式更加出色。
    Word2vec之Skip-gram理解
  • Output Layer 层中不再使用 softmax 而改为使用 sigmoid 函数

以上仅为个人总结,由于技术及知识面不够全面,所以某些地方可能存在认识不够清楚的问题,如有大佬有不同意见,可留言大家一同探讨 – 以python 追时间

参考文章:
https://www.jianshu.com/p/8e291e4ba0da
https://blog.****.net/weixin_41843918/article/details/90312339
https://www.jianshu.com/p/ed15e2adbfad