203

word2vec笔记和实现

 5 years ago
source link: http://blog.stupidme.me/2018/08/25/word2vec笔记和实现/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

学习word2vec的skip-gram实现,除了skip-gram模型还有CBOW模型。

Skip-gram模式是根据中间词,预测前后词,CBOW模型刚好相反,根据前后的词,预测中间词。

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

首先,我们需要定义一个窗口大小,在窗口里面的词,我们才有中间词和前后词的定义。一般这个窗口大小在5-10之间。

举个例子,我们设置窗口大小(window size)为2:

|The|quick|brown|fox|jump|
 

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

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

看一张图,你就明白了:

jANNRfy.png!web

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

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

word2vec是一个神经网络

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

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

输入层输入的就是上面我们说的数据对的数字表示,输出到隐藏层。

隐藏层的神经网络单元的数量,其实就是我们所说的 embedding size ,只有为什么,我们后面简单计算一下就知道。需要注意的是,我们的隐藏层后面不需要使用激活函数。

输出层,我们使用softmax操作,得到每一个预测结果的概率。

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

mEbmi2e.png!web

输入层

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

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

看上图,我们发现,它的输入使用的是 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),这个矩阵就是我们的所有词的向量表示啦!这个矩阵的每一行,就是一个词的矢量表示。如果两个矩阵相乘…

aeIR3uy.png!web

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

怎么样?是不是很妙?

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

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

完整代码请查看 luozhouyang/word2vec

联系我

  • Email: [email protected]
  • WeChat: luozhouyang0528

    fqErmq7.jpg!web
  • 个人公众号,你可能会感兴趣:

    jA36neI.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK