word2vec python源码_Tensorflow完整源码实现Word2vec之Skip-Gram模型

已经介绍了Word2Vec中的Skip-Gram模型的基本原理,以及训练过程的3个策略,详细请参考:

接下来开始动手用 TensorFlow 实现自己的 Word2Vec 模型,本篇文章将利用 TensorFlow 来完成 Skip-Gram 模型。还不是很了解Skip-Gram思想的小伙伴可以参考以上推送文章。

本篇 TensorFlow 实战参考 天雨粟  的实现思路,实战代码的主要目的是加深对Skip-Gram 模型中一些思想和训练技巧的理解,选用了满足自己训练目的的语料规模,对语料质量和算法细节做基本的约束要求,尽可能的降低训练成本。同时,运用 TensorFlow 框架建立网络结构,求解嵌入的词向量。

实战工具

Skip-Gram 模型的训练所使用的语言,库,及工具如下所示:

语言:Python 3

包:TensorFlow包,Numpy包

编辑器:Pycharm

线上GPU:floyd (https://www.floydhub.com/),这是一个非常不错的在线训练深度学习神经网络的平台

数据集:维基百科英文文章预料,参考 天雨粟 给出的预料,预料字节数为90+M.

数据预处理

首先,导入用到的包,如下:

import  random

import time

from collections import  Counter

import  numpy  as np

import  tensorflow  as tf

加载训练网络所用的语料,如下:

with  open('data/text8') as f:

text = f. read ()

数据预处理部分主要做的工作包括:

替换文本中特殊符号,比如:逗号,分号,感叹号等

对文本分词

去除低频词,减少噪音

构建语料

单词映射表

替换文本中的特殊字符:

text = text.replace('.', ' ').replace(',', ' ').replace('"', ' ').\

replace(';', ' ').replace('!', ' ').replace('?', ' ').\

replace('(', ' ').replace(')', ' ').replace('--', ' ').\

replace('?', ' ').replace(':', ' ')

根据空格分词

words = self.text.split()

剔除低频词

word_counts = Counter(words)

words = [word for word in words if word_counts[word] > self.low_freq_del]

去重后的词汇表

vocab = set(words)

在词汇表中建立映射关系

vocab_to_int = {w: c for c, w in enumerate(vocab)}

int_to_vocab = {c: w for c, w in enumerate(vocab)}

去除低频率的单词,同时去除高频出现的停用词,例如“the”, “of”以及“for”这类单词进行剔除。剔除这些单词以后能够加快我们的训练过程,同时减少训练过程中的噪音。采用以下公式:

其中  t  是一个阈值参数,一般为 1e-3 至 1e-5

f w i )  是单词 wi 在整个数据集中的出现频次

P ( w i )  是单词被删除的概率

# 在词汇表中找到单词的索引list

word_vocab_index = [vocab_to_int[w] for w in words]

# 统计单词出现频次

word_index_counter = Counter(word_vocab_index)

total_count = len(word_vocab_index)

# 计算单词频率字典

word_freq_dict = {w: c / total_count for w, c in word_index_counter.items()}

# 计算单词和删除值的字典

word_del_dict = {w: 1 - np.sqrt(self.high_freq_del_t / word_freq_dict[w]) for w in word_index_counter}

# 对单词进行采样

train_words_index = [w for w in word_vocab_index if word_del_dict[w] < self.high_freq_del_prob]

输入样本

Skip-Gram模型的输入是基于中心词的上下文窗依次配对,通过一定批次大小构建输入样本。

对于一个给定词,离它越近的词可能与它越相关,离它越远的词越不相关,这里我们设置窗口大小为 5,对于每个训练单词,我们还会在 [1:5] 之间随机生成一个整数 R,用 R 作为我们最终选择 output word 的窗口大小。这里之所以多加了一步随机数的窗口重新选择步骤,是为了能够让模型更聚焦于当前 input word 的邻近词。

def generate_batches (self,train_words_index):

for idx in range(0, len(train_words_index), self.batch_size):

x, y = [], []

batch = train_words_index[idx: idx + self.batch_size]

for i in range(len(batch)):

batch_x = batch[i]

random_window_size = np.random.randint(1, window_size + 1)

# 要考虑input word前面单词不够的情况

start_point = idx - random_window_size if (idx - random_window_size) > 0 else 0

end_point = idx + random_window_size

# output words(即窗口中的上下文单词)

batch_y = train_words_index[start_point: idx] + words[idx + 1: end_point + 1]

# 由于一个input word会对应多个output word,因此需要长度统一

x.extend([batch_x] * len(batch_y))

y.extend(batch_y)

yield x, y

三层网络

该部分主要包括:

输入层

Embedding,

嵌入矩阵的矩阵形状为   vocab_size×hidden_units_size ,TensorFlow 中的 tf.nn. embedding_lookup 函数可以实现 lookup 的计算方式

Neg ative Sampling,负采样主要是为了解决梯度下降计算速度慢的问题,详细的实现细节请参考  Word2vec之Skip-Gram训练网络的3种技术 ,TensorFlow中的 tf.nn.sampled_softmax_loss 会在 softmax 层上进行采样计算损失,实现类似于 negative sampling 的功能。

