美文网首页
Tensorflow实现Word2Vec

Tensorflow实现Word2Vec

作者: 河南骏 | 来源:发表于2018-12-17 15:50 被阅读0次

    首先是载入各种依赖库,因为要从网络中下载数据,粗腰的依赖库比较多。

    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

          本文链接:https://www.haomeiwen.com/subject/wpkckqtx.html