生成文章文摘

word2vec加TextRank算法生成文章摘要

#--------------------------
from gensim.models import word2vec
import logging
 
# 训练主程序
logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s', level=logging.INFO)
# 加载语料
sentences = word2vec.Text8Corpus(u'model/text8')
# 训练模型  skip-gram
model = word2vec.Word2Vec(sentences, size=200, window=10, min_count=64, sg=1, hs=1, iter=10, workers=25)
#----------------------------------
# 记载已经训练好的中文模型
model = word2vec.Word2Vec.load("model/word2vec")
 
s = model.similarity("孙悟空", "猪八戒")
print(s)
#输出为:
0.787711574192

#----------------------------------------
def cosine_similarity(vec1, vec2):
    '''
    计算两个向量之间的余弦相似度
    :param vec1:
    :param vec2:
    :return:
    '''
    tx = np.array(vec1)
    ty = np.array(vec2)
    cos1 = np.sum(tx * ty)
    cos21 = np.sqrt(sum(tx ** 2))
    cos22 = np.sqrt(sum(ty ** 2))
    cosine_value = cos1 / float(cos21 * cos22)
    return cosine_value
 
 
def compute_similarity_by_avg(sents_1, sents_2):
    '''
    对两个句子求平均词向量
    :param sents_1:
    :param sents_2:
    :return:
    '''
    if len(sents_1) == 0 or len(sents_2) == 0:
        return 0.0
    vec1 = model[sents_1[0]]
    for word1 in sents_1[1:]:
        vec1 = vec1 + model[word1]
 
    vec2 = model[sents_2[0]]
    for word2 in sents_2[1:]:
        vec2 = vec2 + model[word2]
 
    similarity = cosine_similarity(vec1 / len(sents_1), vec2 / len(sents_2))
    return similarity
#--------------------------------
import jieba
import math
from string import punctuation
from heapq import nlargest
from itertools import product, count
from gensim.models import word2vec
import numpy as np
 
model = word2vec.Word2Vec.load("chinese_model/word2vec_wx")
np.seterr(all='warn')
 
 
def cut_sentences(sentence):
    puns = frozenset(u'。!?')
    tmp = []
    for ch in sentence:
        tmp.append(ch)
        if puns.__contains__(ch):
            yield ''.join(tmp)
            tmp = []
    yield ''.join(tmp)
 
 
# 句子中的stopwords
def create_stopwords():
    stop_list = [line.strip() for line in open("stopwords.txt", 'r', encoding='utf-8').readlines()]
    return stop_list
 
 
def two_sentences_similarity(sents_1, sents_2):
    '''
    计算两个句子的相似性
    :param sents_1:
    :param sents_2:
    :return:
    '''
    counter = 0
    for sent in sents_1:
        if sent in sents_2:
            counter += 1
    return counter / (math.log(len(sents_1) + len(sents_2)))
 
 
def create_graph(word_sent):
    """
    传入句子链表  返回句子之间相似度的图
    :param word_sent:
    :return:
    """
    num = len(word_sent)
    board = [[0.0 for _ in range(num)] for _ in range(num)]
 
    for i, j in product(range(num), repeat=2):
        if i != j:
            board[i][j] = compute_similarity_by_avg(word_sent[i], word_sent[j])
    return board
 
 
def cosine_similarity(vec1, vec2):
    '''
    计算两个向量之间的余弦相似度
    :param vec1:
    :param vec2:
    :return:
    '''
    tx = np.array(vec1)
    ty = np.array(vec2)
    cos1 = np.sum(tx * ty)
    cos21 = np.sqrt(sum(tx ** 2))
    cos22 = np.sqrt(sum(ty ** 2))
    cosine_value = cos1 / float(cos21 * cos22)
    return cosine_value
 
 
def compute_similarity_by_avg(sents_1, sents_2):
    '''
    对两个句子求平均词向量
    :param sents_1:
    :param sents_2:
    :return:
    '''
    if len(sents_1) == 0 or len(sents_2) == 0:
        return 0.0
    vec1 = model[sents_1[0]]
    for word1 in sents_1[1:]:
        vec1 = vec1 + model[word1]
 
    vec2 = model[sents_2[0]]
    for word2 in sents_2[1:]:
        vec2 = vec2 + model[word2]
 
    similarity = cosine_similarity(vec1 / len(sents_1), vec2 / len(sents_2))
    return similarity
 
 
def calculate_score(weight_graph, scores, i):
    """
    计算句子在图中的分数
    :param weight_graph:
    :param scores:
    :param i:
    :return:
    """
    length = len(weight_graph)
    d = 0.85
    added_score = 0.0
 
    for j in range(length):
        fraction = 0.0
        denominator = 0.0
        # 计算分子
        fraction = weight_graph[j][i] * scores[j]
        # 计算分母
        for k in range(length):
            denominator += weight_graph[j][k]
            if denominator == 0:
                denominator = 1
        added_score += fraction / denominator
    # 算出最终的分数
    weighted_score = (1 - d) + d * added_score
    return weighted_score
 
 
def weight_sentences_rank(weight_graph):
    '''
    输入相似度的图(矩阵)
    返回各个句子的分数
    :param weight_graph:
    :return:
    '''
    # 初始分数设置为0.5
    scores = [0.5 for _ in range(len(weight_graph))]
    old_scores = [0.0 for _ in range(len(weight_graph))]
 
    # 开始迭代
    while different(scores, old_scores):
        for i in range(len(weight_graph)):
            old_scores[i] = scores[i]
        for i in range(len(weight_graph)):
            scores[i] = calculate_score(weight_graph, scores, i)
    return scores
 
 
