Tensorflow实现Word2Vec
Posted 河南骏
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tensorflow实现Word2Vec相关的知识,希望对你有一定的参考价值。
首先是载入各种依赖库,因为要从网络中下载数据,粗腰的依赖库比较多。
import collections
import math
import os
import random
import zipfile
import numpy as np
import urllib
import tensorflow as tf
这里使用urllib.request.urlretrieve下载数据的亚索文件并核对文件尺寸,如果已经下载了文件则跳过。
url='http://mattmahoney.net/dc/'
def maybe_download(filename,expected_bytes):
if not os.path.exists(filename):
filename,_=urllib.request.urlretrieve(url+filename,filename)
statinfo=os.stat(filename)
if statinfo.st_size==expected_bytes;
print 'Found and verified',filename
else:
print statinfo.st_size
raise exception('Failed to verify'+filename+',can you get to it with a browser?')
return filename
filename=maybe_download(text8.zip,31344016)
接下来解压下载的亚索文件,并使用tf.conpat.as_str将数据转成单词的列表。通过程序输出,可以知道数据最后被转为一个包含17005207个单词的列表。
def read_data(filename):
with zipfile.ZipFile(filename) as f:
data=tf.compat.as_str(f.read(f.namelist()[0])).split()
return data
words=read_data(filename)
print 'Data size',len(words)
接下来是创建vocabulary词汇表,我们使用collections.Counter统计单词列表中单词聘书,然后使用Most_common方法取top 50000频数的单词作为vocabulary,再创建一个dict,将top 50000词汇的vocabulary放入dictionary中,一遍快速查询。python中dict查询复杂度为o(1),性能非常好。接下来将全部单词转为编号(以频数排序的编号),top 50000词汇之外的单词,我们认定其为unknown,将其编号为0,并统计这类词汇的数量。下面遍历单词列表,对其中每一个单词,先判断是否出现在dictionary中,如果是则转为其编号,如果不是则转为编号0(unknown)。最后返回转换后的编码(data)、每个单词的频数统计(count)、词汇表(dictionary)及其反转的形式(reverse_dictionary)。
vocabulary_size=50000
def build_dataset(words):
count=[['UNK',-1]]
count.extend(collections.Counter(words).most_common(vocabulary_size -1))
dictionary=dict()
for word,_ in count:
dictionary[word]=len(dictionary)
data=list()
unk_count=0
for word in words;
if word in dictionary:
index=dictionary[word]
else:
index=0
unk_count+=1
data.append(index)
count[0][1]=unk_count
reverse_dictionary=dict(zip(dictionary.values(),dictionary.keys()))
return data,count,dictionary,reverse_dictionary
data,count,dictionary,reverse_dictionary = build_dataset(words)
然后我们删除原始单词列表,可以解决内存。再打印vocabulary中最高频出现的词汇及其数量(包括unknown词汇),可以看到“UNK”这类一共有418391个,我们的data中前10个单词为[“anarchism”,“originated”,“as”,“a”,“term”,“of”,“abuse”,“first”,“used”,“against”],对应的编号为[5235,3084,12,6,,195,2,3137,46,59,156]
del words
print 'Most common words (+UNK)',count[:5]
print 'Sample data',data[:10],[reverse_dictionary[i] for i in data[:10]]
下面生成word2vec的训练样本。我们使用的Skip-Gram模式(从目标单词反推语境),将原始数据“the quick brown fox jumped over the lazy dog”转为(quick,the),(quick,brown),(brown,quick),(brown,fox)等样本。我们定义函数generate_batch用来生成训练用的Batch数据,参数中batch_size为batch大小,skip_window指单词最远可以联系的距离,设为1代表只能跟紧邻的两个单词生成样本,比如quick只能和前后的单词生成连个样本(quick,the)和(quick,brown)。num_skips为对每个单词生成多少个样本,它不能大于skip_window值的两倍,并且batch_size必须是它的整数倍(确保每个batch包含了一个词汇对应的所有样本)。我们定义单词需要data_index为global变量,因为我们会反复调用generate_batch,所以要确保data_index可以在函数generate_batch中被修改。我们也是用assert确保num_skips和batch_size满足前面提到的条件。然后用np.ndarray将batch和labels初始化为数组。这里定义span为对某个单词创建相关样本时会使用到的单词数量,包括目标单词本身和前后的单词,因此span=2*skip_window+1.并创建一个最大容量为soan的deque,即双向队列,在对deque使用append方法添加变量时,只会保留最会插入的span个变量。
data_index=0
def generate_batch(batch_size,num_skips,skip_window):
global data_index
assert batch_size % num_skips==0
assert num_skips <=2*skip_window
batch=np.ndaaray(shape=(batch_size),dtype=np.int32)
labels=np.ndaaray(shape=(batch_size,1),dtype=np.int32)
span=2*skip_window+1
buffer=collection.deque(maxlen=span)
接下来从需要data_index开始,把span个单词顺序读入buffer作为初始值。因为buffer是容量为span的deque,所以此时buffer已填充满,后续数据将替换掉前面的数据。然后我们进入第一层循环(次数为batch_size/num_skips),每次循环内对一个目标单词生成样本。现在buffer中是目标单词和所有相关单词,我们定义target=skip_window,即buffer中第skip_window个变量为目标单词。然后我们定义生成样本时需要避免的单词李彪target_to_avoid,这个列表一开始包括skip_window个单词(即目标单词)因为我们要预测的是语境单词,不包括目标单词本身。接下来进入第二层循环(次数为num_skips),每次循环中对一个语境单词生成样本,先产生随机数,知道随机数不在target_to_avoid中,代表可以使用的语境单词,然后产生一个样本,feature即目标词汇buffer[skip_window],label则是buffer[target]。同时,因为这个语境单词被使用了,所再把它添加到target_to_avoid中过滤。在对一个目标单词生成完所有样本后(num_skips个样本),我们再读入下一个单词(同时会跑掉buffer中的第一个单词),即把滑窗向后移动一位,这样我们的目标单词也向后移动一个,语境打次也整体后移了,便可以开始生成下一个目标单词的训练样本。两侧循环完成后,我们已经获得了batch_size个训练样本,将batch和labels作为函数结果返回。
for _ in range(span):
buffer.append(data[data_index])
data_index=(data_index+1)%len(data)
for i in range(batch_size//num_skips):
target=skip_window
target_to_avoid=[skip_window]
for j in range(num_skips):
while target in target_to_avoid:
target=random.randint(o,span-1)
targets_to_avoid.append(target)
batch[i*num_skips+j]=buffer[skip_window]
labels[i*num_skips+j,0]=buffer[target]
buffer.append(data[data_index])
data_index=(data_index+1)%len(data)
return batch,labels
这里调用generate_batch函数简单测试一下其功能。参数中将batch_size设为8,num_skips设为2,skip_window设为1,然后执行generate_batch并获得batch和labels。再打印batch和labels的数据,可以看到我们生成的样本是“3084 originated->5235 anarchism”,“3084 originated-> 12 as”,“12 as-> 3084 originated”等。以第一个样本为例,3084是目标单词originated的编号,这个单词对应的语境单词是anarchism,其编号为5235。
batch,labels=generate_batch(batch_size=8,num_skips=2,skip_window=1)
for i in range(8):
print batch[i],reverse_dictionary[batch[i]],'->',labels[i,0],reverse_dictionary[labels[i,0]]
我们定义训练时的batch_size为128,embedding_size为128,embedding_size即将单词转稠密向量的维度,一般是50-1000这个范围内的值,这里使用128作为词向量的维度,skip_window即前面提到的单词间最远可以联系的距离,设为1;num_skips即对每个目标单词提取的样本数,设为2.然后我们再生成验证数据valid_examples,这里随机抽取一些频数最高的单词,看向量空间上跟它们最近的单词是否相关性比较高。valid_size=16指用来抽取的验证单词数,valid_window=100是指验证单词只从频数最高的100个单词中抽取,我们使用np.random.choice函数进行随机抽取。而num_sampled是训练时用来做负样本的噪声单词的数量。
batch_size=128
embedding_size=128
skip_window=1
num_skips=2
valid_size=16
valid_window=100
valid_examples=np.random.choice(valid_window,valid_size,replace=False)
num_sampled=64
下面就开始定义skip-Gram Word2Vec模型的网络结构。我们先创建一个tf.Graph并设置为默认的graph。然后创建训练数据中inputs和lebels的placeholder,同时将前面随机产生的valid_examples转为Tensorflow中的constant。接下来,先使用with tf.device('/cpu:0')限定所有计算在CPU上执行,因为接下去的一些计算操作在GPU上可能还没有实现。然后实现tf.random_uniform随机生成所有单词的词向量embeddings,单词表大小为50000,向量维度为128,再使用tf.nn.embedding_lookup查找输入train_inputs对应的向量embed。下面使用之前提到的NCE Loss中的权重参数nce_weights,并将其nce_biases初始化为0.最后使用tf.nn.nce_loss计算出词向量embedding在训练数据上的Loss,并是引用tf.reduce_mean进行汇总。
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.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))
我们定义优化器为SGD,且学习速率为1.0.然后计算嵌入向量embeddings的L2范数norm,再将embeddings除以其L2范数得到标准化后的normalized_embeddings。再使用tf.nn.embedding_lookup查询验证单词的嵌入向量,并计算验证单词的嵌入向量与词汇表中所有单词的相似性。最后,我们使用tf.global_variables_initializer初始化所有模型参数。
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()
我们定义最大的迭代次数为10万次,然后我们创建并设置默认的session,并执行参数初始化。在每一步训练迭代中,先使用generate_batch生成一个batch的inputs和labels数据,并用它们创建feed_dict。然后使用session.run()执行一次优化器运算(即一次参数更新)和算是计算,并将这一步训练的loss累积到average_loss。
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
之后每2000次循环,计算一下平均loss并显示出来。
if step % 2000==0:
if step>0:
average_loss/=2000
print 'Average loss at step',step,':',average_loss
average_loss=0
每10000次循环,计算一次验证单词与全部单词的相似度,并将其与每个验证单词最相似的8个单词展示出来。
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()
=======================================================================================下面定义一个用来可视化Word2Vec效果的函数。这里low_dim_embs是降维到2维的单词的空间向量,我们将在图标中展示每个单词的位置。我们使用plt.scatter(一般讲matplotlib.pyplot命名为plt)显示散点图(单词的位置),并用plt.annotate展示单词本身。同时,使用plt.savefig保存图片到本地文件
def plot_with_labels(low_dim_embs,labels,filename='tsne.png'):
assert low_dim_embs.shape[0]>=len(labels),'More labels than embeddings'
plt.figure(figsize=(18,18))
for i,label in enumerate(labels);
x,y=low_dim_embs[i,:]
plt.scatter(x,y)
plt.annotate(label,xy=(x,y).xytext=(5,2),textcoords='offset points',ha='right',va='bottom')
plt.savefig(filename)
~
~
我们使用sklearn.manifold.TSNE实现降维,这里直接将原始的128维的嵌入向量降到2维,再用前面的plot_with_labels函数进行展示。这里只显示词频最高的100个单词的可视化结果。
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
tsne=TSNE(PERPLEXITY=30,N_COMPONENTS=2,INIT='PCA',N_ITER=5000)
plot_only=100
low_dim_embs=tsne.fit_transform(final_embeddings[:plot_only,:])
labels=[reverse_dictionary[i] for i in range(plot_only)]
plot_with_labels(low_dim_embs,labels)
以上是关于Tensorflow实现Word2Vec的主要内容,如果未能解决你的问题,请参考以下文章