CS224n是斯坦福大学的一门关于自然语言处理的公开课。课程的第二讲的主要内容就是word2vec。word2vec是Google在2013年发布的一门自然语言处理技术。
词向量一般有两种形式,一种叫做One-hot表示,另外一种叫做Distributed Representation。word2vec就属于第二类。word2vec这种词向量表示方法,可以通过计算欧式距离等来判断它们之间的相似度。word2vec本质上属于一种神经网络语言模型范畴的技术,这一点和n-gram、决策树等等这些统计语言模型还是有不少区别的。
1. Log-Linear模型
Log-linear模型算是word2vec的一大基础了。虽然我现在也没深刻理解Log-linear的科学性论证,但是如果接受它这个模型,去理解word2vec,还是相对比较直观的。模型的具体内容如下:
- 一个输入集合X;
- 一个标注集合Y;
- 一个正整数K指定模型中向量的维数;
- 一个映射函数f(X, Y)->R(k),即将任意的(X,Y)组合映射到一个K维的实数模型向量;
- 一个K维地实数参数向量v;
然后对于任意的x, y,模型定义的条件概率如下:
2. Skip-Gram模型
word2vec有两种模型,一种是CBOW,另外一种是Skip-Gram。CBOW是Continuous Bag-of-Words Model的缩写,这个我还没有仔细研究。因此,就主要看Skip-Gram,它的主要示意性结构如下:
Skip-Gram模型的主要思路是,通过一个中心词来推算周边的单词。换而言之,就是预测概率p(wi | wt),其中t-c <= i <= t + c且i != t,这里的c就是上下文窗口的大小。Skip-Gram算法的目标函数是:
而依据Log-linear模型,p(wo | wi)可以定义为如下形式:
知道了目标函数,理论上就可以通过梯度下降算法进行模型训练了。通过训练,我们就可以得到各个词的word2vec向量表示了。
3. 实际例子
目前为止,我还不会自主地实现word2vec。不过《TensorFlow实战》这本书提供了一个word2vec的例子,我粗略研究了一下。看起来还是比较直观的,因为TensorFlow已经有很完备的算法支持了。这里贴点训练的代码,感受一下训练的过程。
graph = tf.Graph() with graph.as_default(): train_inputs = tf.placeholder(tf.int32, shape=[batch_size]) train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1]) valid_dataset = tf.constant(valid_examples, dtype=tf.int32) with tf.device('/cpu:0'): embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0)) embed = tf.nn.embedding_lookup(embeddings, train_inputs) nce_weights = tf.Variable( tf.truncated_normal([vocabulary_size, embedding_size], stddev=1.0 / math.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=train_labels, inputs=embed, num_sampled=num_sampled, num_classes=vocabulary_size)) optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss) norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True)) normalized_embeddings = embeddings / norm valid_embeddings = tf.nn.embedding_lookup( normalized_embeddings, valid_dataset) similarity = tf.matmul( valid_embeddings, normalized_embeddings, transpose_b=True) init = tf.global_variables_initializer() num_steps = 100001 with tf.Session(graph=graph) as session: init.run() print("Initialized") average_loss = 0 for step in range(num_steps): batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window) feed_dict = {train_inputs : batch_inputs, train_labels:batch_labels} _, loss_val = session.run([optimizer, loss], feed_dict=feed_dict) average_loss += loss_val if step % 2000 == 0: if step > 0: average_loss /= 2000 print("Average loss at step ", step, ": ", average_loss) average_loss = 0 if step % 10000 == 0: sim = similarity.eval() for i in range(valid_size): valid_word = reverse_dictionary[valid_examples[i]] top_k = 8 nearest = (-sim[i, :]).argsort()[1:top_k+1] log_str = "Nearest to %s:" % valid_word for k in range(top_k): close_word = reverse_dictionary[nearest[k]] log_str = "%s %s," % (log_str, close_word) print(log_str) final_embeddings = normalized_embeddings.eval()
重点在tf.nn.embeddinng_lookup()和tf.nn.nce_loss(),主要的活都被他们干了,查找词汇的embedding和计算目标函数值。所以说,看tf的上层代码确实看不出很多细节,下一步准备去看一些比较底层的实现代码。不过程序的结果还是挺有意思的,这段程序会把相似度最高的几组单词打印出来。程序结果如下:
100000步训练之后,可以发现效果还是挺有意思的。很多相近的单词都被找到了,不得不说,这就是word2vec的厉害之处了。而且word2vec是真的很快,这意思是,买不起显卡的我还是可以多搞搞nlp的咯!
嗯嗯,感觉结果有些弱啊
我觉得还行吧,这只是一个单纯的word2vec例子。训练的语料库只有不到30m,这和几百G的大型语料库不能比的。