NLP 笔记:Skip-gram

1 skip-gram举例

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

假设我们的窗口大小skip-window=2,中心词为“loves”,那么上下文的词即为:“the”、“man”、“his”、“son”。这里的上下文词又被称作“背景词”,对应的窗口称作“背景窗口”。

跳字模型能帮我们做的就是,通过中心词(target word)“loves”,生成与它距离不超过2的背景词(context)“the”、“man”、“his”、“son”的条件概率,用公式表示即:

                                

进一步,假设给定中心词的情况下,背景词之间是相互独立的,公式可以进一步得到

            

用概率图表示为:

可以看得出来,这里是一个一对多的情景,根据一个词来推测2m个词,(m表示背景窗口的大小)。

2 skip-gram 逐步解析

2.1  one-hot word symbol(编码)

NLP学习笔记:word2vec_刘文巾的博客-CSDN博客 中我们说过 one-hot编码有很多的弊端(需要的空间很冗余,且无法反映单词之间的相似程度)。

但是计算机没办法识别“字符”,所有的数据必须转化成二进制的编码形式,one-hot编码就是最便捷的一种表达方式了。(这里的one-hot只是一种过渡)

2.2 词嵌入(word embedding)

但是,one-hot表达的向量过于稀疏。我们需要用一个稠密的向量来表示单词空间。(这个向量是可以在空间中表征一定单词的含义)

我们必须先初始化一些这样的表示单词的稠密向量,然后通过学习的方式来更新向量的值(权重),然后得到我们最终想要的向量。

2.1中得到的每一个单词的one-hot编码,都需要映射到一个稠密的向量编码中。

这个映射过程,就被称作是嵌入(embedding),因为是单词的映射,所以被叫做词嵌入(word embedding)。

假设我们要把一个单词映射到一个300维(这个300维是经过大量的实验得出的,一般的映射范围是200-500维)的向量中,那么我们直观的做法就是:矩阵运算。

因为我们每个单词现在的情况是N维的一个向量(N是字典中单词的数量),如果映射到300维,需要的权重矩阵就是N ∗ 300,这样得到的矩阵就是一个1 ∗ 300的矩阵(向量)。

2.2.1 词嵌入举例

假设我们字典中有5个单词(即N=5),那么word embedding如下图所示:

通过上图,我们可以发现一个规律:

计算出来的这个“稠密的”向量,和我们单词在one-hot编码表的位置是有关系的。

如果单词的one-hot编码中,第i维是1,那么生成的稠密词向量就是编码表的第i行。

所以我们word embedding生成稠密词向量的过程,就是一个查找编码表的过程。而这个编码表,我们可以想成是一个神经网络的隐藏层(这个隐藏层没有激活函数)。通过这个隐藏层,我们把one-hot编码映射到一个低维度的空间里面。

3 skip-gram 数学原理

在skip-gram中,每个词被表示成两个d维向量,用来计算条件概率。

假设这个词在词典中索引为i,当它为中心词时向量表示为 \boldsymbol{v}_i\in\mathbb{R}^d,而为背景词时向量表示为\boldsymbol{u}_i\in\mathbb{R}^d

设中心词w_c在词典中索引为c,背景w_o在词典中索引为o,给定中心词生成一个背景词的条件概率可以通过对向量内积做softmax运算而得到:

exp(u_o^Tv_c)可以想成是以c为中心词,o为背景词的概率权重;

分母相当于以c为中心词,生成其他各个词的概率权重。

所以二者一除就是给定中心词生成某一个背景词的条件概率

由上面那个式子,我们可以得到:每一个中心词,推断两边分别m个位置的背景词的概率:

其中,t表示窗口中心词的位置;m表示推断窗口的大小

我们为了求出“最好”的word embedding,可以最大化上面那个概率。

基于此,我们可以使用最大似然估计的方法。

但是一个函数的最大值往往不容易计算,因此,我们可以通过对函数进行变换,从而改变函数的增减性,以便优化。

其中,我们把带到上式中,可以得到

通过上面一段的操作,我们把一个求最大值的问题转换成了一个求最小值的问题。

求最小值,我们可以使用梯度下降法。

在优化之前,我们要搞清楚我们优化的目标是那个,哪些是参数。

我们要做一个假设,在对中心词权重更新时,背景词的权重是固定的,然后在以同样的方式来更新背景词的权重。

接下来我们求导计算梯度

这里就计算出来了中心词的梯度,可以根据这个梯度进行迭代更新。

对于背景词的更新是同样的方法。(就是上式不是对v_c的偏导数,而是每个w_o的偏导数了。

但是要注意背景词的个数不是唯一的,所以更新的时候要逐个更新。

训练结束后,对于词典中的任一索引为i ii的词,我们均得到该词作为中心词和背景词的两组词向量v_iu_i

还有一个小问题要说明一下:

在词典中所有的词都有机会被当做是中心词和背景词。在更新的时候,都会被更新一遍,这种时候该怎么确定一个词的向量到底该怎么选择呢?

——在NLP应用中,一般使用中心词向量作为词的表征向量。

4 skip-gram流程总结

1)one-hot编码。

