1、词向量的发展

最早我们使用的词向量是one-hot,这玩意既稀疏又考虑语序和语义,自然是要被淘汰了。于是便有了分布式词向量,其实就是每一个或几个维度携带了语义和语序等信息。对于词向量我们划分为上下文无关的以及上下文有关的,前者是指训练出的词向量只有一种语义,不能解决一次多义的问题,无法在不同的场景中体现出不同的语义,包括Skipgram、Cbow、MF、glove等模型,后者则是可以根据不同的语义表现出不同的语义,包括ELMO、BERT、ALbert、XLNET和GPT等。
此外,one-hot的表示方法只具有局部泛化能力,可以简单理解为在一篇文章中某个词出现的多,用one-hot表示还可以,换一篇就不行了。而skipgram和Cbow则有全局泛化能力,所有词都能够共享训练参数,换个地方某个词的词向量表示依旧合理。
在实际训练中对词向量效果影响因素主要包括:框架的选择,词向量的维度,采样率,训练窗口的大小。

2、Cbow模型的设计思路

CBOW之所以叫连续词袋模型(continues bag of words),是因为在每个窗口内它也不考虑词序信息,因为它是直接把上下文的词向量相加取平均,自然就损失了词序信息。CBOW抛弃了词序信息,指的就是在每个窗口内部上下文直接相加取平均而没有考虑词序。

1.1、Cbow的目标函数是怎么得到的?

image.png
CBow模型背后的思想在于,作者认为相邻的词相似度越高,越远的相似度越低,那么如果相邻词出现了,中心词出现的概率应该很大,因此可以用中心词去预测相邻的词,用c表示中心词,w表示中心词附近的词,即概率 CBOW和SkipGram - 图2 越大越好。
比如“ferry thinks that he is good-looking but actually he is not. ”如果我们认为两个相邻距离内的词是很相似的,那么P(ferry|thinks)和P(thinks | that )概率应该很大,也就是说目标函数应该是最大化整个句子的概率:
CBOW和SkipGram - 图3
由于p(c|w)肯定在(0,1)范围内,而且我们希望正确的预测词在所有预测词中概率最大,因此可以用softmax的表达式来计算概率,也就是 CBOW和SkipGram - 图4
另一方面,既然认为相邻的词更相似,那么用来表示词的向量的内积应该越大越好,那么上式中的 CBOW和SkipGram - 图5 应该用向量内积来表示,也就是:
CBOW和SkipGram - 图6
这里的U和V代表两个矩阵,c是中心词的向量,w则是中心附近词的向量。那么这里为什么要用两个矩阵U和V呢,还是以上面的句子为例,thinks既是中心词又是ferry的相邻词,所以P(ferry|thinks)和P(thinks|ferry)都是需要计算的,这就需要两个矩阵,U存储的是作为中心词时的向量,V存储的是作为相邻词语时的向量。
得到了模型就可以求解。将式子使用log转换后可以得到:
CBOW和SkipGram - 图7
Cbow每次训练都会输入一个词组,比如上述的(ferry,thinks),输入输出都用one-hot向量表示,损失函数为:
CBOW和SkipGram - 图8
也就是令中心词和近邻词出现的概率最大,让它的概率接近1,从而训练这两个词的向量表示。

CBOW和SkipGram - 图9=UcVw 为输出层的结果,例如 CBOW和SkipGram - 图10 =[1,2,3,4],那么 CBOW和SkipGram - 图11 经过softmax之后得到CBOW和SkipGram - 图12[0.1,0.2,0.3,0.4],索引3的值最大,那么本次将输出词预测为索引3对应的词。假如实际正确词的one-hot为CBOW和SkipGram - 图13[0,0,0,1],则预测正确,[0.1,0.2,0.3,0.4]和[0,0,0,1]的差为本次的损失。

1.2、Cbow的的训练过程

语料 编码
He 1 0 0 0 0
is 0 1 0 0 0
my 0 0 1 0 0
little 0 0 0 1 0
son 0 0 0 0 1

如上图所示,首先语料库内的每个word都可以用one-hot的方式编码。假设选取Context Window2,那么模型中的一对inputtarget就是:

  • inputHeis、little和sonone-hot编码
  • target:my的one-hot编码
  1. 输入为C个V维的vector。其中C=4,V=5(词表长度)。4个vector分别为5维的He、is、little和son的one-hot编码形式;
  2. 激活函数相当简单,在输入层和隐藏层之间,每个input vector分别乘以一个VxN维度的矩阵W,将得到的向量各个维度做平均得到hh乘以一个NxV维度的矩阵W’,得到output layer的权重;