def different(scores, old_scores):
    '''
    判断前后分数有无变化
    :param scores:
    :param old_scores:
    :return:
    '''
    flag = False
    for i in range(len(scores)):
        if math.fabs(scores[i] - old_scores[i]) >= 0.0001:
            flag = True
            break
    return flag
 
 
def filter_symbols(sents):
    stopwords = create_stopwords() + ['。', ' ', '.']
    _sents = []
    for sentence in sents:
        for word in sentence:
            if word in stopwords:
                sentence.remove(word)
        if sentence:
            _sents.append(sentence)
    return _sents
 
 
def filter_model(sents):
    _sents = []
    for sentence in sents:
        for word in sentence:
            if word not in model:
                sentence.remove(word)
        if sentence:
            _sents.append(sentence)
    return _sents
 
 
def summarize(text, n):
    tokens = cut_sentences(text)
    sentences = []
    sents = []
    for sent in tokens:
        sentences.append(sent)
        sents.append([word for word in jieba.cut(sent) if word])
 
    # sents = filter_symbols(sents)
    sents = filter_model(sents)
    graph = create_graph(sents)
 
    scores = weight_sentences_rank(graph)
    sent_selected = nlargest(n, zip(scores, count()))
    sent_index = []
    for i in range(n):
        sent_index.append(sent_selected[i][1])
    return [sentences[i] for i in sent_index]
 
 
if __name__ == '__main__':
    with open("news.txt", "r", encoding='utf-8') as myfile:
        text = myfile.read().replace('\n', '')
        print(summarize(text, 2))

#新的一篇文章
#-------------------------------------------------------------------
Gensim中word2vec模型的期望输入是讲过分词的句子列表,即是某个二维数组
这里我们暂时使用 Python 内置的数组,不过其在输入数据集较大的情况下会占用大量的 RAM。
Gensim 本身只是要求能够迭代的有序句子列表,因此在工程实践中我们可以使用自定义的生成器,只在内存中保存单条语句。
# 引入 word2vec
from gensim.models import word2vec

# 引入日志配置
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

# 引入数据集
raw_sentences = ["the quick brown fox jumps over the lazy dogs","yoyoyo you go home now to sleep"]

# 切分词汇
sentences= [s.encode('utf-8').split() for s in sentences]

# 构建模型
model = word2vec.Word2Vec(sentences, min_count=1)

# 进行相关性比较
model.similarity('dogs','you')


#这里我们调用word2vec创建模型实际会对数据执行两次迭代操作
第一轮操作会统计词频来构建内部的词典数结构,
第二轮操作会进行神经网络训练,
而这两个步骤是可以分步进行的,这样对于某些不可重复的流(譬如 Kafka 等流式数据中)可以手动控制
model = gensim.models.Word2Vec(iter=1)  # an empty model, no training yet
model.build_vocab(some_sentences)  # can be a non-repeatable, 1-pass generator
model.train(other_sentences)  # can be a non-repeatable, 1-pass generator
((((((((((((((((((((((((((((((((((
# 只取100维,可以训练的快一点,完全相同参数下,100维:28w words/s, 300维: 14w words/s
model = word2vec.Word2Vec(size=100, window=5, min_count=3, workers=12)
))))))))))))))))))))))))))))))))))
我们在上文中也提及,如果是对于大量的输入语料集或者需要整合磁盘上多个文件夹下的数据,我们可以以迭代器的方式而不是一次性将全部内容读取到内存中来节省 RAM 空间:


class MySentences(object):
    def __init__(self, dirname):
        self.dirname = dirname

    def __iter__(self):
        for fname in os.listdir(self.dirname):
            for line in open(os.path.join(self.dirname, fname)):
                yield line.split()

sentences = MySentences('/some/directory') # a memory-friendly iterator
model = gensim.models.Word2Vec(sentences)

Word2Vec 的训练属于无监督模型,并没有太多的类似于监督学习里面的客观评判方式,更多的依赖于端应用。Google 之前公开了20000条左右的语法与语义化训练样本,每一条遵循A is to B as C is to D这个格式,地址在这里:

模型创建:
Gensim中word2vec模型的期望输入是进过分词的句子列表,即是某个二维数组。这里我们暂时使用 Python 内置的数组,不过其在输入数据集较大的情况下会占用大量的 RAM。Gensim 本身只是要求能够迭代的有序句子列表,因此在工程实践中我们可以使用自定义的生成器,只在内存中保存单条语句。

# 引入 word2vec
from gensim.models import word2vec

# 引入日志配置
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

# 引入数据集
raw_sentences = ["the quick brown fox jumps over the lazy dogs","yoyoyo you go home now to sleep"]

# 切分词汇
sentences= [s.encode('utf-8').split() for s in sentences]

# 构建模型
model = word2vec.Word2Vec(sentences, min_count=1)

# 进行相关性比较
model.similarity('dogs','you')


