word2vec总结

1. CBOW模型

CBOW模型的训练输入是某一个特征词的上下文相关的词对应的词向量,而输出就是这特定的一个词的词向量。比如下面这段话,我们的上下文大小取值为4,特定的这个词是"Learning",也就是我们需要的输出词向量,上下文对应的词有8个,前后各4个,这8个词是我们模型的输入。由于CBOW使用的是词袋模型,因此这8个词都是平等的,也就是不考虑他们和我们关注的词之间的距离大小,只要在我们上下文之内即可。
在这里插入图片描述
在这个CBOW的例子里,我们的输入是8个词向量,输出是所有词的softmax概率(训练的目标是期望训练样本特定词对应的softmax概率最大),对应的CBOW神经网络模型输入层有8个神经元,输出层有词汇表大小个神经元。隐藏层的神经元个数我们可以自己指定。通过DNN的反向传播算法,我们可以求出DNN模型的参数,同时得到所有的词对应的词向量。这样当我们有新的需求,要求出某8个词对应的最可能的输出中心词时,我们可以通过一次DNN前向传播算法并通过softmax激活函数找到概率最大的词对应的神经元即可。
总结:连续词袋模型是根据上下文预测中心词

1.2 连续词袋模型训练过程

(1)输入层:上下文单词的onehot. (假设词表大小为 V V V,上下文单词个数为 C C C
(2)所有onehot分别乘以共享的输入权重矩阵 W W W. (权重矩阵 W W W V ∗ N V*N VN矩阵, N N N为自己设定的数,随机初始化)
(3)所得的向量 (因为是onehot所以为向量) 相加求平均作为隐层向量, size为 1 ∗ N 1*N 1N
(4)乘以输出权重矩阵 W ′ W^{'} W (shape= N ∗ V N*V NV)
(5)得到向量shape= 1 ∗ V 1*V 1V 激活函数处理得到V-dim概率分布(PS: 因为是onehot,其中的每一维都代表着一个单词)
(6)概率最大的index所指示的单词为预测出的中间词(target word)与true label的onehot做比较,误差越小越好(根据误差更新权重矩阵)
需要定义loss function(一般为交叉熵代价函数),采用梯度下降算法更新W和W’。训练完毕后,输入层的每个单词与矩阵W相乘得到的向量的就是我们想要的词向量(word embedding)
在这里插入图片描述

注意事项
在一些程序中(包括gensim 和 google的 word2vec)并没有用到onehot encoder,而是初始化的时候直接为每个词随机生成一个N维的向量,并且把这个N维向量作为模型参数学习;所以word2vec结构中不存在上图中显示的将V维映射到N维的隐藏层。
本质是一样的,加上 one-hot encoder 层,是为了方便理解,因为这里的 N 维随机向量,就可以理解为是 V 维 one-hot encoder 输入层到 N 维隐层的权重。每个 one-hot encoder 里值是 1 的那个位置,对应的 V 个权重被激活,其实就是『从一个V*N的随机词向量矩阵里,抽取某一行』。学习 N 维向量的过程,也就是优化 one-hot encoder 层到隐含层权重的过程

2. Skip-Gram模型

输入是特定的一个词的词向量,而输出是特定词对应的上下文词向量。还是上面的例子,我们的上下文大小取值为4, 特定的这个词"Learning"是我们的输入,而这8个上下文词是我们的输出。
在这个Skip-Gram的例子里,我们的输入是特定词, 输出是softmax概率排前8的8个词,对应的Skip-Gram神经网络模型输入层有1个神经元,输出层有词汇表大小个神经元。隐藏层的神经元个数我们可以自己指定。通过DNN的反向传播算法,我们可以求出DNN模型的参数,同时得到所有的词对应的词向量。这样当我们有新的需求,要求出某1个词对应的最可能的8个上下文词时,我们可以通过一次DNN前向传播算法得到概率大小排前8的softmax概率对应的神经元所对应的词即可。

word2vec为什么不用现成的DNN模型,要继续优化出新方法呢?最主要的问题是DNN模型的这个处理过程非常耗时。我们的词汇表一般在百万级别以上,这意味着我们DNN的输出层需要进行softmax计算各个词的输出概率的的计算量很大。

3. 霍夫曼树

word2vec使用了CBOW与Skip-Gram来训练模型与得到词向量,相比传统的DNN模型有以下优化。

优化数据结构,用霍夫曼树来代替隐藏层和输出层的神经元,霍夫曼树的叶子节点起到输出层神经元的作用,叶子节点的个数即为词汇表的小大。 而内部节点则起到隐藏层神经元的作用

霍夫曼树的建立过程如下:
输入:权值为(w1,w2,...wn)的n个节点;输出:对应的霍夫曼树
1)将(w1,w2,...wn)看做是有n棵树的森林,每个树仅有一个节点。
2)在森林中选择根节点权值最小的两棵树进行合并,得到一个新的树,这两颗树作为新树的左右子树。新树的根节点权重为左右子树的根节点权重之和。
3)将之前的根节点权值最小的两棵树从森林删除,并把新树加入森林。
4)重复步骤(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合并,得到新子树,依次类推,最终得到下面的霍夫曼树。
在这里插入图片描述
一般得到霍夫曼树后我们会对叶子节点进行霍夫曼编码,由于权重高的叶子节点越靠近根节点,而权重低的叶子节点会远离根节点,这样我们的高权重节点编码值较短,而低权重值编码值较长。这保证的树的带权路径最短,也符合我们的信息论,即我们希望越常用的词拥有更短的编码。一般对于一个霍夫曼树的节点(根节点除外),可以约定左子树编码为0,右子树编码为1.如上图,则可以得到c的编码是00
在word2vec中,约定编码方式和上面的例子相反,即约定左子树编码为1,右子树编码为0,同时约定左子树的权重不小于右子树的权重。

4. Hierarchical Softmax(层次softmax)的模型概述

word2vec对DNN模型做了如下改进
(1) 对于从输入层到隐藏层的映射,没有采取神经网络的线性变换加激活函数的方法,而是采用简单的对所有输入词向量求和并取平均的方法
例:输入的是三个4维词向量(1,2,3,4),(9,6,11,8),(5,10,7,12),那么我们word2vec映射后的词向量就是(5,6,7,8)
(2) 从隐藏层到输出的softmax层这里的计算量改进。为了避免要计算所有词的softmax概率,word2vec用霍夫曼树来代替从隐藏层到输出softmax层的映射

用霍夫曼树代替隐藏层到输出softmax层的映射过程如下:

霍夫曼树的所有内部节点就类似之前神经网络隐藏层的神经元。其中,根节点的词向量对应我们的投影后的词向量,而所有叶子节点就类似于之前神经网络softmax输出层的神经元,叶子节点的个数就是词汇表的大小。在霍夫曼树中,隐藏层到输出层的softmax映射不是一下子完成的,而是沿着霍夫曼树一步步完成的,因此这种softmax取名为"Hierarchical Softmax"
在这里插入图片描述
word2vec中采用了二元逻辑回归的方法,即规定沿着左子树走,那么就是负类,沿着右子树走,那么就是正类。判别正类和负类的方法是使用sigmoid函数,即: P ( + ) = σ ( x w T θ ) = 1 1 + e − x w T θ P(+)=\sigma\left(x_{w}^{T} \theta\right)=\frac{1}{1+e^{-x_{w}^{T} \theta}} P(+)=σ(xwTθ)=1+exwTθ1

其中 x w x_w xw是当前内部节点的词向量,而 θ θ θ则是我们需要从训练样本求出的逻辑回归的模型参数。

使用霍夫曼树的好处:之前计算量为V,现在变成了 l o g 2 V log_2V log2V。其次由于使用霍夫曼树,高频的词靠近树根,这样高频词需要更少的时间会被找到,这符合我们的贪心优化思想

被划分为左子树而成为负类的概率为P(−)=1−P(+)。在某一个内部节点,要判断是沿左子树还是右子树走的标准就是看P(−),P(+)谁的概率值大。而控制P(−),P(+)谁的概率值大的因素一个是当前节点的词向量,另一个是当前节点的模型参数θ。

上图中 w 2 w_2 w2如果它是一个训练样本的输出,那么我们期望对于里面的隐藏节点n(w2,1)的P(−)概率大,n(w2,2)的P(−)概率大,n(w2,3)的P(+)概率大

补充解释
取一个适当大小的窗口当做语境,输入层读入窗口内的词,将它们的向量(K维,初始随机)加在一起,形成隐藏层K个节点。输出层是一个巨大的二叉 树,叶节点代表语料里所有的词(语料含有V个独立的词,则二叉树有|V|个叶节点)。而这整颗二叉树构建的算法就是Huffman树。这样,对于叶节点的每一个词,就会有一个全局唯一的编码,形如"010011",记左子树为1,右子树为0。接下来,隐层的每一个节点都会跟二叉树的内节点有连边,于是对于二叉树的每一个内节点都会有K条连边,每条边上也会有权值。
对于语料库中的某个词 w t w_t wt,对应着二叉树的某个叶子节点,因此它必然有一个二进制编码,如"010011"。在训练阶段,给定上下文,要预测后面的词 w t w_t wt的时候,就从二叉树的根节点开始遍历,这里的目标就是预测这个词的二进制编号的每一位。即给定的上下文,我们的目标是使得预测词的二进制编码概率最大。形象地说,我们希望在根节点,词向量和与根节点相连经过 logistic 计算得到 bit=1 的概率尽量接近 0,在第二层,希望其 bit=1 的概率尽量接近1,这么一直下去,我们把一路上计算得到的概率相乘,即得到目标词 w t w_t wt在当前网络下的概率 P ( w t ) P(w_t) P(wt),那么对于当前这个 sample的残差就是 1 − P ( w t ) 1-P(w_t) 1P(wt),于是就可以使用梯度下降法训练这个网络得到所有的参数值了。显而易见,按照目标词的二进制编码计算到最后的概率值就是归一化的。

Hierarchical Softmax用Huffman编码构造二叉树,其实借助了分类问题中,使用一连串二分类近似多分类的思想。例如我们是把所有的词都作为输出,那么“桔 子”、“汽车”都是混在一起。给定 w t w_t wt的上下文,先让模型判断 w t w_t wt是不是名词,再判断是不是食物名,再判断是不是水果,再判断是不是“桔子”。
在训练过程中,模型会赋予这些抽象的中间结点一个合适的向量,这个向量代表了它对应的所有子结点。因为真正的单词公用了这些抽象结点的向量,所以Hierarchical Softmax方法和原始问题并不是等价的,但是这种近似并不会显著带来性能上的损失同时又使得模型的求解规模显著上升

传统的Softmax直接从隐层直接计算每一个输出的概率,就需要对|V|中的每一个词都算一遍,这个过程时间复杂 度是O(|V|)的。而使用了二叉树(如Word2vec中的Huffman树),其时间复杂度就降到了O(log2(|V|)),速度大大地加快了。

5. 负采样近似训练

有一个训练样本,中心词是 w w w ,它周围上下文共有 2 c 2c 2c个词,记为 c o n t e x t ( w ) context(w) context(w)。由于这个中心词 的确和 c o n t e x t ( w ) context(w) context(w)相关存在,因此它是一个真实的正例。通过Negative Sampling采样,我们得到neg个和 w w w 不同的中心词 w i w_i wii=1,2,..neg,这样 c o n t e x t ( w ) context(w) context(w) w i w_i wi 就组成了neg个并不真实存在的负例。利用这一个正例和neg个负例,我们进行二元逻辑回归,得到负采样对应每个词 w i w_i wi 对应的模型参数 θ i θ_i θi和每个词的词向量。

Negative Sampling负采样方法
word2vec采样的方法并不复杂,如果词汇表的大小为V,那么我们就将一段长度为1的线段分成V份,每份对应词汇表中的一个词。当然每个词对应的线段长度是不一样的,高频词对应的线段长,低频词对应的线段短。每个词 的线段长度由下式决定:
len ⁡ ( w ) = count ⁡ ( w ) ∑ u ∈  vocab  count ⁡ ( u ) \operatorname{len}(w)=\frac{\operatorname{count}(w)}{\sum_{u \in \text { vocab }} \operatorname{count}(u)} len(w)=u vocab count(u)count(w)
在word2vec中,分子和分母都取了3/4次幂如下
len ⁡ ( w ) = count ⁡ ( w ) 3 / 4 ∑ u ∈  vocab  count ⁡ ( u ) 3 / 4 \operatorname{len}(w)=\frac{\operatorname{count}(w)^{3 / 4}}{\sum_{u \in \text { vocab }} \operatorname{count}(u)^{3 / 4}} len(w)=u vocab count(u)3/4count(w)3/4
在采样前,我们将这段长度为1的线段划分成M等份,这里M>>V,这样可以保证每个词对应的线段都会划分成对应的小块。而M份中的每一份都会落在某一个词对应的线段上。在采样的时候,我们只需要从M个位置中采样出neg个位置就行,此时采样到的每一个位置对应到的线段所属的词就是我们的负例词。在word2vec中,M取值默认为 1 0 8 10^8 108
在这里插入图片描述

6. 一些推导公式

跳字模型
用一个词来预测它在文本序列周围的词

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
连续词袋模型
用在文本序列周围的词来预测中心词
在这里插入图片描述
损失函数
在这里插入图片描述
在这里插入图片描述
Uc表示中心词向量 V表示背景词
在这里插入图片描述
在这里插入图片描述

7. 负采样程序

负采样的整体过程如下:
对于skip-gram模型,我们要采样的负样本作为背景词,首先根据每个词的被选为负样概率 p ( w ) p(w) p(w)选出若干个词(一般量比较大,如1e5个),然后再在这若干个词中选出K组负样本。

def get_negative(all_contexts, sampling_weights, K):
	'''
	args:
		all_contexts: 所有的背景词,shape=[[],[],[]]
		sampling_weights: 采样到每个词的概率=词出现的频率的0.75次方
		K:负采样个数
	'''
	all_negatives, neg_candidates, i = [], [], 0
	population = list(range(len(sampling_weights)))
	for contexts in all_contexts:
		negatives = []
		# 本程序基于skipgram 模型,因此负样本为背景词,要选择k组背景词负样本
		while len(negatives)<len(contexts)*K:
			if i == len(neg_candidates):
				# 先选出大量负样本再从这些负样本中选择所需个数的负样本
				i, neg_candidates = 0, random.choices(population, sampling_weights, k=int(1e5))
			neg, i = neg_candidates[i], i+1
			if neg not in set(contexts):
				negatives.append(neg)
		all_negatives.append(negatives)
	return all_negatives

8. skip_gram程序

def skip_gram(center, contexts_and_negatives, embed_v, embed_u):
    v = embed_v(center)
    u = embed_u(contexts_and_negatives)
    pred = torch.bmm(v, u.permute(0, 2, 1))
    return pred
net = nn.Sequential(nn.Embedding(num_embeddings=len(idx_to_token), embedding_dim=embed_size),
                    nn.Embedding(num_embeddings=len(idx_to_token), embedding_dim=embed_size))
pred = skip_gram(center, context_negative, net[0], net[1])

len(idx_to_token) = 512
embed_size = 100
那么v.shape = [512, 1, 100], u.shape = [512, 60, 100]

问题: 为啥skip_gram的前向传播直接是 v u T vu^T vuT
为了便于说明,我们从 v v v u u u中直接拿一个"矩阵"来说明:
v v v中拿一个batch, 假设为 v 1 v_1 v1,且shape=[1, 100]; 从 u u u中拿一个batch, 假设为 u 1 u_1 u1,且shape=[60,100]; 为了方便举例,我们将两者的形状再次缩小,设 v 1 v_1 v1.shape=[1, 3], u 1 u_1 u1.shape=[2, 3], 即设 v 1 = [ 0.5, 1, − 0.1 ] v_1=\left[ \text{0.5, 1,}-0.1 \right] v1=[0.5, 1,0.1] u 1 = [ 0.1, 0.5,  0.2 − 0.5, 0.3,  0.8 ] u_1=\left[ \begin{array}{c} \text{0.1, 0.5, }0.2\\ -\text{0.5, 0.3, }0.8\\ \end{array} \right] u1=[0.1, 0.5, 0.20.5, 0.3, 0.8]
其中 v 1 v_1 v1的一行代表一个中心词的向量; u 1 u_1 u1的每一行代表中心词上下文及噪声词的向量,所以 v 1 u 1 T v_1u_1^T v1u1T就表示中心词与每个上下文的词或噪声词的内积,假设 u 1 u_1 u1的第一行是上下文词, 第二行是噪声词的向量,那么 v 1 v_1 v1 u 1 u_1 u1第一行做内积结果应该趋向(上下文之间的词相似性更高), v 1 v_1 v1 u 1 u_1 u1第二行做内积结果应该趋向0(噪声词和中心词没啥关联),正好我们设置的label = [1, 0]这种格式, 通过交叉熵损失函数,迭代优化后会调整 u 1 u_1 u1 v 1 v_1 v1的值(实际上是调整embed_v和embed_u),使得内积结果趋向label; 最终的词向量是embed_v,它每一行代表一个词的向量, 每个词的索引和行数对应

未完,待更新…
完整程序见:word2vec.py
原理参考:
[1].word2vec原理(三) 基于Negative Sampling的模型
[2].本地word2vec总结
[3].动手学深度学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值