这里的W即为1.1节中的U矩阵,W’为V矩阵。
图片.png

  1. 隐藏层的维度设置为理想中压缩后的词向量维度。示例中假设我们想把原始的5维的原始one-hot编码维度压缩到2维,那么N=2;
  2. 输出层是一个softmax层,用于组合输出概率。所谓的损失函数,就是这个输出层output和target之间的的差(output的V维向量和input vector的one-hot编码向量的差),该神经网络的目的就是最小化这个loss;
  3. 优化结束后,隐藏层的V*N维向量就可以作为Word-Embedding的结果,每一行表示一个词的向量表示。

image.pngimage.pngimage.pngimage.png
CBOW模型窗口内部抛弃了词序信息,对语言来说语序是很重要的,‘我爱你’的词袋特征是‘我’ ‘爱’ ‘你’ ,‘你爱我’的词袋特征是‘你’ ‘爱’ ‘我’,这两个句子的特征是完全相同的,但是这两个句子的语义是不一样的,如果在CBOW窗口内加入n-gram特征(比如2-gram)就会得到额外的信息,第一个句子的2gram特征是‘我爱’和‘爱你’,第二个句子的特征是‘你爱’ 和‘爱我’,这样就把上下文完全相同的两个句子区分开了,这种做法就是fastText的做法

3、 SkipGram

通过查看所有语料的词作为中心词时,其(中心词)与上下文的2m个词语的所有共现情况,这样就得到我们要逼近的中心词与上下文对应关系的条件概率分布(这个概率分布是忽视掉了上下文词语间的顺序的),我们通过模型去训练出词典中心词向量矩阵和词典上下文词向量矩阵(这两个矩阵就是存储了语料中中心词与其上下文的对应关系信息)。
image.pngCBOW和SkipGram - 图20
输入是One-Hot Vector,Hidden Layer没有激活函数,也就是线性的单元。Output Layer维度跟Input Layer的维度一样。

4、CBOW和SkipGram比较

  1. skipgram比cbow预测次数更多,结果更准确。
  2. skipgram比cbow任务难度大,skipgram一个词预测多个,cbow多个词预测一个。
  3. cbow对于出现次数少的单词效果不好。

在cbow方法中,是用周围词预测中心词,从而利用中心词的预测结果情况,使用GradientDesent方法,不断的去调整周围词的向量。当训练完成之后,每个词都会作为中心词,把周围词的词向量进行了调整,这样也就获得了整个文本里面所有词的词向量。

要注意的是, cbow的对周围词的调整是统一的:求出的gradient的值会同样的作用到每个周围词的词向量当中去。

可以看到,cbow预测行为的次数跟整个文本的词数几乎是相等的(每次预测行为才会进行一次backpropgation, 而往往这也是最耗时的部分),复杂度大概是O(V);

而skip-gram是用中心词来预测周围的词。在skip-gram中,会利用周围的词的预测结果情况,使用GradientDecent来不断的调整中心词的词向量,最终所有的文本遍历完毕之后,也就得到了文本所有词的词向量。

可以看出,skip-gram进行预测的次数是要多于cbow的:因为每个词在作为中心词时,都要使用周围词进行预测一次。这样相当于比cbow的方法多进行了K次(假设K为窗口大小),因此时间的复杂度为O(KV),训练时间要比cbow要长。
图片.png

举例:”i love machine learning”,中心词为machine,window为1. CBOW: 输入为一个pairs,[(machine,(love,learning))] Skip-gram: 输入为两个pairs,[(machine,love), (machine,learning)]

但是在skip-gram当中,每个词都要收到周围的词的影响,每个词在作为中心词的时候,都要进行K次的预测、调整。因此, 当数据量较少,或者词为生僻词出现次数较少时, 这种多次的调整会使得词向量相对的更加准确。因为尽管cbow从另外一个角度来说,某个词也是会受到多次周围词的影响(多次将其包含在内的窗口移动),进行词向量的跳帧,但是他的调整是跟周围的词一起调整的,grad的值会平均分到该词上, 相当于该生僻词没有收到专门的训练,它只是沾了周围词的光而已。

因此,从更通俗的角度来说:

这里把反向传播视为老师向学生传授知识,skipgram有K个老师,cbow有一个老师

在skip-gram里面,每个词在作为中心词的时候,实际上是 1个学生 VS K个老师,K个老师(周围词)都会对学生(中心词)进行“专业”的训练,这样学生(中心词)的“能力”(向量结果)相对就会扎实(准确)一些,但是这样肯定会使用更长的时间;