先说一下自动文摘的方法。自动文摘(Automatic Summarization)的方法主要有两种:Extraction和Abstraction。其中Extraction是抽取式自动文摘方法,通过提取文档中已存在的关键词,句子形成摘要;Abstraction是生成式自动文摘方法,通过建立抽象的语意表示,使用自然语言生成技术,形成摘要。由于生成式自动摘要方法需要复杂的自然语言理解和生成技术支持,应用领域受限。所以本人学习的也是抽取式的自动文摘方法
目前主要方法有:

    基于统计:统计词频,位置等信息,计算句子权值,再简选取权值高的句子作为文摘,特点:简单易用,但对词句的使用大多仅停留在表面信息。
    基于图模型:构建拓扑结构图,对词句进行排序。例如,TextRank/LexRank
    基于潜在语义:使用主题模型,挖掘词句隐藏信息。例如,采用LDA,HMM
    基于整数规划:将文摘问题转为整数线性规划,求全局最优解。(~.~我也不懂)


TextRank 算法是一种用于文本的基于图的排序算法。其基本思想来源于谷歌的 PageRank算法, 通过把文本分割成若干组成单元(单词、句子)并建立图模型, 利用投票机制对文本中的重要成分进行排序, 仅利用单篇文档本身的信息即可实现关键词提取、文摘。和 LDA、HMM 等模型不同, TextRank不需要事先对多篇文档进行学习训练, 因其简洁有效而得到广泛应用。

==========================================================================================
=========================================================================================

word2vec是如何得到词向量的?这个问题比较大。从头开始讲的话,首先有了文本语料库,你需要对语料库进行预处理,这个处理流程与你的语料库种类以及个人目的有关,比如,如果是英文语料库你可能需要大小写转换检查拼写错误等操作,如果是中文日语语料库你需要增加分词处理。这个过程其他的答案已经梳理过了不再赘述。得到你想要的processed corpus之后,将他们的one-hot向量作为word2vec的输入,通过word2vec训练低维词向量(word embedding)就ok了。不得不说word2vec是个很棒的工具,目前有两种训练模型(CBOW和Skip-gram),两种加速算法(Negative Sample与Hierarchical Softmax)。本答旨在阐述word2vec如何将corpus的one-hot向量(模型的输入)转换成低维词向量(模型的中间产物,更具体来说是输入权重矩阵),真真切切感受到向量的变化,不涉及加速算法。如果读者有要求有空再补上。
1 Word2Vec两种模型的大致印象

刚才也提到了,Word2Vec包含了两种词训练模型:CBOW模型和Skip-gram模型。

    CBOW模型根据中心词W(t)周围的词来预测中心词
    Skip-gram模型则根据中心词W(t)来预测周围词


==============================================================================
======================================================================================
word2vec 介绍
2018年06月12日 22:23:22 jcsyl_mshot 阅读数:1916
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jcsyl_mshot/article/details/80672118
1.背景

在NLP中,传统算法通常使用one-hot形式表示一个词,存在以下问题:

1)维度爆炸,词表通常会非常大,导致词向量维度也会非常大。

2)损失语义信息,one hot随机给每个词语进行编号映射,无法表示词语之间的关系。

所以word embeding的优势如下:

1)将词语映射成一个固定维度的向量,节省空间。

2)词向量可能会具备一定的语义信息,将相似的词语放到相近的向量空间(比如香蕉和苹果都是属于水果,苹果又会涉及到歧义问题),可以学习到词语之间的关系(比如经典的 男人-女人=国王-王后)。

本文会介绍一下Word2vec原理,这是一种常见的可以用于训练词向量的模型工具。常见的做法是,我们先用word2vec在公开数据集上预训练词向量,加载到自己的模型中,对词向量进行调整,调整成适合自己数据集的词向量。
2.训练模式

我们通常是通过将词向量用于某些任务中,用这些任务的衡量指标去衡量模型结果。

那么反过来,如果我们想要训练词向量,可以先去训练一个语言模型,然后将模型中对应的参数,作为词向量。从任务形式上看,我们是在训练语言模型,而实际上我们最终的目标是想得到词向量,我们更关心的是这个词向量合不合理。

Word2vec根据上下文之间的出现关系去训练词向量,有两种训练模式,Skip Gram和CBOW(constinuous bags of words),其中Skip Gram根据目标单词预测上下文,CBOW根据上下文预测目标单词,最后使用模型的部分参数作为词向量。

AutoEncoder也可以用于训练词向量,先将one hot映射成一个hidden state,再映射回原来的维度,令输入等于输出,取中间的hidden vector作为词向量,在不损耗原表达能力的前提下压缩向量维度,得到一个压缩的向量表达形式
2.1 CBOW

根据上下文预测目标单词,我们需要极大化这个目标单词的出现概率。
这里写图片描述

假设词表大小为V,词向量维度为N,上下文单词为x1,x2, …, xc,定义上下文窗口大小为c,对应的目标单词为y,我们将x跟y都表示成one hot形式。这里涉及到两个矩阵参数,W是词向量矩阵,每一行都是某个词的词向量v,W’可以看做是一个辅助矩阵,每一列可以看做是某个词对应的相关向量v’。

前向过程:

x->hidden:对于每个xi,取出对应的词向量vi,再对这些词向量取平均作为hidden vector,相当于通过简单粗暴的叠加,得到这些词语的语义向量。

h->y:将h乘以W’得到一个维度为V的向量u,进行softmax归一化得到概率向量,取概率最大的作为预测单词。

后向过程:

我们需要极大化目标单词的出现概率p(y | x1, x2, … , xc),也就是极小化负对数似然函数,Loss函数定义为:
这里写图片描述

我们需要更新两个矩阵参数,W和W’,先根据loss对参数求梯度,再使用梯度下降法更新参数。具体的求导过程这里略过,请移步原论文。

对于W’,经过求导,v’更新公式为:
这里写图片描述
对于W,经过求导,v更新公式为:
这里写图片描述
2.2 skip-gram
3.训练优化

