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)

---------------------

作者:河南骏

来源:CSDN

原文:https://blog.csdn.net/Eason_oracle/article/details/79061555

版权声明:本文为博主原创文章,转载请附上博文链接!

你可能感兴趣的:(Tensorflow实现Word2Vec)