每个单词形成V∗1的向量;对于整个词汇表就是V∗V的矩阵。

2)word embedding 词嵌入。

根据索引映射,将每个单词映射到d维空间。

通过这样的方式就可以将所有的单词映射到矩阵W上(矩阵W的形状为V∗d),并且每个单词与矩阵中的某列一一对应。


3)skip-gram模型训练。

初始化一个d维空间的矩阵作为权重矩阵W ′ ,该矩阵的形状为V ∗ d 。

值得注意的是,目前我们已经有了两个d维空间的矩阵,要清楚这两个矩阵都是干什么的。

矩阵W的每一行对应的是单词作为中心词时的向量v_i

这里初始化的矩阵W'的每一列则对应的是单词作为背景词时的向量u_i

每个词都有机会成为中心词,同时也会成为其他中心词的背景词,因为窗口一直再变动。

4)取出一个词作为中心词。该词的词向量v_c(它的形状为d维的向量1∗d),与背景词权重矩阵W‘中的其他词做内积。

此时会得到一个背景词的计算结果,即: v_c*u_o^T(假设背景词的索引为o)。

重复计算,我们可以得到v_c作为中心词的时候,字典中每一个词作为背景词的概率权重。(字典中有V个词,我们就可以得到V个概率权重)

5)softmax层的映射。

在上一步中得到了V个背景词的概率权重,那么此时我们需要做的就是计算某一个词作为背景词的条件概率,即

6)概率最大化。

skip-gram的学习相当于监督学习,我们是有明确的背景词输入的。(就是知道这个地方的背景词应该是哪个)

我们期望窗口内的词的输出概率最大。因此我们的目标变为极大化概率P(w_o \mid w_c)

在不影响函数单调性的前提下我们变为极大化函数: \log P(w_o \mid w_c)l(对数似然)。

但是,计算一个函数的最大值不如计算一个函数的最小值来的方便,因此这里给这个函数进行单调性变换:-\log P(w_o \mid w_c)

7)极小化目标函数。

通过上面的变换,此时已经变为一个数学优化问题,梯度下降法更新参数。

8)更新中心词的向量v_c

v_c := v_c - \alpha * \nabla P(w_o \mid w_c)

后面减去的即为梯度:\nabla P(w_o \mid w_c)=\boldsymbol{u}_o - \sum_{j \in (c-m,0) \cup (0,c+m) } \left(\frac{\text{exp}(\boldsymbol{u}_j^\top \boldsymbol{v}_c)}{ \sum_{i \in \mathcal{V}} \text{exp}(\boldsymbol{u}_i^\top \boldsymbol{v}_c)}\right) \boldsymbol{u}_j

9) 8对应的梯度里面,相比于5,又多了一层对于j的求和,这个下标代表的背景词,m是窗口的大小。这么做才能与我们最初的设定相对应:一个中心词,推断多个背景词。

10).根据同样的原理,在窗口进行移动了之后,可以进行同样的更新。

11.在词汇表中所有的词都被训练之后。我们得到了每个词的两个词向量分别为v_i,u_iv_i是作为中心词时的向量,我们一般用这个作为最终的选择。u_i对应的就是作为背景词时的向量了。

 5 skip-gram 优化

我们现在有一个包含10000个单词的词汇表,向量特征为300维。

那么对于这个词汇量的skip-gram,将会有两个weights矩阵----一个隐藏层和一个输出层。

这两层都会有一个300x10000=3000000的权重矩阵。

在如此大的神经网络上进行梯度下降是非常慢的,更加严重的是,我们需要大量训练数据去调整权重,以及避免过拟合。

在论文“Distributed Representations of Words and Phrases and their Compositionality”中,主要有三种方式来优化skip-gram:

5.1 在模型中将共同出现的单词对或者短语当做单个“词”

比如像“Boston Globe”(“波士顿环球报”,一种报纸)这样的单词对,在作为单独的“Boston”和“Globe”来进行看待的时候有着非常不同的含义。

所以在将它作为一整个“Boston Globe”来说是非常有意义的,不论它出现在文中哪里,都将一整个当成一个单词来看待,并且有它自己的词向量。

论文中使用了1千亿个来自谷歌新闻数据集的单词来进行训练。额外增加的短语使得这个模型的单词尺寸缩减到了3百万个单词!

5.2 二次采样

在一个很大的语料库中,有一些很高频率的一些词(比如冠词 a,the ;介词 in,on等)。

这些词相比于一些出现的次数较少的词,提供的信息不多。同时这些词会导致我们很多的训练是没有什么作用的。

为了缓解这个问题,论文中提出了二次采样的思路:每一个单词都有一定概率被丢弃。这个概率为:

f(wi)是单词出现的次数与总单词个数的比值。(例如“peanut”在1 billion 单词语料中出现了1000次,那么z('peanut')=1E-6)

