Word2vec
Word2vec的本质可以说是对NNLM模型的改进,他的出现使得词得到了很好的表示,既可以不像ont-hot编码那样冗余,又可以得到词与词之间的关系信息。
Skip-gram 和 CBOW 模型
得到词向量的两种方式。
Skip-gram
用一个词语做为输入,预测它周围的上下文,那这个模型叫做Skip-gram 模型,它的一般形式如下:
CBOW (Continuous Bag-of-Words)模型
拿一个词语的上下文作为输入,来预测这个词语本身,则是 CBOW 模型。
代码简析
- 数据集是简单的12句话,共13种不同单词组成。
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
tf.reset_default_graph()
# 3 Words Sentence
sentences = [ "i like dog", "i like cat", "i like animal",
"dog cat animal", "apple cat dog like", "dog fish milk like",
"dog cat eyes like", "i like apple", "apple i hate",
"apple i movie book music like", "cat dog hate", "cat dog like"]
word_sequence = " ".join(sentences).split()
word_list = " ".join(sentences).split()
word_list = list(set(word_list))
word_dict = {w: i for i, w in enumerate(word_list)}
- 使用的Skip-gram模型,输入为随机选择的一个词,输出为这个词的上文或下文(前一个单词或后一个单词)。
def random_batch(data, size):
random_inputs = []
random_labels = []
random_index = np.random.choice(range(len(data)), size, replace=False)
for i in random_index:
random_inputs.append(data[i][0]) # target
random_labels.append([data[i][1]]) # context word
return random_inputs, random_labels
skip_grams = []
for i in range(1, len(word_sequence) - 1):
target = word_dict[word_sequence[i]]
context = [word_dict[word_sequence[i - 1]], word_dict[word_sequence[i + 1]]]
for w in context:
skip_grams.append([target, w])
- 两个模型使用不同的损失函数。
- 模型一参数和初始化,中间Embedding层(代码中的W)的向量就是得到的词向量。
# Word2Vec Parameter
batch_size = 20
embedding_size = 2 # To show 2 dim embedding graph
voc_size = len(word_list)
# Model
inputs = tf.placeholder(tf.float32, shape=[None, voc_size])
labels = tf.placeholder(tf.float32, shape=[None, voc_size])
# W and WT is not Traspose relationship
W = tf.Variable(tf.random_uniform([voc_size, embedding_size], -1.0, 1.0))
WT = tf.Variable(tf.random_uniform([embedding_size, voc_size], -1.0, 1.0))
hidden_layer = tf.matmul(inputs, W) # [batch_size, embedding_size]
output_layer = tf.matmul(hidden_layer, WT) # [batch_size, voc_size]
cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits_v2(logits=output_layer, labels=labels))
optimizer = tf.train.AdamOptimizer(0.001).minimize(cost)
- 模型二 ,使用tf.nn.embedding_lookup来连接输入,原理和上面的方法一样,但是如果我们有训练好的Embedding层,就可以用这种方式直接使用,不用重新训练。
# Model
inputs = tf.placeholder(tf.int32, shape=[batch_size])
labels = tf.placeholder(tf.int32, shape=[batch_size, 1]) # To use tf.nn.nce_loss, [batch_size, 1]
embeddings = tf.Variable(tf.random_uniform([voc_size, embedding_size], -1.0, 1.0))
selected_embed = tf.nn.embedding_lookup(embeddings, inputs) # 把输入对应成2维的
nce_weights = tf.Variable(tf.random_uniform([voc_size, embedding_size], -1.0, 1.0))
# 共有voc_size个类别,embedding_size为2维的
nce_biases = tf.Variable(tf.zeros([voc_size]))
# Loss and optimizer
cost = tf.reduce_mean(tf.nn.nce_loss(nce_weights, nce_biases, labels, selected_embed, num_sampled, voc_size))
# num_Sampled 对应负样本的个数
optimizer = tf.train.AdamOptimizer(0.001).minimize(cost)
- 训练和验证
# Training
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
for epoch in range(10000):
batch_inputs, batch_labels = random_batch(skip_grams, batch_size)
_, loss = sess.run([optimizer, cost], feed_dict={inputs: batch_inputs, labels: batch_labels})
if (epoch + 1) % 1000 == 0:
print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
trained_embeddings = embeddings.eval()
for i, label in enumerate(word_list):
x, y = trained_embeddings[i]
plt.scatter(x, y)
plt.annotate(label, xy=(x, y), xytext=(5, 2), textcoords='offset points', ha='right', va='bottom')
plt.show()
结果
总结
- 因为我们训练的次比较少,所以词向量用二维表示,可以在平面画出来,意思相近的词会聚集在一起。
- tf.nn.nce_loss 应用到了Negative Sampling这一技术,提高了效率,加速了收敛,详情看参考-1
- 本文只是极简版的word2vec ,还有很多细节的东西需要自行挖掘。