Word2vec之Skip-gram理解
此文章参考多位大佬所总结出来的,如涉及侵权,请告之!
Word2vec
定义
将文本语料以无监督的方式来进行语义知识的学习工具, 主要包含 skip-gram,**CBOW(Continuous bag of word)**二种模型, 本质是通过文本学习来用词向量表征词的语义信息,通过嵌入空间使词语义相似的词距离相近, Embedding其实是一种映射,即把词从原有的空间中嵌入到新的维度空间中
The Model - 模型
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代表的是每个隐层的神经元:
-
我们将一个向量映射到一个 300 维的矩阵中,会得到的矩阵就是一个1∗300的矩阵,可以理解为向量。以下图所示(假设N=5)。
-
矩阵运算,对应元素相乘再加得到,比如: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 输出层
数学原理
-
我们现在已经从 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").
-
进一步,假设给定中心词的情况下,背景之间是相互独立的,公式近一步得到一个联合概率:
即为:
-
- 在 skip-gram 模型中,每个词表示为二个 d 维的向量,用来计算条件概率,假设这个词在词典中的索引为 i,当它为中心词时向量表示为(表示为二个向量之间的距离), 当它为背景词时(window_size内的所有词称之为背景词)向量表示为 ,设中心词向量 索引为 c,背景词向量 索引为 o,在给定中心词预测背景词的概率可以对向量内积做 softmax 计算得到,如下工式:
为了更方便的理解,我们可以参考下图:
- 上述有中心词推断背景词,值得注意的是,整个过程的输入是 中心词向量、背景词向量,假设上述提到的 背景词之间是相互独立的,因此可以把背景词出现的概率 写成连乘的形式:
-
上述 T 表示中心词的位置,m 表示 window_size 的大小,这样我们就能计算出每个中心词出现背景词的概率,在输入中我们已经给出了背景词的向量,因此我们的目标就是最大化背景词的输出概率,这很容易让人想起最大似然估计方式,但是一个函数的最大值一般很难计算,因此我们将以上函数进行一个变化,从而改变函数的增减性,以便优化:
- 上述函数变化应该非常容易理解,就是一个对数似然变化,对数是单调递增,不会影响函数的单调性,
- “-”负号使函数的单调性对调,也就是求最大值变化为求最小值的问题
-
最小值问题我们一般采用梯度下降法,在使用梯度下降之前,我们应该先定义损失函数,毕竟上边的公式只是一个概率,下面把 softmax 计算值代入:
-
将 log 除法变为减法, 并且log和e 可以相互抵消,就可以得到
- 求导计算梯度:
-
这就计算出了中心词的梯度,可以按照这个梯度来迭代更新,背景词也按同样的方式进行更新,要注意的是背景词的个数不是唯一的,因此要逐一更新,如下图:中心词为“The”,对应的背景词就有2个“quick”、“brown”
-
训练结束后,我们对于下标为 i 的词,均得到其作为中心词和背景词的词向量 和
总结
- One-hot 编码,形成 1 * V 的向量,整个词汇表就是 V*V 的矩阵
- Lookup Table查表降维,通过索引映射,将单个词映射到 d 维空间,通过这种方式可以将整个词汇表映射到矩阵 W 中(矩阵的形状为 V * d)并且单个词与矩阵中的某一行对应
- Skip-gram 模型训练,我们要初始化一个空间矩阵作为权重矩阵 ,该矩阵的形状为 d * V,值得注意的是目前已经存在二个矩阵,一个作为中心词时的向量 ,另 一个作为背景词向量 ,每个词都有机会成为中心词,同时也有机会成为其它中心词的背景词,因为窗口在变动
- 取出中心词向量 (它的形状为 1* V)与权重矩阵中的每个词做内积,此时会得到每个词的计算结果(本质就是矩阵的乘法运算),即 (此时背景词的索引为 o)
- Softmax映射,上一步计算出 V 个数字,此时再计算每个词的概率,即:.大家要注意的是 i 表示词汇表中任意一个词,因此计算量较大
- 概率最大化,我们期望将 window_size 内单词出现的概率最大,因此也就变成了使目标函数极大化概率 ,在不影响函数单调性的情况下,我们变为极大化函数:(对数似然),但是,我们明白,计算函数的最大值不如计算一个函数的最小值来的方便,因此我们对这个函数进行单调性变化:
- 极小化目标函数,通过上面的变化,就变成了数学优化问题,梯度下降法更新参数
- 更新中心词向量 ,上面已经推导出结果:, 后面减去的即为梯度:
- 简单分析一下:大括号里面的值是一个概率,乘上一个矩阵向量 ,仍是一个矩阵,相当于对矩阵进行了加权,然后 这个背景矩阵减去这个加权矩阵,就得到了的梯度值,如此就可以对上一个学习率进行迭代更新了
- 有同学可能注意到了,这里面为什么又多了个下标,这个下标代表的背景词,的取值范围是−m≤j≤m, j不能为0,这么做才能与我们最初的设定相对应吧!一个中心词,推断多个背景词。
- 在对词汇表中所有的词进行训练后,我们就会得到每个词对应的二个词向量 和 ,表示作为中心词时的词向量,同时也是我们最终需要的结果,对应的就是我们的词作为背景词时的词向量了
Negative Sampling 负采样
-
由于 skip-gram 采用的是随机梯度下降,也就是说每次完成计算,都会对所有权重进行更新,但是有二个问题,决定了模型的效率低下
- 我们了解到 word2vec 是一个庞大的神经网络,例如,有10000个词的词汇表,向量特征为300维,因此就会有二个 300*10000=3000000的矩阵,在如此大的神经网络上进行梯度下降是非常缓慢的,更严重的是,我们还要根据训练数据去调整 weights
- 我们使用的目标函数是 softmax, 即 , 我们可以观察分母,需要把所有单词的词向量算出来再求和,计算量非常大
-
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 ,经验公式为:
- f(w) 代表 每个单词的一个权重,即它单词出现的词频,分母代表所有单词的权重和。公式中3/4完全是基于经验的,论文中提到这个公式的效果要比其它公式更加出色。
- 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