原始的方法所存在的问题是计算量太大,体现在以下两方面:

1)前向过程,h->y这部分在对向量进行softmax的时候,需要计算V次(每次除目标词的概率, 还需要计算词表中其它词的概率 ,最终才能进行softmax 归一)。

2)后向过程,softmax涉及到了V列向量,所以也需要更新V个向量。

问题就出在V太大,而softmax需要进行V次操作,用整个W进行计算。

因此word2vec使用了两种优化方法,Hierarchical SoftMax和Negative Sampling,对softmax进行优化,不去计算整个W,大大提高了训练速度。
3.1 Hierarchical SoftMax

首先我们要定义词向量的维度大小M,以及CBOW的上下文大小2c,这样我们对于训练样本中的每一个词,其前面的c个词和后面的c个词作为了CBOW模型的输入,该词本身作为样本的输出,期望softmax概率最大。

在做CBOW模型前,我们需要先将词汇表建立成一颗霍夫曼树。可以根据单词在语料中出现的次数建立。

对于从输入层到隐藏层(投影层),这一步比较简单,就是对w周围的2c个词向量求和取平均即可。

我们把之前所有都要计算的从输出softmax层的概率计算变成了一颗二叉霍夫曼树,那么我们的softmax概率计算只需要沿着树形结构进行就可以了。如下图所示,我们可以沿着霍夫曼树从根节点一直走到我们的叶子节点的词w2。
这里写图片描述
其中,根节点的词向量对应我们的投影后的词向量,而所有叶子节点就类似于之前神经网络softmax输出层的神经元,叶子节点的个数就是词汇表的大小。在霍夫曼树中,隐藏层到输出层的softmax映射不是一下子完成的,而是沿着霍夫曼树一步步完成的。

如何“沿着霍夫曼树一步步完成”呢?在word2vec中,我们采用了二元逻辑回归的方法,即规定沿着左子树走,那么就是负类(霍夫曼树编码1),沿着右子树走,那么就是正类(霍夫曼树编码0)。判别正类和负类的方法是使用sigmoid函数,即:
这里写图片描述

我们使用最大似然法来寻找所有节点的词向量和所有内部节点θ,先拿上面的w2例子来看,我们期望最大化下面的似然函数:
这里写图片描述

在根节点处左右概率之和是1,然后在接下来的每个节点,对应两个子节点的概率值之和等于父节点本身的概率值,那么走到最后,所有叶子节点的概率值之和必定还是等于1。

Loss函数定义为:
这里写图片描述

其中n(w, i)表示从根节点到叶节点w路径中的第i个节点,v’(w, i)表示n(w, i)所对应的v’向量,L(w) 路径的长度。

通过求导也就是说,这里只需要更新L(w)-1个v’向量,时间复杂度直接从O(V)降到了O(logV),
3.2 Negative Sampling
==========================================================================================================================================================================================================================================================================================================================================================================================

    NLP是AI安全领域的一个重要支撑技术。本文讲介绍NLP中的Word2Vec模型和Doc2Vec模型。

Word2Vec

Word2Vec是Google在2013年开源的一款将词表征为实数值向量的高效工具,采用的模型有CBOW(Continuous Bag-Of-Words,即连续的词袋模型)和Skip-Gram 两种。Word2Vec通过训练,可以把对文本内容的处理简化为K维向量空间中的向量运算,而向量空间上的相似度可以用来表示文本语义上的相似度。因此,Word2Vec 输出的词向量可以被用来做很多NLP相关的工作,比如聚类、找同义词、词性分析等等。

image.png

CBOW和Skip-gram原理图

CBOW模型能够根据输入周围n-1个词来预测出这个词本身,而Skip-gram模型能够根据词本身来预测周围有哪些词。也就是说,CBOW模型的输入是某个词A周围的n个单词的词向量之和,输出是词A本身的词向量,而Skip-gram模型的输入是词A本身,输出是词A周围的n个单词的词向量。Word2Vec最常用的开源实现之一就是gensim,网址为:

    http://radimrehurek.com/gensim/

gensim的安装非常简单:

    pip install –upgrade gensim

gensim的使用非常简洁,加载数据和训练数据可以合并,训练好模型后就可以按照单词获取对应的向量表示:

    sentences = [['first', 'sentence'], ['second', 'sentence']]model = gensim.models.Word2Vec(sentences, min_count=1)print model['first']

其中Word2Vec有很多可以影响训练速度和质量的参数。第一个参数可以对字典做截断,少于min_count次数的单词会被丢弃掉, 默认值为5:

model = Word2Vec(sentences, min_count=10)

另外一个是神经网络的隐藏层的单元数,推荐值为几十到几百。事实上Word2Vec参数的个数也与神经网络的隐藏层的单元数相同,比如:

    size=200

那么训练得到的Word2Vec参数个数也是200:

model = Word2Vec(sentences, size=200)

以处理IMDB数据集为例,初始化Word2Vec对象,设置神经网络的隐藏层的单元数为200,生成的词向量的维度也与神经网络的隐藏层的单元数相同。设置处理的窗口大小为8个单词,出现少于10次数的单词会被丢弃掉,迭代计算次数为10次,同时并发线程数与当前计算机的cpu个数相同:

model=gensim.models.Word2Vec(size=200, window=8, min_count=10, iter=10, workers=cores)

其中当前计算机的cpu个数可以使用multiprocessing获取:

cores=multiprocessing.cpu_count()