cbow是 1个老师 VS K个学生,K个学生(周围词)都会从老师(中心词)那里学习知识,但是老师(中心词)是一视同仁的,教给大家的一样的知识。至于你学到了多少,还要看下一轮(假如还在窗口内),或者以后的某一轮,你还有机会加入老师的课堂当中(再次出现作为周围词),跟着大家一起学习,然后进步一点。因此相对skip-gram,你的业务能力肯定没有人家强,但是对于整个训练营(训练过程)来说,这样肯定效率高,速度更快。

5、Negative sampling的前世今生(实际使用较多)

从式子中我们可以发现,若要求解一组词的出现概率,每次都需要遍历所有的词,也就是分母中的将所有可能的预测词向量和中心词向量相乘并相加,工作量太大。这里工作量大指的是hidden layer到output layer时softmax操作的分母需要计算当前词的向量与所有词的向量乘积,当词库达到百万级别,这个计算量很大。
这里的关键问题是要计算的样本太多。作者转换思路,以窗口内的词为界限,窗口内的中心词和相邻次的同时出现记为正样本,窗口外的词和窗口内的相邻次记为负样本。
既然是一个二分类任务,而不是多分类任务,那么计算概率就不需要使用softmax函数,而是可以使用sigmoid函数,这样就可以避免需要分母上繁重的计算。而对于sigmoid函数而言,计算简单,不再遍历所有负样本,而是采样计算部分样本(一组正样本和k个负样本),那么总体计算量就会大大减少
通过划分为两类样本,要求的概率就变成了:
CBOW和SkipGram - 图22
经过log对数转换后,得到:
CBOW和SkipGram - 图23
假设我们取得负样本为k个,那么公式后半部分为 k 个从 噪声数据集中随机选择的词语出现概率(log形式)的期望值。目标函数需要尽可能的分配高概率给真实的目标词,而低概率给其他 k 个。

负样本采样方法

如果直接以文本中的单词的词频取样,会造成常出现的词在负采样中出现次数多,模型经常做无谓的训练。
如果按照均匀分布随机采样,则采样的负样本没有代表性。
折中策略,采样概率的公式为
image.png
相比于直接使用频次作为权重,取0.75幂的好处可以减弱不同频次差异过大带来的影响,使得小频次的单词被采样的概率变大。

6、hierarchical softmax的前世今生

既然问题的关键是找到一个分类标准,将样本分成正负两类,并选取一定的负样本。那么二叉树也同样能实现这个功能,如果我们使用霍夫曼树替换掉softmax函数,那么实现过程将变成这样:
image.png
hierarchical softmax结构是把输出层改成了一颗哈夫曼树,其中图中白色的叶子节点表示词汇表中所有的|V|个词,黑色节点表示非叶子节点,每一个叶子节点也就是每一个单词,都对应唯一的一条从root节点出发的路径。
因此在途中路过非叶子结点(包括root)时,需要分别知道往左走和往右走的概率。例如到达非叶子节点 的时候往左边走和往右边走的概率分别是:
CBOW和SkipGram - 图26
以上图中目标词为 2为例,
CBOW和SkipGram - 图27
到这里可以看出目标词为 的概率可以表示为:
CBOW和SkipGram - 图28
其中 CBOW和SkipGram - 图29 是非叶子结点 ( , )的向量表示,也是需要训练才能得到,ℎ是隐藏层的输出值。我们的目的是使的w=w2这条路径的概率最大,那么我只需要去更新从根结点到w2这一个叶子结点的路径上面节点的向量即可,而不需要更新所有的词的出现概率,这样大大的缩小了模型训练更新的时间。
虽然这个改进版的方法叫做hierarchical softmax,但是和softmax并没有什么关系。那么为什么使用霍夫曼树呢,首先每一个二叉树就相当于一个分类器,将样本进行了分类,从而可以使用sigmoid函数对概率进行计算。其次霍夫曼树将词频考虑进去,词频越大,越靠近根节点,也就越容易被找到,那么需要进行乘积的次数就越少,最终的概率也就越大。这也符合近邻词出现频率高,被选到的概率更大的现象。

优缺点

提高模型训练的效率。
如果我们的训练样本里的中心词w是一个很生僻的词,则耗时很久。
在实际中,hierarchical softmax对低频词往往表现更好,Negative sampling对高频词和较低维度的向量表现更好。

7、negative sampling和hierarchical softmax详解

待阅读理解