详细实现代码如下:

def build_tf_nn(self,train_words_index, int_to_vocab):

# 输入层网络

train_graph = tf.Graph()

with train_graph.as_default():

# 1.输入层

inputs = tf.placeholder(tf.int32, shape=[None], name='inputs')

labels = tf.placeholder(tf.int32, shape=[None, None], name='labels')

# 2.嵌入层

vocab_size = len(int_to_vocab)

embedding_size = 200 # 嵌入维度

embedding = tf.Variable(tf.random_uniform([vocab_size, embedding_size], -1, 1)) # 嵌入层权重矩阵

embed = tf.nn.embedding_lookup(embedding, inputs) # 实现lookup

# 3.Softmax 层(Negative sampling)

smw = tf.Variable(tf.truncated_normal([vocab_size, embedding_size], stddev=0.1))

smb = tf.Variable(tf.zeros(vocab_size))

# 计算negative sampling下的损失

loss = tf.nn.sampled_softmax_loss(smw, smb, labels, embed, self.negative_sample_cnt, vocab_size)

cost = tf.reduce_mean(loss)

optimizer = tf.train.AdamOptimizer().minimize(cost)

验证

抽取几个词,找出各自最相近的 topk 个单词。首先把测试样本加入到图中,

with train_graph.as_default():

#随机挑选一些单词

valid_size = 16

valid_window = 100

#从不同位置各选8个单词

valid_examples = np.array(random.sample(range(valid_window), valid_size/2))

valid_examples = np.append(valid_examples, random.sample(range(1000, 1000 + valid_window), valid_size/2))

# 验证单词集

valid_dataset = tf.constant(valid_examples, dtype=tf.int32)

# 计算每个词向量的模并进行单位化

norm = tf.sqrt(tf.reduce_sum(tf.square(embedding), 1, keepdims=True))

normalized_embedding = embedding / norm

# 查找验证单词的词向量

valid_embedding = tf.nn.embedding_lookup(normalized_embedding, valid_dataset)

# 计算余弦相似度

similarity = tf.matmul(valid_embedding, tf.transpose(normalized_embedding))

接下来,运行以上默认图:

def run_graph(self,train_words,train_graph,int_to_vocab,valid_examples,similarity):

epochs = self.epochs  # 迭代轮数

batch_size = self.batch_size  # batch大小

window_size = self.window_size  # 窗口大小

with train_graph.as_default():

saver = tf.train.Saver()  # 文件存储

with tf.Session(graph=train_graph) as sess:

iteration = 1

loss = 0

sess.run(tf.global_variables_initializer())

for e in range(1, epochs + 1):

batches = self.generate_batches(train_words)

start = time.time()

#

for x, y in batches:

feed = {inputs: x,

labels: np.array(y)[:, None]}

train_loss, _ = sess.run([cost, optimizer], feed_dict=feed)

loss += train_loss

if iteration % 100 == 0:

end = time.time()

print("Epoch {}/{}".format(e, epochs),

"Iteration: {}".format(iteration),

"Avg. Training loss: {:.4f}".format(loss / 100),

"{:.4f} sec/batch".format((end - start) / 100))

loss = 0

start = time.time()

# 计算相似的词

if iteration % 1000 == 0:

# 计算similarity

sim = similarity.eval()

for i in range(valid_size):

valid_word = int_to_vocab[valid_examples[i]]

top_k = 8 # 取最相似单词的前8个

nearest = (-sim[i, :]).argsort()[1:top_k + 1]

log = 'Nearest to [%s]:' % valid_word

for k in range(top_k):

close_word = int_to_vocab[nearest[k]]

log = '%s %s,' % (log, close_word)

print(log)

iteration += 1

save_path = saver.save(sess, "checkpoints/text8.ckpt")

embed_mat = sess.run(normalized_embedding)

每 1000 个时步打印一次,从最后的训练结果来看,模型还是学到了一些常见词的语义,比如 one 等计数词以及 gold 之类的金属词,animals 中的相似词也相对准确,列表如下:

为了能够更全面地观察我们训练结果,我们采用 sklearn 中的 TSNE 来对高维词向量进行可视化。

%matplotlib inline

%config InlineBackend.figure_format = 'retina'

import matplotlib.pyplot as plt

from sklearn.manifold import  TSNE

viz_words = 500

tsne = TSNE ()

embed_tsne = tsne. fit_transform (embed_mat[:viz_words, :])

fig, ax = plt. subplots (figsize=(14, 14))

for idx in range( viz_words ):

plt.scatter (*embed_tsne[idx, :], color='steelblue')

plt.annotate (int_to_vocab[idx], (embed_tsne[idx, 0], embed_tsne[idx, 1]), alpha=0.7)

以上便是在 TensorFlow 中完整源码实现Word2vec之Skip-Gram模型的详细过程代码。

相关链接

点击以下标题查看相关内容:

关于算法channel

算法channel 是一个提供系统入门,工程与学术相结合的原创干货公众号,包括基础算法,机器学习,深度学习,NLP,工具库使用等10余个频道,如果你对算法和人工智能感兴趣,欢迎在公众号后台点击 「交流群」。

你可能感兴趣的:(word2vec,python源码)