Press "Enter" to skip to content

自己动手实现word2vec(Skip-gram模型)

本站内容均来自兴趣收集,如不慎侵害的您的相关权益,请留言告知,我们将尽快删除.谢谢.

学习word2vec的skip-gram实现,除了skip-gram模型还有CBOW模型。 Skip-gram模式是根据中间词,预测前后词,CBOW模型刚好相反,根据前后的词,预测中间词。
那么什么是 中间词 呢?什么样的词才叫做 前后词 呢?
首先,我们需要定义一个窗口大小,在窗口里面的词,我们才有中间词和前后词的定义。一般这个窗口大小在5-10之间。 举个例子,我们设置窗口大小(window size)为2:

|The|quick|brown|fox|jump|
复制代码

那么, brown 就是我们的中间词, The 、 quick 、 fox 、 jump 就是前后词。
我们知道,word2vec实际上就是一个神经网络(后面会解释),那么这样的数据,我们是以什么样的格式用来训练的呢? 看一张图,你就明白了:

可以看到,我们总是以 中间词 放在第一个位置,然后跟着我们的前后相邻词。可以看到,每一对词都是一个输入和一个输出组成的数据对(X,Y)。其中,X是feature,Y是label。
所以,我们训练模型之前,需要根据语料,整理出所有的像上面这样的输入数据用来训练。

word2vec是一个神经网络

word2vec是一个简单的神经网络,有以下几个层组成:

  • 1个输入层
  • 1个隐藏层
  • 1个输出层

输入层输入的就是上面我们说的数据对的数字表示,输出到隐藏层。 隐藏层的神经网络单元的数量,其实就是我们所说的 embedding size ,只有为什么,我们后面简单计算一下就知道。需要注意的是,我们的隐藏层后面不需要使用激活函数。 输出层,我们使用softmax操作,得到每一个预测结果的概率。
这里有一张图,能够表示这个网络:

输入层

现在问题来了,刚刚我们说,输入层的输入是我们之前准备的数据对的数字表示,那么我们该如何用数字表示文本数据呢?
好像随便一种方式都可以用来表示我们的文本啊。
看上图,我们发现,它的输入使用的是 one-hot 编码。什么是ont-hot编码呢?如图所示,假设有n个词,则每一个词可以用一个n维的向量来表示,这个n维向量只有一个位置是1,其余位置都是0。
那么为什么要用这样的编码来表示呢?答案后面揭晓。

隐藏层

隐藏层的神经单元数量,代表着每一个词用向量表示的维度大小。假设我们的 hidden_size 取300,也就是我们的隐藏层有300个神经元,那么对于每一个词,我们的向量表示就是一个 的向量。 有多少个词,就有多少个这样的向量!
所以对于 输入层 和 隐藏层 之间的权值矩阵 ,它的形状应该是 [vocab_size, hidden_size]的矩阵,

输出层

那么我们的输出层,应该是什么样子的呢?从上面的图上可以看出来,输出层是一个 [vocab_size] 大小的向量,每一个值代表着输出一个词的概率。
为什么要这样输出?因为我们想要知道,对于一个输入词,它接下来的词最有可能的若干个词是哪些,换句话说,我们需要知道它接下来的词的概率分布。
你可以再看一看上面那张网络结构图。
你会看到一个很常见的函数 softmax ,为什么是softmax而不是其他函数呢?不妨先看一下softmax函数长啥样:

很显然,它的取值范围在(0,1),并别所有的值和为1。这不就是天然的概率表示吗?
当然,softmax还有一个性质,因为它函数指数操作,如果 损失函数使用对数函数 ,那么可以抵消掉指数计算。
关于更多的softmax,请看斯坦福Softmax Regression

整个过程的数学表示

至此,我们已经知道了整个神经网络的结构,那么我们应该怎么用数学表示出来呢?
回顾一下我们的结构图,很显然,三个层之间会有两个权值矩阵 ,同时,两个偏置项 。所以我们的整个网络的构建,可以用下面的伪代码:

import tensorflow as tf
# 假设vocab_size = 1000
VOCAB_SIZE = 1000
# 假设embedding_size = 300
EMBEDDINGS_SIZE = 300
# 输入单词x是一个[1,vocab_size]大小的矩阵。当然实际上我们一般会用一批单词作为输入,那么就是[N, vocab_size]的矩阵了
x = tf.placeholder(tf.float32, shape=(1,VOCAB_SIZE))
# W1是一个[vocab_size, embedding_size]大小的矩阵
W1 = tf.Variable(tf.random_normal([VOCAB_SIZE, EMBEDDING_SIZE]))
# b1是一个[1,embedding_size]大小的矩阵
b1 = tf.Variable(tf.random_normal([EMBEDDING_SIZE]))
# 简单的矩阵乘法和加法
hidden = tf.add(tf.mutmul(x,W1),b1)
W2 = tf.Variable(tf.random_normal([EMBEDDING_SIZE,VOCAB_SIZE]))
b2 = tf.Variable(tf.random_normal([VOCAB_SIZE]))
# 输出是一个vocab_size大小的矩阵,每个值都是一个词的概率值
prediction = tf.nn.softmax(tf.add(tf.mutmul(hidden,w2),b2))
复制代码

损失函数

网络定义好了,我们需要选一个损失函数来使用梯度下降算法优化模型。
我们的输出层,实际上就是一个softmax分类器。所以按照常规套路,损失函数就选择 交叉熵损失函数 。
哈哈,还记得交叉熵是啥吗?

p,q是真是概率分布和估计概率分布。

# 损失函数 
cross_entropy_loss = tf.reduce_mean(-tf.reduce_sum(y_label * tf.log(prediction), reduction_indices=[1]))
# 训练操作
train_op = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy_loss)
复制代码

接下来,就可以准备号数据,开始训练啦!

为啥输入使用one-hot编码?

我们知道word2vec训练后会得到一个权值矩阵W1(暂时忽略b1),这个矩阵就是我们的所有词的向量表示啦!这个矩阵的每一行,就是一个词的矢量表示。如果两个矩阵相乘…

看到了吗?ont-hot编码的特点, 在矩阵相乘的时候,就是选取出矩阵中的某一行,而这一行就是我们输入这个词语的word2vec表示! 。
怎么样?是不是很妙?
由此,我们可以看出来,所谓的word2vec,实际上就是一个 查找表 ,是一个 二维 的浮点数矩阵!
以上是word2vec的skip-gram模型的完整分析,怎么样,是不是弄清楚了word2vec的原理和细节?
完整代码请查看 luozhouyang/word2vec

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注