概述
Word2vec在NLP里占有非常重要的地位,是很多深度学习技术的基础,那它解决了什么问题?是什么?为什么能解决呢?下面详细地介绍各个部分。
Word2vec解决什么问题?
我们知道,在一般的机器学习中,数据分为两大类,一类是数值型,另一类是字符型数据:对于数值型数据,能够有效地处理,可以直接用于数据拟合,回归分析中;对于字符型数据,如常见的性别(男,女),省份等,我们会把他们one-hot编码,这样由原来的一列数据增加若干列数据,当我们的一列数据具有大量不同的值时,扩增的列也将大幅增加,这在机器学习中会极大影响模型的学习,降低学习的效果。
而在NLP中,我们的数据全部是字符型数据,one-hot编码根本不现实!当然,也可以这样做,我们得到每个词的维度将是词汇表的大小,至少数万级别。
为此,我们需要找到其他的方法,将每个词的表示降维,如果从one-hot的结果出发,我们利用PCA, SGD等方法也可以降维,但代价是巨大的,不仅消耗大量计算资源,而且可扩展性差,上述降维技术要求把所有的数据一次性输入,若我们增加一些数据,则需要重新训练。
Word2vec怎么解决的
泛化地说:word2vec得到每个词的词向量并不是一步完成的,首先为每个词初始化一个随机向量,每次训练一个滑动窗口内的数据(一般是5个词),将滑动窗口内某个(些)词作为训练数据,其他词作为预测数据完成一次训练,更新词的向量参数,这样经过多次迭代后,每个词的向量会达到稳定,完成词向量的学习。
具体地说:
Word2vec分为两个模型:CBOW(Continuous Bag-of-Words Model) 模型和 Skip-gram模型,它们的区别如图:
CBOW模型
首先介绍CBOW模型,上图给出了模型的结构,包括三层:输入层,投影层和输出层;每次的输入和输出都是在一个滑动窗口内。举例而言, w w w为一个词, C o n t e x t c ( w ) Context_c(w) Contextc(w)为词前后长度为c的所有词,这2c+1个词组成一个滑动窗口;
- 输入层:输入内容为中心词 w w w外的所有词,具体内容是把每个词转化为表示为one-hot编码,此时每个词的维度为词汇表的大小。
- 投影层:输入层为 2 c ⋅ V 2c \cdot V 2c⋅V的维度,输入到投影层有一个参数矩阵 V ⋅ m V\cdot m V⋅m(其中m为我们预设的词向量维度) ,经计算得到 2 c ⋅ m 2c \cdot m 2c⋅m的矩阵,投影层将 2 c 2c 2c个向量做求和累加,即 x w = ∑ i = 1 2 c v w x_w=\sum_{i=1}^{2c}v_w xw=∑i=12cvw
- 输出层:经过投影层后,我们得到 1 ∗ m 1*m 1∗m维的向量,这就是我们模型的输出,那怎么将他和真实的label(也即是 w w w)关联在一起,构造合适的损失函数进行学习?这里有三种思路:
传统做法
首先想到的是将得到的向量与新的矩阵M(维度为 V ∗ m V*m V∗m)计算相似度,得到 1 ∗ V 1*V 1∗V个值,然后进行softmax处理,得到 1 ⋅ m 1\cdot m 1⋅m维向量,将其与为真实词对应的向量 v m v_m vm比较设置目标函数,进行更新,即完成一次参数更新。
上述过程的简单代码表示即:
# 输入层-投影层
EMBEDDING_DIM = 5 # you can choose your own number
W1 = tf.Variable(tf.random_normal([vocab_size, EMBEDDING_DIM]))
b1 = tf.Variable(tf.random_normal([EMBEDDING_DIM])) #bias
hidden_representation = tf.add(tf.matmul(x,W1), b1)
# 输出层
prediction = tf.nn.softmax(tf.add( tf.matmul(hidden_representation, W1), b1))
# 设置目标函数
cross_entropy_loss = tf.reduce_mean(-tf.reduce_sum(y_label * tf.log(prediction), reduction_indices=[1]))
# 更新
train_step = tf.train.GradientDescentOptimizer(0.1).minimize(cross_entropy_loss)
sess = tf.Session()
init = tf.global_variables_initializer()
sess.run(init)
sess.run(train_step, feed_dict={
x: x_train, y_label: y_train})
注意此时,我们得到两个矩阵,前面为input_embedding, 后面为output_embedding,训练后输入和输出的向量效果几乎一样,而把这两个矩阵设为同样的权重则效果更差。
Hierarchical Softmax
- 首先介绍Humman Tree
由名字可知,Huffman Tree是一种树结构,它的定义是:
给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度达到最小,
称这样的二叉树为最优二叉 树,也称为霍夫曼树(Huffman Tree).
我们以每个词出现频率作为权重,构建Huffman Tree,具体过程如下[1]:
输入: 权值为 ( w 1 , w 2 , . . . , w n ) (w_1, w_2, ...,w_n) (w1,w2,...,wn)的n个节点
输出:一颗Huffman 树
- 将 ( w 1 , w 2 , . . . , w n ) (w_1, w_2, ...,w_n) (w1,w2,...,wn)看做是有n棵树的森林,每个树仅有一个节点。
2)在森林中选择根节点权值最小的两棵树进行合并,得到一个新的树,这两颗树分布作为新树的左右子树。新树的根节点权重为左右子树的根节点权重之和。
3)将之前的根节点权值最小的两棵树从森林删除,并把新树加入森林。- 重复步骤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合并,得到新子树,依次类推,最终得到下面的霍夫曼树。
- 为什么使用Huffman tree
由传统做法虽然可以求得词向量,但计算量很大,耗资源严重,为了解决上述问题,避免计算所有词的softmax概率,引入Huffman Tree;具体的做法是:
对于我们要预测的标签,也就是中间的词,先将其进行Huffman编码,实际上事先需要对所有的词进行Huffman编码,这里对于所有词汇组成的Huffman Tree,我们可以约定左子树编码为1,右子树为0,这样我们可能得到中心词 w w w的编码为 010 010 010
由真实值的编码和隐藏层的向量,怎么利用设置目标函数呢?
这里,我们采用二元逻辑回归的方法,即如果编码为1,那就是负类,沿左子树走;如果编码为0,那沿着右子树走,就是正类,举例,当为1时:
P ( + ) = σ ( x w T θ ) = 1 1 + e − x w T θ P(+) = \sigma(x_w^T\theta) = \frac{1}{1+e^{-x_w^T\theta}} P(+)=σ(xwTθ)=1+e−xwTθ1
具体而言,即:
P ( d j w ∣ x w , θ j − 1 w ) = { σ ( x w T θ j − 1 w ) d j w = 0 1 − σ ( x w T θ j − 1 w ) d j w = 1 P(d_j^w|x_w, \theta_{j-1}^w)= \begin{cases} \sigma(x_w^T\theta_{j-1}^w)& {d_j^w=0}\\ 1- \sigma(x_w^T\theta_{j-1}^w) & {d_j^w = 1} \end{cases} P(djw∣xw,θj−1w)={
σ(xwTθj−1w)1−σ(xwTθj−1w)djw=0djw=1
其中 x w x_w xw