Paragraph Vector在Gensim和Tensorflow上的编写以及应用

上一期讨论了Tensorflow以及Gensim的Word2Vec模型的建设以及对比。这一期,我们来看一看Mikolov的另一个模型,即Paragraph Vector模型。目前,Mikolov以及Bengio的最新论文Ensemble of Generative and Discriminative Techniques for Sentiment Analysis of Movie Reviews里就引入了该模型作为用户对影视作品的评论分析方法。与此同时,网络上很多地方也指出该模型效果并没有其前期模型Word2Vec的效果好。这里,我们先不讨论其效果是好是坏,单就如何搭建该模型来展开讨论。首先,我将介绍该模型的Gensim编写方法,之后,在Gensim模型的思维下,我们将尝试运用Tensorflow来编写这个模型。在我们开始码代码前,对于这个模型,还是有必要做一点稍许介绍的。

模型背景:

该模型起始于Word2Vec中的CBOW以及Skip-Gram模型。从模型的框架来看,其结构基本等同于CBOW或者Skip-Gram模型,但最大区别在于加入了一个新的于单词维度相等的维度作为句子维度,段落维度或者文章维度。 维度的意义为需要运用该模型的人他们所需要代表的意义,即句子分类,段落分类还是文章分类。这个新的维度存在于不同于单词维度的空间,所以大家注意不要混淆单词维度和这个新维度的概念。模型的训练方式等同于Word2Vec。模型的目的在于为单词加入一些更长的序列意义外同时为句子,段落或者文章的非监督分类得到类似于Word2Vec的效果。详细的说明大家可以阅读如下链接

模型代码:

1. Gensim代码:

首先,让我们来看看Gensim代码是如何表达的:

from gensim.models import doc2vec
from collections import namedtuple
import csv
import re
import string
# 选择wikipedia作为输入,录入一部分wikipedia的csv文档
reader = csv.reader(open("wikipedia.csv"))
count = 0
data = ‘‘
for row in reader:
    count = count + 1
    if count > 301:
       break
    else:
        data += row[1]
# 分句。我们以句号,问号以及感叹号作为分句依据。
# 值得注意的是,该依据并非十分严谨,例如英文中的
# Mr.Wang就会被划分为两句,但是由于该代码是作为
# 示范,我们对严谨的分句并不感兴趣,大家有空可以
# 做更好的处理
sentenceEnders = re.compile(‘[.?!]‘)
data_list = sentenceEnders.split(data)
# 建设一个namedtuple框架来装载输入
LabelDoc = namedtuple(‘LabelDoc‘, ‘words tags‘)
exclude = set(string.punctuation)
all_docs = []
count = 0
for sen in data_list:
    word_list = sen.split()
    # 当一句话小于三个词儿时,我们认为其意义并不
    # 完整,所以去除该类话以净化我们的输入。
    if len(word_list) < 3:
        continue
    tag = [‘SEN_‘ + str(count)]
    count += 1
    sen = ‘‘.join(ch for ch in sen if ch not in exclude)
    all_docs.append(LabelDoc(sen.split(), tag))
# 打印例子来看看all_docs的形状
print all_docs[0:10]
# 在Gensim的官方文件中,作者指出最好的效果要么来自于随意排列输入句子,要么
# 来自于在训练迭代的过程中减少learning rate alpha,故这里我们运用了后者。
model = doc2vec.Doc2Vec(alpha=0.025, min_alpha=0.025)  # use fixed learning rate
model.build_vocab(all_docs)
for epoch in range(10):
    model.train(all_docs)
    model.alpha -= 0.002  # decrease the learning rate
    model.min_alpha = model.alpha  # fix the learning rate, no decay
# 保存该模型
model.save(‘my_model.doc2vec‘)

不难看出,在整理好输入后,除了需要设计减少learning rate alpha外,其余的训练方法非常浅显易懂。在测试该模型的效果时,运行以下代码即可:

import random
import numpy as np
import string

# 选取一个任意的句子id
doc_id = np.random.randint(model.docvecs.count)
print doc_id