创建字典并开始训练获取Word2Vec。gensim的官方文档中强调增加训练次数可以提高生成的Word2Vec的质量,可以通过设置epochs参数来提高训练次数,默认的训练次数为5:

x=x_train+x_testmodel.build_vocab(x)
model.train(x, total_examples=model.corpus_count, epochs=model.iter)

经过训练后,Word2Vec会以字典的形式保存在model对象中,可以使用类似字典的方式直接访问获取,比如获取单词“love”的Word2Vec就可以使用如下形式:

model[“love”]

Word2Vec的维度与之前设置的神经网络的隐藏层的单元数相同为200,也就是说是一个长度为200的一维向量。通过遍历一段英文,逐次获取每个单词对应的Word2Vec,连接起来就可以获得该英文段落对应的Word2Vec:

def getVecsByWord2Vec(model, corpus, size):
    x=[]
    for text in corpus:
        xx = []
        for i, vv in enumerate(text):
            try:
                xx.append(model[vv].reshape((1,size)))
            except KeyError:
                continue
        x = np.concatenate(xx)
    x=np.array(x, dtype='float')
    return x

需要注意的是,出于性能的考虑,我们将出现少于10次数的单词会被丢弃掉,所以存在这种情况,就是一部分单词找不到对应的Word2Vec,所以需要捕捉这个异常,通常使用python的KeyError异常捕捉即可。
Doc2Vec

基于上述的Word2Vec的方法,Quoc Le 和Tomas Mikolov又给出了Doc2Vec的训练方法。如下图所示,其原理与Word2Vec相同,分为Distributed Memory (DM) 和Distributed Bag of Words (DBOW)。

image.png

DM和DBOW原理图

以处理IMDB数据集为例,初始化Doc2Vec对象,设置神经网络的隐藏层的单元数为200,生成的词向量的维度也与神经网络的隐藏层的单元数相同。设置处理的窗口大小为8个单词,出现少于10次数的单词会被丢弃掉,迭代计算次数为10次,同时并发线程数与当前计算机的cpu个数相同:

model=Doc2Vec(dm=0, dbow_words=1, size=max_features, window=8, min_count=10, iter=10, workers=cores)

其中需要强调的是,dm为使用的算法,默认为1,表明使用DM算法,设置为0表明使用DBOW算法,通常使用默认配置即可,比如: model = gensim.models.Doc2Vec.Doc2Vec(size=50, min_count=2, iter=10)与Word2Vec不同的地方是,Doc2Vec处理的每个英文段落,需要使用一个唯一的标识标记,并且使用一种特殊定义的数据格式保存需要处理的英文段落,这种数据格式定义如下:

SentimentDocument = namedtuple('SentimentDocument', 'words tags')

其中SentimentDocument可以理解为这种格式的名称,也可以理解为这种对象的名称,words会保存英文段落,并且是以单词和符合列表的形式保存,tags就是我们说的保存的唯一标识。最简单的一种实现就是依次给每个英文段落编号,训练数据集的标记为“TRAIN数字”,训练数据集的标记为“TEST数字”:

def labelizeReviews(reviews, label_type):
    labelized = []
    for i, v in enumerate(reviews):
        label = '%s_%s' % (label_type, i)
        labelized.append(SentimentDocument(v, [label]))
    return labelized

创建字典并开始训练获取Doc2Vec。与Word2Vec的情况一样,gensim的官方文档中强调增加训练次数可以提高生成的Doc2Vec的质量,可以通过设置epochs参数来提高训练次数,默认的训练次数为5:

x=x_train+x_test
model.build_vocab(x)
model.train(x, total_examples=model.corpus_count, epochs=model.iter)

经过训练后,Doc2Vec会以字典的形式保存在model对象中,可以使用类似字典的方式直接访问获取,比如获取段落“I love tensorflow”的Doc2Vec就可以使用如下形式:

model.docvecs[”I love tensorflow”]

一个典型的doc2ver展开为向量形式,内容如下所示,为了显示方便只展示了其中一部分维度的数据:

array([ 0.02664499, 0.00475204, -0.03981256, 0.03796276, -0.03206162, 0.10963056, -0.04897128, 0.00151982, -0.03258783, 0.04711508, -0.00667155, -0.08523653, -0.02975186, 0.00166316, 0.01915652, -0.03415785, -0.05794788, 0.05110953, 0.01623618, -0.00512495, -0.06385455, -0.0151557 , 0.00365376, 0.03015811, 0.0229462 , 0.03176891, 0.01117626, -0.00743352, 0.02030453, -0.05072152, -0.00498496, 0.00151227, 0.06122205, -0.01811385, -0.01715777, 0.04883198, 0.03925886, -0.03568915, 0.00805744, 0.01654406, -0.05160677, 0.0119908 , -0.01527433, 0.02209963, -0.10316766, -0.01069367, -0.02432527, 0.00761799, 0.02763799, -0.04288232], dtype=float32)

Doc2Vec的维度与之前设置的神经网络的隐藏层的单元数相同为200,也就是说是一个长度为200的一维向量。以英文段落为单位,通过遍历训练数据集和测试数据集,逐次获取每个英文段落对应的Doc2Vec,这里的英文段落就可以理解为数据集中针对电影的一段评价:

def getVecs(model, corpus, size):
    vecs = [np.array(model.docvecs[z.tags[0]]).reshape((1, size)) for z in corpus]
    return np.array(np.concatenate(vecs),dtype='float')