t是一个选定的阈值,一般在1E-5左右。

5.3 负采样

在训练skip-gram时,每接受一个训练样本,就需要调整所有神经单元权重参数,来使神经网络预测更加准确。【分布式所有 单词对的 exp值的和】

换句话说,每个训练样本都将会调整所有神经网络中的参数。

词汇表的大小决定了我们skip-gram 神经网络将会有一个非常大的权重参数,并且所有的权重参数会随着数十亿训练样本不断调整。

负采样的思路:每次让一个训练样本仅仅更新一小部分的权重参数,从而降低梯度下降过程中的计算量。

如果词汇表的大小为1万, 当输入样本 ( "fox", "quick") 到神经网络时,在输出层我们期望对应 “quick” 单词的那个神经元结点输出 1,其余 9999 个都应该输出 0。在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们称之为 negative word。

负采样的想法也很直接 ,将随机选择一小部分的 negative words,比如选 10个 negative words 来更新对应的权重参数。

在论文中作者指出指出对于小规模数据集,建议选择 5-20 个 negative words,对于大规模数据集选择 2-5个 negative words.【每一个correct pair配这么多个negative pair】

如果使用了负采样,那么 仅仅需要去更新positive word- “quick” 和选择的其他 10 个negative words 的结点对应的权重,共计 11 个输出神经元,相当于每次只更新 300 x 11 = 3300 个权重参数。对于 3百万 的权重来说,相当于只计算了千分之一的权重,这样计算效率就大幅度提高。

这里指的权重是前面说的W'的权重,也就是背景词矩阵的权重

 

5.3.1 负样本(negative words)的选择

论文中使用 一元模型分布 (unigram distribution) 来选择 negative words。

一个单词被选作 负样本 的概率跟它出现的频次有关,出现频次越高的单词越容易被选作负样本,经验公式为:

其中:

f(w) 代表 每个单词被赋予的一个权重,即 它单词出现的词频。

分母 代表所有单词的权重和。

公式中指数3/4完全是基于经验的,论文中提到这个公式的效果要比其它公式更加出色。

5.3.2 正样本的概率

对于正样本,Skig-Gram 模型的输出是

引入负样本之后,我们计算概率的函数不像之前是softmax了,而是sigmoid函数。

5.3.3 负样本的概率

对于负样本,Skig-Gram 模型的输出是

5.3.4 总样本

参考文献

NLP之---word2vec算法skip-gram原理详解_Ricky-CSDN博客_skipgram

Word2Vec教程-Skip-Gram模型 - 简书 (jianshu.com)

Word2Vec教程-Negative Sampling 负采样 - 简书 (jianshu.com)

countries.capitals.projections.eps (arxiv.org)

Word2Vec导学第二部分 - 负采样_陈诚的博客-CSDN博客_负采样

Word2vec, Skip-Gram, CBOW, NEG, 负采样 - 知乎 (zhihu.com)

  • 31
    点赞
  • 104
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
skip-gram 是一种词向量表示方法,用于将单词转换为向量表示。在实际应用中,我们可以使用类似于 TensorFlow 和 PyTorch 这样的深度学习框架来训练 skip-gram 模型。 以下是使用 TensorFlow 进行 skip-gram 模型训练的示例代码: ```python import tensorflow as tf # 定义训练数据集 data = ["apple", "banana", "orange", "grape", "pear"] vocabulary = list(set("".join(data))) vocabulary_size = len(vocabulary) # 定义 skip-gram 模型 embedding_size = 32 num_sampled = 3 inputs = tf.placeholder(tf.int32, shape=[None]) labels = tf.placeholder(tf.int32, shape=[None, 1]) embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0)) embed = tf.nn.embedding_lookup(embeddings, inputs) nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size], stddev=1.0 / tf.sqrt(embedding_size))) nce_biases = tf.Variable(tf.zeros([vocabulary_size])) loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights, biases=nce_biases, labels=labels, inputs=embed, num_sampled=num_sampled, num_classes=vocabulary_size)) # 定义优化器和训练操作 optimizer = tf.train.AdamOptimizer().minimize(loss) # 训练模型 num_epochs = 1000 batch_size = 5 with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for epoch in range(num_epochs): for i in range(len(data)): batch_inputs = [vocabulary.index(c) for c in data[i]] batch_labels = [[vocabulary.index(c)]] * len(data[i]) _, loss_val = sess.run([optimizer, loss], feed_dict={inputs: batch_inputs, labels: batch_labels}) print("epoch %d, iteration %d, loss: %f" % (epoch, i, loss_val)) ``` 在这个示例中,我们首先定义了训练数据集和词汇表,然后使用 TensorFlow 定义了 skip-gram 模型,并使用交叉熵作为损失函数。接着,我们定义了 Adam 优化器来最小化损失,并使用训练数据对模型进行训练。在每个 epoch 和每个 iteration 中,我们会输出当前的损失值。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UQI-LIUWJ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值