第3篇-《Distributed Representations of Sentences and Documents》(即Doc2vec)精读分享

本文详细解读《Distributed Representations of Sentences and Documents》,探讨句子分布式表示的历史,包括统计模型(Bag-of-words, Bag-of-n-grams)和深度学习方法(加权平均法,深度学习模型)。重点介绍了论文提出的PV-DM和PV-DBOW模型,以及模型的训练和预测过程。" 114561703,10546259,Java自定义导出Excel:灵活设置列与行,"['Java开发', '文件处理', '数据导出', 'Excel工具']
摘要由CSDN通过智能技术生成

论文原文链接

《Distributed Representations of Sentences and Documents》

论文导读

句子分布式表示

    什么叫做句子的分布式表示?顾名思义,和词的分布式表示一样,句子分布式表示指的就是将一个句子,使用向量的形式代表它。有什么好处呢?好处就是:我们知道,词的分布式表示,也就是词向量,可以将词的语义信息和语法信息囊括在向量中,怎么解释呢?从语义的角度来看,经过训练后的词向量,“经济适用房”和“经适房”这两个词的距离(欧式距离或者其他距离等)会比较相近。从语法的角度来看,名词们会聚集在一起,动词们会聚集在一起。从语义特征的应用上来分析,我们可以做文本分类,信息检索,机器翻译。从语法特征的应用上来分析,可以将其应用于词性标注,实体识别。那么,使用句向量代表一个句子,也同样可以将其应用于文本分类,信息检索,机器翻译等等的领域。

句子分布式表示的历史模型

    在这篇文章之前的历史里,有很多方法来做到使用句向量代表一个句子。主要分成两大类:
        1.基于统计的句子分布式表示
        2.基于深度学习的句子分布式表示
    1.基于统计的句子分布式表示
    在这里,基于统计的句子分布式表示我主要将两种方法,一种是Bag-of-words,另一种是Bag-of-n-grams。我们先将下Bag-of-words的方法。Bag-of-words方法很简单,假设我们的语料只有“我/看/了/一篇/博客/,/这篇/博客/让/我/学习到/了/很多”这句话,句子中我直接使用了斜杠代表分好词的结果。那么经过,词频数的统计,我们可以得到如下信息,

频数
2
1
2
一篇 1
博客 2
1
这篇 1
1
学习到 1
很多 1

于是,我们便使用[2, 1, 2, 1, 2, 1, 1, 1, 1, 1]这个向量代表这个句子。
其算法流程是这样的:
    ①根据语料构建一个词表,词表中,每个词都会有个索引。
    ②对于一句话sentence,统计其出现过的词及其词频数。
    ③将这个统计的词频数写成一个向量,这个向量就代表这个句子。
    以上便是Bag-of-words的主要思想。那Bag-of-n-gram又是怎么样的呢?大家可以先去了解下n-gram。在这里,我们拿Bag-of-2-gram来举例子,对于同样的语料,“我/看/了/一篇/博客/,/这篇/博客/让/我/学习到/了/很多”这句话,Bag-of-2-gram去统计“我/看”,“看/了”,“了/一篇”,“一篇/博客”,“博客/,”,“,/这篇”,“这篇/博客”,“博客/让”,“让/我”,“我/学习到”,“学习到/了”,“了/很多”这些组合出现的频数,然后再将这个频数组成的向量作为这个句子的向量。
    经过上面的讲解,大家可以想想,Bag-of-words和Bag-of-n-gram有什么缺点的呢?那就是句向量的维度会很大,并且句向量中很多部分都是0,也就是向量会非常稀疏。因此后续也有学者使用PCA,SVD等方法将其降维,降到一个维度较小的向量,方便计算。但是大家仔细想想也可以知道,这种方法肯定是存在缺陷的。比如,有以下缺点:

  1. 由于是词袋模型,因此就丢失了一些位置信息。
  2. 句向量只是单纯的利用了统计的信息,并没有将一些语法囊括进去。
    对于Bag-of-n-grams模型来说,也有上述的缺点。
        2.基于深度学习的句子分布式表示
        在这里,基于深度学习的句子分布式表示的方法我主要讲两种方法,一种是加权平均法,另一种是深度学习模型。
        我们首先讲一下加权平均法的算法思路,其流程是这样的:
        ①根据语料构建词表
        ②使用词向量的模型(如Skip-gram或者语言模型)训练词向量,得到每个词的词向量
        ③将某个句子中的每个词的词向量,相加然后取平均,得到该句子的句向量。
        以上便是加权平均法的算法思路,思路非常简单,效果也不错。只不过这个方法没有将词的顺序考虑进去。也就是同样会丢失词之间的顺序信息及词与词之间的关系。
        接下来我们讲一下某一个深度学习模型的方法,如下图
    在这里插入图片描述
    算法的流程是这样的:
        ①根据语料构建词表
        ②使用词向量的模型(如Skip-gram或者语言模型)训练词向量,得到每个词的词向量。或者不训练也没关系。
        ③如果第②步骤已经训练出了词向量,那就将句子“我/看/了/这篇/博客”先找到词向量,然后按上面的流程放入到模型中,最终得到一个句向量。如果②中没有提前训练好词向量,那也没关系,随机初始化词向量也可以的。
        从上述的讲解可以知道,这个模型是需要标注数据的,而现实情况是,基于某个业务,标注数据是很难获得的。或者说标注数据是很昂贵的。因此这个模型并不具有通用性。