# 通过docvecs.most_similar函数计算相近的句子id,并依次打印出前8个
sims = model.docvecs.most_similar(doc_id, topn=model.docvecs.count)
print(‘TARGET‘ , all_docs[doc_id].words)
count = 0
for i in sims:
    if count > 8:
        break
    pid = int(string.replace(i[0], "SEN_", ""))
    print(i[0],": ", all_docs[pid].words)
    count += 1

运行结果如下:

显而易见,当我们的目标句子是关于Maldonado时,我们的最接近句子也是关于他的。同时,我们的句子是关羽notable victories(明显的胜利)时,第二接近的句子也是关于这个主题。由此可见,系统的确学习到了一些关联性。但是我们终究只是运用了一个黑盒子,这个黑盒子到底是怎么工作的呢?下面我们将试图用Tensorflow还原这个逻辑。

2. Tensorflow代码:

在我5月19日的博客上已经介绍了关于Word2Vec里CBOW模型在Tensorflow上的编写,详细信息请点击链接查询。基于这个模型,我们将来推演如何更改以获得PV-DM模型,即Paragraph Vector版本的CBOW模型。

首先,我们需要整理输入。方法相同于之前Gensim的代码,这里将不予重复。但是值得注意的是,原来的wikipedia.csv文档被我们预处理为一个装有单词list以及其对应句子id的一个namedtuple struct。那么,在接受这个struct的同时,我们需要更改build_data函数来正确的组建dictionary以及我们需要的data输入。这里,我们的目标是保持原来的count, dictionary以及reverse dictionary, 但是对于输入data,我们希望直接更改我们的输入,把namedtuple中,单词list里的单词换成他们在dictionary中的index。如下代码将做到这个功能:

def build_dataset(input_data, min_cut_freq):
  # 这里将input_data重新收集为CBOW模型中的words list以方便
  # counter函数的使用。
  words = []
  for i in input_data:
        for j in i.words:
            words.append(j)
  count_org = [[‘UNK‘, -1]]
  count_org.extend(collections.Counter(words).most_common())
  count = [[‘UNK‘, -1]]
  for word, c in count_org:
    word_tuple = [word, c]
    if word == ‘UNK‘:
        count[0][1] = c
        continue
    if c > min_cut_freq:
        count.append(word_tuple)
  dictionary = dict()
  for word, _ in count:
    dictionary[word] = len(dictionary)
  data = []
  unk_count = 0
  for tup in input_data:
    word_data = []
    for word in tup.words:
      if word in dictionary:
        index = dictionary[word]
      else:
        index = 0
        unk_count += 1
      word_data.append(index)
    data.append(LabelDoc(word_data, tup.tags))
  count[0][1] = unk_count
  reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))
  return data, count, dictionary, reverse_dictionary

由以上代码,我们将会得到我们需要的输入。那么,如何建立我们的模型呢?在建立模型前,我们需要跟改generate_batch函数以求保持原来的batch和label输出外,增加一个对应每个label的一个paragraph label。

def generate_DM_batch(batch_size, num_skips, skip_window):
    global word_index
    global sentence_index
    assert batch_size % num_skips == 0
    assert num_skips <= 2 * skip_window
    batch = np.ndarray(shape=(batch_size, num_skips), dtype=np.int32)
    labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
    para_labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32) # Paragraph Labels
    span = 2 * skip_window + 1 # [ skip_window target skip_window ]
    buffer = collections.deque(maxlen=span)
    for _ in range(span):
        buffer.append(data[sentence_index].words[word_index])
        sen_len = len(data[sentence_index].words)
        if sen_len - 1 == word_index: # reaching the end of a sentence
            word_index = 0
            sentence_index = (sentence_index + 1) % len(data)
        else: # increase the word_index by 1
            word_index += 1
    for i in range(batch_size):
        target = skip_window  # target label at the center of the buffer
        targets_to_avoid = [ skip_window ]
        batch_temp = np.ndarray(shape=(num_skips), dtype=np.int32)
        for j in range(num_skips):
            while target in targets_to_avoid:
                target = random.randint(0, span - 1)
            targets_to_avoid.append(target)
            batch_temp[j] = buffer[target]
        batch[i] = batch_temp
        labels[i,0] = buffer[skip_window]
        para_labels[i, 0] = sentence_index
        buffer.append(data[sentence_index].words[word_index])
        sen_len = len(data[sentence_index].words)
        if sen_len - 1 == word_index: # reaching the end of a sentence
            word_index = 0
            sentence_index = (sentence_index + 1) % len(data)
        else: # increase the word_index by 1
            word_index += 1
    return batch, labels, para_labels

