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

学习word2vec的skip-gram实现,除了skip-gram模型还有CBOW模型。 Skip-gram模式是根据中间词,预测前后词,CBOW模型刚好相反,根据前后的词,预测中间词。

那么什么是中间词呢?什么样的词才叫做前后词呢?

首先,我们需要定义一个窗口大小,在窗口里面的词,我们才有中间词和前后词的定义。一般这个窗口大小在5-10之间。 举个例子,我们设置窗口大小(window size)为2:

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

那么,brown就是我们的中间词,Thequickfoxjump就是前后词。

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

word2vec window

可以看到,我们总是以中间词放在第一个位置,然后跟着我们的前后相邻词。可以看到,每一对词都是一个输入和一个输出组成的数据对(X,Y)。其中,X是feature,Y是label。

所以,我们训练模型之前,需要根据语料,整理出所有的像上面这样的输入数据用来训练。

word2vec是一个神经网络

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

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

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

这里有一张图,能够表示这个网络:

skip-gram-net-arhc

输入层

现在问题来了,刚刚我们说,输入层的输入是我们之前准备的数据对的数字表示,那么我们该如何用数字表示文本数据呢?

好像随便一种方式都可以用来表示我们的文本啊。

看上图,我们发现,它的输入使用的是one-hot编码。什么是ont-hot编码呢?如图所示,假设有n个词,则每一个词可以用一个n维的向量来表示,这个n维向量只有一个位置是1,其余位置都是0。

那么为什么要用这样的编码来表示呢?答案后面揭晓。

隐藏层

隐藏层的神经单元数量,代表着每一个词用向量表示的维度大小。假设我们的hidden_size取300,也就是我们的隐藏层有300个神经元,那么对于每一个词,我们的向量表示就是一个1*N的向量。 有多少个词,就有多少个这样的向量!

所以对于输入层隐藏层之间的权值矩阵W,它的形状应该是[vocab_size, hidden_size]的矩阵,

输出层

那么我们的输出层,应该是什么样子的呢?从上面的图上可以看出来,输出层是一个[vocab_size]大小的向量,每一个值代表着输出一个词的概率。

为什么要这样输出?因为我们想要知道,对于一个输入词,它接下来的词最有可能的若干个词是哪些,换句话说,我们需要知道它接下来的词的概率分布。

你可以再看一看上面那张网络结构图。

你会看到一个很常见的函数softmax,为什么是softmax而不是其他函数呢?不妨先看一下softmax函数长啥样:

softmax(x) = \frac{e^x}{\sum_{i}^{N}e^{x_i}}

很显然,它的取值范围在(0,1),并别所有的值和为1。这不就是天然的概率表示吗?

当然,softmax还有一个性质,因为它函数指数操作,如果损失函数使用对数函数,那么可以抵消掉指数计算。

关于更多的softmax,请看斯坦福Softmax Regression

整个过程的数学表示

至此,我们已经知道了整个神经网络的结构,那么我们应该怎么用数学表示出来呢?

回顾一下我们的结构图,很显然,三个层之间会有两个权值矩阵W,同时,两个偏置项b。所以我们的整个网络的构建,可以用下面的伪代码:

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分类器。所以按照常规套路,损失函数就选择交叉熵损失函数

哈哈,还记得交叉熵是啥吗?

H(p,q)=-\sum_{x}p(x)logq(x)

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),这个矩阵就是我们的所有词的向量表示啦!这个矩阵的每一行,就是一个词的矢量表示。如果两个矩阵相乘...

matrix_mult_w_one_hot

看到了吗?ont-hot编码的特点,在矩阵相乘的时候,就是选取出矩阵中的某一行,而这一行就是我们输入这个词语的word2vec表示!

怎么样?是不是很妙?

由此,我们可以看出来,所谓的word2vec,实际上就是一个查找表,是一个二维的浮点数矩阵!

以上是word2vec的skip-gram模型的完整分析,怎么样,是不是弄清楚了word2vec的原理和细节?

完整代码请查看luozhouyang/word2vec

联系我

  • Email: [email protected]

  • WeChat: luozhouyang0528

  • 个人公众号,你可能会感兴趣:

猜你喜欢

转载自juejin.im/post/5b986f296fb9a05d11176a15
今日推荐