训练Word2Vec和Doc2Vec是非常费时费力的过程,调试阶段会频繁更换分类算法以及修改分类算法参数调优,为了提高效率,可以把之前训练得到的Word2Vec和Doc2Vec模型保存成文件形式,以Doc2Vec为例,使用model.save函数把训练后的结果保存在本地硬盘上,运行程序时,在初始化Doc2Vec对象之前,可以先判断本地硬盘是否存在模型文件,如果存在就直接读取模型文件初始化Doc2Vec对象,反之则需要训练数据:

if os.path.exists(doc2ver_bin):
    print "Find cache file %s" % doc2ver_bin
    model=Doc2Vec.load(doc2ver_bin)
else:
    model=Doc2Vec(size=max_features, window=5, min_count=2, workers=cores,iter=40)
    model.build_vocab(x))
    model.train(x, total_examples=model.corpus_count, epochs=model.iter)
    model.save(doc2ver_bin)


==========================================================================================================================================================================================================================================================================================================================================================================================
深入word2vec(1) - 第一次训练
2016年01月13日 19:07:37 jasonwayne 阅读数:5532
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jasonwayne/article/details/50512657
输入数据格式

语料来自公司爬取的财经类新闻数据,使用了fudannlp进行分词,word2vec要求语料为以空格分隔的单词,因此将分词结果处理成如下的格式(下面是语料中的一行)。

北京 调整 小 客车 指标 摇号 规则 新能源车 不 摇号 新华社 北京 1月 7日 电 ( 记者 丁静 ) 记者 7日 从 北京 市小 客车 指标 调控 管理 办公室 获悉 , 北京 调整 普通 小 客车 指标 摇号 阶梯 中签率 进阶 规则 。 此外 , 2016 年 起 新能源 小 客车 指标 向 通过 资格 审核 的 申请人 直接 配置 , 不 再 进行 摇号 。 2016 年 北京 市小 客车 指标 年度 配额 为 15万 个 , 其中 普通 指标 额度 9万 个 , 示范 应用 新能源 指标 额度 6 万 个 。

    1

将标题和正文拼接在了一起。正文的段落之间有“ ”做为分隔。

语料只有531M,而且新闻没有去重。
使用gensim进行训练

训练环境:16核Intel(R) Xeon(R) CPU E5620 @ 2.40GHz CPU, 24G内存

import logging
from gensim.models import word2vec
import os

current_dir = os.path.dirname(__file__)
logging.basicConfig(format="%(asctime)s: %(levelname)s: %(message)s", level=logging.INFO)

sentences = word2vec.LineSentence(os.path.join(current_dir, "../corpus_format/corpus.txt"))
# 只取100维,可以训练的快一点,完全相同参数下,100维:28w words/s, 300维: 14w words/s
model = word2vec.Word2Vec(size=100, window=5, min_count=3, workers=12)
model.build_vocab(sentences)
model.train(sentences)
model.save(os.path.join(current_dir, '../model/train.model'))


截取部分log

# build_vocab
2016-01-13 18:18:45,435: INFO: PROGRESS: at sentence #120000, processed 89607119 words, keeping 517748 word types
2016-01-13 18:18:45,828: INFO: collected 521590 word types from a corpus of 90421590 raw words and 121280 sentences
2016-01-13 18:18:47,865: INFO: min_count=3 retains 280987 unique words (drops 240603)
2016-01-13 18:18:47,865: INFO: min_count leaves 90077138 word corpus (99% of original 90421590)
2016-01-13 18:18:49,271: INFO: deleting the raw counts dictionary of 521590 items
2016-01-13 18:18:49,335: INFO: sample=0 downsamples 0 most-common words
2016-01-13 18:18:49,335: INFO: downsampling leaves estimated 90077138 word corpus (100.0% of prior 90077138)
2016-01-13 18:18:49,336: INFO: estimated required memory for 280987 words and 100 dimensions: 421480500 bytes
2016-01-13 18:18:49,865: INFO: constructing a huffman tree from 280987 words
2016-01-13 18:19:07,971: INFO: built huffman tree with maximum node depth 25
2016-01-13 18:19:08,121: INFO: resetting layer weights

# train
2016-01-13 18:19:15,085: INFO: training model with 12 workers on 280987 vocabulary and 100 features, using sg=1 hs=1 sample=0 and negative=0
2016-01-13 18:19:15,085: INFO: expecting 121280 examples, matching count from corpus used for vocabulary survey
2016-01-13 18:19:16,755: INFO: PROGRESS: at 0.16% examples, 27575 words/s
2016-01-13 18:19:17,766: INFO: PROGRESS: at 0.99% examples, 177381 words/s
2016-01-13 18:19:19,027: INFO: PROGRESS: at 1.32% examples, 165506 words/s
2016-01-13 18:19:20,223: INFO: PROGRESS: at 1.98% examples, 211065 words/s
...
...
...
2016-01-13 18:24:19,906: INFO: reached end of input; waiting to finish 36 outstanding jobs
2016-01-13 18:24:22,030: INFO: PROGRESS: at 97.13% examples, 286653 words/s
2016-01-13 18:24:23,115: INFO: PROGRESS: at 98.37% examples, 288375 words/s
2016-01-13 18:24:24,235: INFO: PROGRESS: at 99.03% examples, 288916 words/s
2016-01-13 18:24:25,451: INFO: PROGRESS: at 99.44% examples, 288545 words/s
2016-01-13 18:24:26,011: INFO: training on 90421590 raw words took 310.9s, **289706** trained words/s
2016-01-13 18:24:26,017: INFO: saving Word2Vec object under src/../model/train.model, separately None
2016-01-13 18:24:26,017: INFO: not storing attribute syn0norm
2016-01-13 18:24:26,017: INFO: not storing attribute cum_table
2016-01-13 18:24:26,017: INFO: storing numpy array 'syn0' to src/../model/train.model.syn0.npy
2016-01-13 18:24:26,152: INFO: storing numpy array 'syn1' to src/../model/train.model.syn1.npy