这里,我们保持了两个global的变量,即word_index和sentence_index。前者标记了在一句中前一个batch读到了哪个单词,后者标记了前一个batch读到了哪个句子。他们的初始值都是0。如果我们发现目前所读入的单词在句子中是最后一个词,即sen_len - 1 == word_index, 我们将会重置word_index,并移动sentence_index去向下一句。这样,我们保持了原有的batch和labels外针对每一个input window定义了它自身所应对的一个para_label。 好了,材料齐备了,那么我们如何运用这些材料构建paragraph vector呢?

with graph.as_default():

  # Input data.
  train_inputs = tf.placeholder(tf.int32,shape=[batch_size, skip_window * 2])
  train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
  #paragraph vector place holder
  train_para_labels = tf.placeholder(tf.int32,shape=[batch_size, 1])

  # Ops and variables pinned to the CPU because of missing GPU implementation
  with tf.device(‘/cpu:0‘):
    # Look up embeddings for inputs.
    embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
    embed_word = tf.nn.embedding_lookup(embeddings, train_inputs)
    # Look up embeddings for paragraph inputs
    para_embeddings = tf.Variable(tf.random_uniform([paragraph_size, embedding_size], -1.0, 1.0))
    embed_para = tf.nn.embedding_lookup(para_embeddings, train_para_labels)
    # Concat them and average them
    embed = tf.concat(1, [embed_word, embed_para])
    reduced_embed = tf.div(tf.reduce_sum(embed, 1), skip_window*2 + 1)

    # Construct the variables for the NCE loss
    nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size],
                            stddev=1.0 / math.sqrt(embedding_size)))
    nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
    # Compute the average NCE loss for the batch.
    # tf.nce_loss automatically draws a new sample of the negative labels each
    # time we evaluate the loss.
    loss = tf.reduce_mean( tf.nn.nce_loss(nce_weights, nce_biases, reduced_embed, train_labels,
                     num_sampled, vocabulary_size))

这里,我们首先保留了原来的word embedding的graph,在此基础上,我们加入了paragraph_labels的placeholder,并且定义了paragraph vector的embedding。在把他们合并并且加权平均了后,我们通过nce loss的方式训练该模型。最后

with tf.Session(graph=graph) as session:
  # We must initialize all variables before we use them.
  init.run()
  print("Initialized")

  average_loss = 0
  for step in xrange(num_steps):
    batch_inputs, batch_labels, batch_para_labels = generate_DM_batch(
        batch_size, num_skips, skip_window)
    feed_dict = {train_inputs : batch_inputs, train_labels : batch_labels, train_para_labels: batch_para_labels}

在session里,我们呼叫我们的generatge_DM_batch函数并将batch, label和paragraph_label喂给我们的模型。该模型在运行的过程中效果并不很好,由于时间紧张,我没有对模型进行优化。之后我将会gensim里关于shuffle输入语句或者减少learning rate alpha的提议进行尝试。如果你发现我的代码有误,请务必指出,感谢你的热情参与!谢谢!

时间: 2024-08-09 10:44:37

Paragraph Vector在Gensim和Tensorflow上的编写以及应用的相关文章

TensorFlow上实践基于自编码的One Class Learning

“我不知道什么是爱,但我知道什么是不爱” --One Class Learning的自白 一.单分类简介 如果将分类算法进行划分,根据类别个数的不同可以分为单分类.二分类.多分类,常见的分类算法主要解决二分类和多分类问题,预测一封邮件是否是垃圾邮件是一个典型的二分类问题,手写体识别是一个典型的多分类问题,这些算法并不能很好的应用在单分类上,但单分类问题在工业界广泛存在,由于每个企业刻画用户的数据都是有限的,很多二分类问题很难找到负样本,比如通过用户的搜索记录预测一个用户是否有小孩,可以通过规则筛

以horovd的HorovodAllreduceOp为例,学习如何在tensorflow上添加一个新的操作OP