精读论文前必要的知识储备

  1. 熟悉词向量相关的知识,如word2vec。
  2. 了解使用语言模型训练词向量的方法,如NNLM,ELMOs,BERT。有些人认为word2vec不算是一种语言模型,这个我觉得见仁见智。总之,word2vec可以训练出词向量,语言模型也可以训练出词向量。大家细细品味训练的方法的区别就好了。

论文abstract和introduction

    论文的“摘要”主要介绍了句向量表示的概念和意义,以往句向量表示模型以及它的缺点。本文提出的模型以及它的优点,以及新模型的效果。
    论文的“介绍”部分主要提了一些过往的模型,比如基于统计的句子分布式表示和基于深度学习的句子分布式表示。其实也就是本博客前面所讲的内容。

论文提出的模型及其我个人的理解

PV-DM(Distributed Memory version of Paragraph Vector)

在这里插入图片描述
    PV-DM(Distributed Memory version of Paragraph Vector)模型如上图,大家可以看到,其实模型和word2vec(戳此进入我的word2vec讲解博客)中的CBOW很像,不过仔细观察可以发现,word2vec的CBOW模型,输入是窗口中中心词的周边的词,而该篇论文中,PVDM模型的输入是窗口中除最后一个词外的所有的词,外加该文档的向量,然后去预测窗口中的最后一个词。其实,这就是语言模型。
总结一下,其算法是这样的:类似于NNLM(戳此进入我的NNLM讲解博客)的语言模型,拿前面几个词去预测下一个词。与NNLM不同的是,模型还将一个句子映射成一个向量,一起放入输入,去联合预测下一个词。
在训练阶段:
    首先,通过训练集构建词典,并且随机初始化词向量矩阵,以及句向量矩阵,然后设置n-gram窗口大小(也就是滑动窗口大小)。然后通过不断的迭代,训练词向量矩阵和句向量矩阵。
在测试阶段:
    我们固定词向量以及模型中的其他参数,然后随机初始化要预测的句子的向量矩阵,然后利用梯度下降法去训练要预测的句子的向量矩阵,模型收敛时,测试集中的句向量就生成了。

PV-DBOW(Distributed Bag of Words version of Paragraph Vector)

在这里插入图片描述

    论文中还讲了另一种模型,就是PV-DBOW,其实它的原理和word2vec(戳此进入我的word2vec讲解博客)里面的skip-gram有点像,只不过输入变成了句向量,然后label变成了这个句子中的某个词。

论文复现代码

完整代码地址:https://github.com/haitaifantuan/nlp_paper_understand

语料预处理

    首先,我们需要对语料做预处理操作。
    代码如下,预处理的操作包括,读取原始语料、建立词表、将原始语料转换成id形式等操作。

#coding=utf-8
'''
Author:Haitaifantuan
'''
import os
import nltk
import numpy as np
import collections
import pickle
import random