# 训练后的模型大小
108M    ./train.model.syn0.npy
108M    ./train.model.syn1.npy
60M     ./train.model
275M    .

训练后,通过查看指定词的最相似单词,直观看看模型的效果。

from gensim.models import Word2Vec
import logging
import codecs
import os

logging.basicConfig(format="%(asctime)s: %(levelname)s: %(message)s", level=logging.INFO)

current_folder = os.path.dirname(__file__)
input_path = os.path.join(current_folder, "../tests/test_words.txt")
output_path = os.path.join(current_folder, "../tests/similar_words.txt")
model_path = os.path.join(current_folder, "../model/train.model")

model = Word2Vec.load(model_path)

with open(input_path) as fin, codecs.open(output_path, 'w', encoding='utf-8') as fout:
    for line in fin:
        word = line.strip().decode('utf-8')
        if word in model.vocab:
            fout.write(word + ": " + ", ".join(map(lambda x: x[0], model.most_similar(word))) + "\n")
        else:
            fout.write(word + ": " + "not in vocab." + "\n")

日志

2016-01-13 18:27:41,751: INFO: loading Word2Vec object from src/../model/train.model
2016-01-13 18:27:45,287: INFO: loading syn0 from src/../model/train.model.syn0.npy with mmap=None
2016-01-13 18:27:45,354: INFO: loading syn1 from src/../model/train.model.syn1.npy with mmap=None
2016-01-13 18:27:45,417: INFO: setting ignored attribute syn0norm to None
2016-01-13 18:27:45,417: INFO: setting ignored attribute cum_table to None
2016-01-13 18:27:45,428: INFO: precomputing L2-norms of word weight vectors

similar_words的结果(越相似的结果在前)

中国: 世界, 外文局, 展览业, 冯兴元, 怀而言, 高知县, 产大型, 肖金成, 漫画节, 国际
浙江: 江苏, 湖南, 河北, 江西, 贵州, 四川, 云南, 施甸县, 黑龙江, 深州市
杭州: 浙江, 南京, 南充, 云曼, 昆明, 郑州, 深圳, 天津, 长沙, 西安
习近平: 总书记, 习, 赴渝, 一席, 讲话, 同志, 军无法, 娓娓道来, 近平, 谆谆
杨幂: 黄磊, 邓超, 赵雅芝, 杨子姗, 刘亦菲, 马伊琍, 林心如, 姚晨, 林志玲, 陈乔恩
科大讯飞: 和晶科技, 易华录, 东方国信, 华录百纳, 楚天科技, 川大智胜, 亿阳信通, 盈方微, 禾欣股份, 银之杰
乐视网: 300104, 同方国芯, 金证股份, 明家科技, 掌趣科技, 互动娱乐, 58.80298.88, 海格通信, 大橡塑, 南玻A
百度: 阿里, 阿里巴巴, 乐视, 浏览器, 爱奇艺, 腾讯, 搜索, 京东, 搜狗, APP
新浪: 牛63180熊65157, 网易, 讯6日, 早捞底, FX168, 网, 财经, 通无混乱, 讯, 徐雯
阿里: 百度, 阿里巴巴, 京东, 数娱, 爱奇艺, 天猫, Uber, 李彦宏, 去哪儿, 苏宁
腾讯: 搜狐, 微信, 爱奇艺, 阿里巴巴, 百度, 乐视, 公众号, 央视, 优酷土豆, 网易
苹果: 三星, 越除, 谷歌, 智能手机, TCL, 一路超, 小米, Fitbit, 索尼, 亚马逊
香蕉: 豆腐, 烤, 女式, 肉, 白面, 狗肉, 菜, 厨娘, 天之蓝, 裤
橘子: 网聊, 义渠, 女郎, 恩爱, 赵雅芝, 马伯庸, 许志安, 出镜, 邓超, 黄磊
财经: 讯6日, 杜琰, 许亚鑫, ZeroHedge, 徐雯, 刘丽丽, 网易, 新浪, 牛63180熊65157, 讯近日
股市: 中国股市, 暴跌, A股, 金融市场, A股市场, 美国股市, 亚太股市, 亚洲股市, 大跌, 动荡不堪
沪指: 上证指数, 翻绿, 险守, 上证综指, 3.02%, 3109.98, 3536.59, 跳水, 翻红, 跌逾
中国平安: 中国人寿, 中国太平, 中国太保, 民生银行, 联想集团, 中国联通, 安信信托, 蒙牛乳业, 产险, 瑞东集团
招商银行: 平安银行, 光大银行, 北京银行, 交通银行, 兴业银行, 上海浦东发展银行, 中信银行, 建设银行, 中国民生银行, 中国光大银行
温家宝: 朱镕基, 刘仲藜, 姜信治, 肖培, 梁建勇, 丹在, 朱之文, 吴仪, 任, 1923年
北京: 倪元锦, 9点05分, 9时, 靳颖姝, 上海, 成都, 吴晶罗宇凡, 赖大臣, 姜琳琳, 魏蔚魏
上海: 深圳, 广州, 长沙, 天津, 南京, 北京, 佛山, 西安, 郑州, 成都
深圳: 广州, 上海, 成都, 武汉, 杭州, 天津, 珠海, 济南, 下沙, 肇嘉浜路
刀切: not in vocab.
馒头: 剪指甲, 梨, 柿子, 热汤, 水泡, 冰糖, 米饭, 厂子, 薯条, 烧饼
包子: 馄饨, 泡面, 竹竿, 柿子, 烤, 放有, 洗净, 包间, 馒头, 换水