参考: http://www.tensorfly.cn/tfdoc/how_tos/adding_an_op.html 添加新的OP需要3步(下述所有代码在here): 1. 定义 Op 的接口 // 1. 定义 Op 的接口 // REGISTER_OP()向 TensorFlow 系统注册来定义 Op 的接口,该OP就是HorovodAllreduceOp. // 在注册时, 指定 Op 的名称: REGISTER_OP("HorovodAllreduce") // 输入(类型和名

tensorflow 上的 python 卷积 API

默认的 layout input            filter           output NHWCi        hwCiCo        NHWCo 正向卷积:input * filter -> output 反向卷积: bpi:   output * filter -> input bpk:  input * output -> filter 原文地址:https://www.cnblogs.com/leoict2017/p/11260488.html

mac上如何编写离线blog

mac上有吗的离线博客要数blogo了,具体链接:https://itunes.apple.com/us/app/blogo-blogging-app-for-wordpress/id902111700?mt=12缺点是只支持wordress借口,实在让人难以接受,特别是在国人喜欢自定义喜欢修改修改的情况下,完全的标准接口可能在中国式难以存活的,虽然他做的最漂亮,还是要给一个赞的.当然我也给一个下载地址: 白度网盘:http://pan.baidu.com/s/1eQ94yOU 密码:vtga

在hadoop上进行编写mapreduce程序,统计关键词在text出现次数

mapreduce的处理过程分为2个阶段,map阶段,和reduce阶段.在要求统计指定文件中的所有单词的出现次数时, map阶段把每个关键词写到一行上以逗号进行分隔,并初始化数量为1(相同的单词hadoop中的map会自动放到一行中) reduce阶段是把每个单词出现的频率统计出来重新写回去. 如代码: package com.clq.hadoop2; import org.apache.hadoop.io.IntWritable; import org.apache.hadoop.io.Lo

第一次课上作业——编写程序,输出30道四则运算题

一.设计思路 1.首先完成一个四则运算的出题程序,其次运用循环的方式完成30道题2.首先使用随机函数生成四则运算中的第一个数的随机数,随机数要求是整数和真分数,其他的数不支持 2.1.首先生成一个随机数,取值为1或2,以此来确定四则运算中第一个随机数应该是整数还是真分数.若随机数为1则为整数,若随机数为2则为真分数.. 2.2.若为四则运算中的数为真分数,则随机生成两个数,作为分子和分母,并使得分母大于分子. 3.使用随机函数生成四则运算中的第二个数的随机数,此随机数的要求以及操作同第二步 4.

【转载】史上最全:TensorFlow 好玩的技术、应用和你不知道的黑科技

[导读]TensorFlow 在 2015 年年底一出现就受到了极大的关注,经过一年多的发展,已经成为了在机器学习.深度学习项目中最受欢迎的框架之一.自发布以来,TensorFlow 不断在完善并增加新功能,直到在这次大会上发布了稳定版本的 TensorFlow V1.0.这次是谷歌第一次举办的TensorFlow开发者和爱好者大会,我们从主题演讲.有趣应用.技术生态.移动端和嵌入式应用多方面总结这次大会上的Submit,希望能对TensorFlow开发者有所帮助. TensorFlow:面向大

一线开发者在Reddit上讨论深度学习框架:PyTorch和TensorFlow到底哪个更好?

本文标签:   机器学习 TensorFlow Google深度学习框架 分布式机器学习 PyTorch   近日,Reddit用户 cjmcmurtrie 发了一个主题为「PyTorch vs. TensorFlow」的讨论帖,想要了解这两大流行的框架之间各自有什么优势. 原帖地址:https://redd.it/5w3q74 帖子一楼写道: 我还没有从 Torch7 迁移到 TensorFlow.我玩过 TensorFlow,但我发现 Torch7 更加直观(也许是我玩得不够?).我也尝试了

Ubuntu上安装TensorFlow(python2.7版)

笔记内容:Ubuntu上安装TensorFlow(python2.7版)笔记日期:2018-01-31 Ubuntu上安装TensorFlow(python2.7版) 我的系统环境: Ubuntu 16.04 LTS python 2.7 python 3.5 TensorFlow的两个版本: TensorFlow的安装形式主要有以下几种: virtualenv pip docker anaconda 源代码编译 我这里使用pip进行安装,pip是Python软件包管理系统:Pip Instal