word2vec学习笔记——CS224n

CS224n是斯坦福大学的一门关于自然语言处理的公开课。课程的第二讲的主要内容就是word2vec。word2vec是Google在2013年发布的一门自然语言处理技术。

词向量一般有两种形式,一种叫做One-hot表示,另外一种叫做Distributed Representation。word2vec就属于第二类。word2vec这种词向量表示方法,可以通过计算欧式距离等来判断它们之间的相似度。word2vec本质上属于一种神经网络语言模型范畴的技术,这一点和n-gram、决策树等等这些统计语言模型还是有不少区别的。

1. Log-Linear模型

Log-linear模型算是word2vec的一大基础了。虽然我现在也没深刻理解Log-linear的科学性论证,但是如果接受它这个模型,去理解word2vec,还是相对比较直观的。模型的具体内容如下:

  1. 一个输入集合X;
  2. 一个标注集合Y;
  3. 一个正整数K指定模型中向量的维数;
  4. 一个映射函数f(X, Y)->R(k),即将任意的(X,Y)组合映射到一个K维的实数模型向量;
  5. 一个K维地实数参数向量v;

然后对于任意的x, y,模型定义的条件概率如下:

image

2. Skip-Gram模型

word2vec有两种模型,一种是CBOW,另外一种是Skip-Gram。CBOW是Continuous Bag-of-Words Model的缩写,这个我还没有仔细研究。因此,就主要看Skip-Gram,它的主要示意性结构如下:

image

Skip-Gram模型的主要思路是,通过一个中心词来推算周边的单词。换而言之,就是预测概率p(wi | wt),其中t-c <= i <= t + c且i != t,这里的c就是上下文窗口的大小。Skip-Gram算法的目标函数是:

image

而依据Log-linear模型,p(wo | wi)可以定义为如下形式:

 

image

 

知道了目标函数,理论上就可以通过梯度下降算法进行模型训练了。通过训练,我们就可以得到各个词的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的上层代码确实看不出很多细节,下一步准备去看一些比较底层的实现代码。不过程序的结果还是挺有意思的,这段程序会把相似度最高的几组单词打印出来。程序结果如下:

image

100000步训练之后,可以发现效果还是挺有意思的。很多相近的单词都被找到了,不得不说,这就是word2vec的厉害之处了。而且word2vec是真的很快,这意思是,买不起显卡的我还是可以多搞搞nlp的咯!眨眼

已有2条评论 发表评论

  1. ATM战队 /

    嗯嗯,感觉结果有些弱啊

    1. lshw4320814 / 本文作者

      我觉得还行吧,这只是一个单纯的word2vec例子。训练的语料库只有不到30m,这和几百G的大型语料库不能比的。

发表评论