大部分词的结果还能看,北京和很多人名联系在了一起,馒头的最相近词竟是剪指甲。
再训练

刚才是进行了一轮训练的效果,再进行五轮训练,再来看看效果。

import logging
from gensim.models import word2vec
from gensim.models import Word2Vec
import os

current_dir = os.path.dirname(__file__)
logging.basicConfig(format="%(asctime)s: %(levelname)s: %(message)s", level=logging.INFO)

sentences = word2vec.LineSentence(os.path.join(current_dir, "../corpus_format/corpus.txt"))
model = Word2Vec.load(os.path.join(current_dir, "../model/train.model"))
for i in range(5):
    model.train(sentences)
model.save(os.path.join(current_dir, '../model/train.model'))

运行时的内存占用

$ pmap 14224

total          1720868K

再进行五轮迭代训练后的结果

中国: 世界, 怀而言, 国际, 展览业, 劲猛, 高知县, 产大型, 韩斌, 冯兴元, 陈文玲
浙江: 江苏, 湖南, 四川, 河北, 江西, 贵州, 杭州, 黑龙江, 云南, 省
杭州: 浙江, 郑州, 南京, 成都, 长沙, 天津, 昆明, 滨江站, 深圳, 云曼
习近平: 总书记, 习, 同志, 念新, 赴渝, 讲话, 谆谆, 邓小平, 一席, 视察
杨幂: 黄磊, 主演, 黄渤, 邓超, 导演, 孙俪, 执导, 郭采洁, 黄晓明, 李仁港
科大讯飞: 东方国信, 水晶光电, 和晶科技, 鸿博股份, 欧菲光, 银江股份, 东软载波, 黄河旋风, 四维图新, 禾欣股份
乐视网: 300104, 华谊兄弟, 明家科技, 互动娱乐, 乐视, 泰亚股份, 金证股份, 同方国芯, 00043, 56.1116.021518.666.59
百度: 阿里巴巴, 阿里, 搜狗, 搜索, 陌陌, 爱奇艺, 浏览器, QQ, Uber, 京东
新浪: 进跌浪, 网易, 讯, FX168, 网, 财经, 牛63180熊65157, 讯6日, 讯据, 汇通
阿里: 阿里巴巴, 百度, 京东, 数娱, 天猫, 去哪儿, 苏宁, Uber, 携程, 乐视
腾讯: 搜狐, 爱奇艺, 微信, 阿里巴巴, 百度, 公众号, 网易, 乐视, 阿里, 搜索
苹果: 三星, 一路超, 小米, iPhone, 华为, 智能手机, 谷歌, TCL, Facebook, 亚马逊
香蕉: 肉, 菜, 米饭, 冬瓜, 炸, 萝卜, 红薯, 红酒, 鸡, 焦糖味
橘子: 唐宜青, 挤成, 甜食, 咸鲜, 粽子, 新派, Enjoy, 热乎乎, 蟠桃, 挂件
财经: 讯6日, 网易, 新浪, 郭亦非, 进跌浪, 刘丽丽, 周纯, 徐雯, 网, 杜琰
股市: 中国股市, 暴跌, A股, 美国股市, A股市场, 金融市场, 亚太股市, 大跌, 亚洲股市, 内地股市
沪指: 上证指数, 上证综指, 翻绿, 跳水, 跌逾, 险守, 低开, 翻红, 午盘, 高开
中国平安: 中国人寿, 中国太保, 民生银行, 工商银行, 中国联通, 华润电力, 万达信息, 中国国航, 马明哲, 锦江股份
招商银行: 兴业银行, 北京银行, 平安银行, 中信银行, 交通银行, 光大银行, 中国民生银行, 建设银行, 中国银行, 宁波银行
温家宝: 朱镕基, 吴仪, 府内, 与习, 九三学社, 理李, 刘仲藜, 枫香镇, 溧水县, 总理
北京: 上海, 倪元锦, 成都, 魏蔚, 王雷生, 晨报, 姜琳琳, 丁瑶瑶, 南京, 刘丽丽
上海: 广州, 深圳, 天津, 长沙, 郑州, 北京, 南京, 成都, 武汉, 西安
深圳: 广州, 上海, 成都, 武汉, 杭州, 天津, 龙岗, 珠海, 广东, 东莞
刀切: not in vocab.
馒头: 肉, 柿子, 米饭, 姜汁, 极显, 润喉, 奶渣, 面条, 白面, 洗
包子: 吃, 碗, 馒头, 盒饭, 葱姜蒜, 衣服, 小方凳, 菜, 米饭, 喝

参考

http://blog.csdn.net/zhoubl668/article/details/24314769
http://arxiv.org/pdf/1402.3722v1.pdf
https://districtdatalabs.silvrback.com/modern-methods-for-sentiment-analysis
https://github.com/piskvorky/gensim/blob/develop/gensim/models/word2vec.py#L428

猜你喜欢

转载自blog.csdn.net/lusic01/article/details/87278824
今日推荐