class Data_preprocessing(object):
    '''
    这个类是用来对数据进行预处理,根据aclImdb数据里面,有5万是带情感标签的数据,有5万是不带情感标签的数据。
    带标签的数据中,有2.5万是训练集的标签,另外2.5万是测试集的标签。
    2.5万训练集的数据中,有1.25万是负面情绪的标签,1.25万正面情绪的标签。
    2.5万测试集的数据中,有1.25万是负面情绪的标签,1.25万正面情绪的标签。
    '''
    def __init__(self):
        # 以下参数需要配置=======================================
        self.window_size = 10
        # 以上参数需要配置=======================================
        
        # 判断文件夹存不存在,不存在就创建一个
        if not os.path.exists('./saved_things/'):
            os.mkdir('./saved_things/')
        if not os.path.exists('./saved_things/model'):
            os.mkdir('./saved_things/model')
        if not os.path.exists('./saved_things/data_pickle'):
            os.mkdir('./saved_things/data_pickle') 
        if not os.path.exists('./saved_things/model/training_set_doesnt_finished_model'):
            os.mkdir('./saved_things/model/training_set_doesnt_finished_model')
        if not os.path.exists('./saved_things/model/training_set_classification_finished_model'):
            os.mkdir('./saved_things/model/training_set_classification_finished_model')
            
        if not os.path.exists('./saved_things/data_pickle/test-input-data-可删除.pickle'):  # 判断某个文件存不存在,不存在就重新读取并且创建这些文件
            # 从本地读取原始语料,并且将其分词后,返回回来。
            self.train_neg_raw_data, self.train_pos_raw_data, self.test_neg_raw_data, self.test_pos_raw_data, self.upsup_raw_data = self.read_all_raw_data_from_local_file()
            # 统计词频,构造常用词词表。
            self.word2id_dictionary = self.construct_word_dictionary()
            # 保存成pickle,下次继续训练可以加快代码运行速度
            with open('./saved_things/data_pickle/all_data_not_combined-可删除.pickle', 'wb') as file:
                pickle.dump([self.train_neg_raw_data, self.train_pos_raw_data, self.test_neg_raw_data, self.test_pos_raw_data, self.upsup_raw_data, self.word2id_dictionary], file)
            
            # 根据常用词词表,将raw_data转换为词所对应的id。
            self.train_zipped, self.test_zipped, self.upsup_raw_data_converted_to_id = self.convert_data_to_word_id_and_shuffle()
            # 保存成pickle,下次继续训练可以加快代码运行速度
            with open('./saved_things/data_pickle/shuffled_data_combined-可删除.pickle', 'wb') as file:
                pickle.dump([self.train_zipped, self.test_zipped, self.upsup_raw_data_converted_to_id], file)

            # 基于转换为id后的训练数据,构建input样本。
            self.train_embedding_word_input_data, self.train_embedding_sentence_input, self.train_embedding_labels, self.train_sentiment_input, self.train_sentiment_labels = self.construct_input_data_and_label(self.train_zipped)
            # 基于转换为id后的测试数据,构建input样本。
            self.test_embedding_word_input_data, self.test_embedding_sentence_input, self.test_embedding_labels, self.test_sentiment_input, self.test_sentiment_labels = self.construct_input_data_and_label(self.test_zipped)
            # 保存成pickle,下次继续训练可以加快代码运行速度
            with open('./saved_things/data_pickle/train-input-data-可删除.pickle', 'wb') as file:
                pickle.dump([self.train_embedding_word_input_data, self.train_embedding_sentence_input, self.train_embedding_labels, self.train_sentiment_input, self.train_sentiment_labels], file)
            with open('./saved_things/data_pickle/test-input-data-可删除.pickle', 'wb') as file:
                pickle.dump([self.test_embedding_word_input_data, self.test_embedding_sentence_input, self.test_embedding_labels, self.test_sentiment_input, self.test_sentiment_labels], file)

        else:
            with open('./saved_things/data_pickle/all_data_not_combined-可删除.pickle', 'rb') as file:
                self.train_neg_raw_data, self.train_pos_raw_data, self.test_neg_raw_data, self.test_pos_raw_data, self.upsup_raw_data, self.word2id_dictionary = pickle.load(file)
            with open('./saved_things/data_pickle/shuffled_data_combined-可删除.pickle', 'rb') as file:
                self.train_zipped, self.test_zipped, self.upsup_raw_data_converted_to_id = pickle.load(file)
            with open('./saved_things/data_pickle/train-input-data-可删除.pickle', 'rb') as file:
                self.train_embedding_word_input_data, self.train_embedding_sentence_input, self.train_embedding_labels, self.train_sentiment_input, self.train_sentiment_labels = pickle.load(file)
            with open('./saved_things/data_pickle/test-input-data-可删除.pickle', 'rb') as file:
                self.test_embedding_word_input_data, self.test_embedding_sentence_input, self.test_embedding_labels, self.test_sentiment_input, self.test_sentiment_labels = pickle.load(file)
            print('从保存下来
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值