精华内容
下载资源
问答
  • 文本向量化

    千次阅读 2018-09-02 15:50:35
    Table of Contents 概述 word2vec NNLM C&...文本表示是自然语言处理中的基础工作,文本表示的好坏直接影响到整个自然语言处理系统的性能。...目前对文本向量化大部分的研究都是通过词向量化实现的,...

    Table of Contents

    概述

    word2vec

    NNLM

    C&W

    CBOW and Skip-gram

    doc2vec/str2vec


    概述

    文本表示是自然语言处理中的基础工作,文本表示的好坏直接影响到整个自然语言处理系统的性能。文本向量化就是将文本表示成一系列能够表达文本语义的向量,是文本表示的一种重要方式。目前对文本向量化大部分的研究都是通过词向量化实现的,也有一部分研究者将句子作为文本处理的基本单元,于是产生了doc2vec和str2vec技术。

     

    word2vec

    词袋(Bag Of Word)模型是最早的以词语为基础处理单元的文本项量化方法。该模型产生的向量与原来文本中单词出现的顺序没有关系,而是词典中每个单词在文本中出现的频率。该方法虽然简单易行,但是存在如下三个方面的问题:维度灾难,无法保留词序信息,存在语义鸿沟。

    随着互联网技术的发展,大量无标注数据的产生,研究重心转移到利用无标注数据挖掘有价值的信息上来。词向量(word2vec)技术就是为了利用神经网络,从大量无标注的文本中提取有用的信息而产生的。

    词袋模型只是将词语符号化,所以词袋模型是不包含任何语义信息的。如何使“词表示”包含语义信息是该领域研究者面临的问题。分布假设(distributional hypothesis)的提出为解决上述问题提供了理论基础。该假设的核心思想是:上下文相似的词,其语义也相似。随后有学者整理了利用上下文表示词义的方法,这类方法就是有名的词空间模型(word space model)。通过语言模型构建上下文与目标词之间的关系,是一种常见的方法,神经网络词向量模型就是根据上下文与目标词之间的关系进行建模。

    NNLM

    神经网络语言模型(Neural Network Language Model,NNLM)与传统方法估算的不同在于直接通过一个神经网络结构对n元条件概率进行估计。由于NNLM模型使用低维紧凑的词向量对上下文进行表示,解决了词袋模型带来的数据稀疏、语义鸿沟等问题。另一方面,在相似的上下文语境中,NNLM模型可以预测出相似的目标词,而传统模型无法做到这一点。例如,如果在预料中A=“小狗在院子里趴着”出现1000次,B=“小猫在院子里趴着”出现1次。A和B的唯一区别就是狗和猫,两个词无论在语义还是语法上都相似。根据频率来估算概率P(A)>>P(B),这显然不合理。如果采用NNLM计算P(A)~P(B),因为NNLM模型采用低维的向量表示词语,假定相似的词其词向量也相似。

    C&W

    C&W(context&word,上下文和目标词)主要目的并不在于生成一份好的词向量,甚至不想训练语言模型,而是要用这份词向量去完成 NLP 里面的各种任务,比如词性标注、命名实体识别、短语识别、语义角色标注等等。NNLM模型的目标是构建一个语言概率模型,而C&W则是以生成词向量为目标的模型。C&W模型并没有采用语言模型去求解词语上下文的条件概率,而是直接对n元短语打分,这就省去了NNLM模型中从隐藏层到输出层的权重计算,大大降低了运算量。其核心机理是:如果n元短语在语料库中出现过,那么模型会给该短语打高分;如果是未出现在语料库中的短语则会得到较低的评分。

    CBOW and Skip-gram

    为了更高效地获取词向量,研究者在NNLM和C&W模型的基础上保留其核心部分,得到了CBOW(Continuous Bag of Words)模型和Skip-gram模型。

    CBOW模型使用一段文本的中间词作为目标词,去掉了隐藏层,大幅度提升了计算速率。此外,COBW模型还使用上下文各词的词向量的平均值替代NNLM模型各个拼接的词向量。即根据上下文来预测当前词语的概率,且上下文所有词对当前词出现概率的影响权重是一样的。Skip-gram模型同样没有隐藏层,与CBOW模型输入上下文词的平均词向量不同,它时从目标词w的上下文中选择一个词,将其词向量组成上下文的表示。即根据当前词语来预测上下文概率。

     

    doc2vec/str2vec

    利用world2vec计算词语间的相似度有非常好的效果,word2vec技术也可以用于计算句子或者其他长文本间的相似度,其一般做法是对文本分词后,提取其关键词,用词向量表示这些关键词,接着对关键词向量求平均或者将其拼接,最后利用词向量计算文本间的相似度。这种方法丢失了文本中的语序信息,而文本的语序包含重要信息。为此,有研究者在word2vec的基础上提出了文本向量化(doc2vec),又称str2vec和para2vec。

     

    doc2vec技术存在两种模型--Distributed Memory(DM)和Distributed Bag of Words(DBOW),分别对应word2vec技术里的CBOW和Skip-gram模型。Doc2vec 相对于 word2vec 不同之处在于,在输入层,增添了一个新句子向量Paragraph vector,Paragraph vector 可以被看作是另一个词向量,它扮演了一个记忆,词袋模型中,因为每次训练只会截取句子中一小部分词训练,而忽略了除了本次训练词以外该句子中的其他词,这样仅仅训练出来每个词的向量表达,句子只是每个词的向量累加在一起表达的。正如上文所说的词袋模型的缺点,忽略了文本的词序问题。而 Doc2vec 中的 Paragraph vector 则弥补了这方面的不足,它每次训练也是滑动截取句子中一小部分词来训练,Paragraph Vector 在同一个句子的若干次训练中是共享的,所以同一句话会有多次训练,每次训练中输入都包含 Paragraph vector。它可以被看作是句子的主旨,有了它,该句子的主旨每次都会被放入作为输入的一部分来训练。这样每次训练过程中,不光是训练了词,得到了词向量。同时随着一句话每次滑动取若干词训练的过程中,作为每次训练的输入层一部分的共享 Paragraph vector,该向量表达的主旨会越来越准确。

                                                 DM模型示意图

                                              DBOW模型示意图

    那么 Doc2vec 是怎么预测新的句子 Paragraph vector 呢?其实在预测新的句子的时候,还是会将该 Paragraph vector 随机初始化,放入模型中再重新根据随机梯度下降不断迭代求得最终稳定下来的句子向量。不过在预测过程中,模型里的词向量还有投影层到输出层的 softmax weights 参数是不会变的,这样在不断迭代中只会更新 Paragraph vector,其他参数均已固定,只需很少的时间就能计算出带预测的 Paragraph vector。

    展开全文
  • 文本处理-分词、向量化、TF-IDF理论和实现

    万次阅读 多人点赞 2018-06-28 18:00:18
    在上文中,我们讲到了文本挖掘的预处理的关键一步:“分词”,而在做了分词后,如果我们是做文本分类聚类,则后面关键的特征预处理步骤有向量化向量化的特例Hash Trick,本文我们就对向量化和特例Hash Trick预处理...

    分词

    在做文本挖掘的时候,首先要做的预处理就是分词。英文单词天然有空格隔开容易按照空格分词,但是也有时候需要把多个单词做为一个分词,比如一些名词如“New York”,需要做为一个词看待。而中文由于没有空格,分词就是一个需要专门去解决的问题了。无论是英文还是中文,分词的原理都是类似的,本文就对文本挖掘时的分词原理做一个总结。

    1. 分词的基本原理

        现代分词都是基于统计的分词,而统计的样本内容来自于一些标准的语料库。假如有一个句子:“小明来到荔湾区”,我们期望语料库统计后分词的结果是:"小明/来到/荔湾/区",而不是“小明/来到/荔/湾区”。那么如何做到这一点呢?

        从统计的角度,我们期望"小明/来到/荔湾/区"这个分词后句子出现的概率要比“小明/来到/荔/湾区”大。如果用数学的语言来说说,如果有一个句子SS,它有m种分词选项如下:

    A11A12...A1n1A11A12...A1n1

    A21A22...A2n2A21A22...A2n2

    ........................

    Am1Am2...AmnmAm1Am2...Amnm

     

        其中下标nini代表第ii种分词的词个数。如果我们从中选择了最优的第rr种分词方法,那么这种分词方法对应的统计分布概率应该最大,即:

    r=argmaxiP(Ai1,Ai2,...,Aini)r=argmax⏟iP(Ai1,Ai2,...,Aini)

     

        但是我们的概率分布P(Ai1,Ai2,...,Aini)P(Ai1,Ai2,...,Aini)并不好求出来,因为它涉及到nini个分词的联合分布。在NLP中,为了简化计算,我们通常使用马尔科夫假设,即每一个分词出现的概率仅仅和前一个分词有关,即:

    P(Aij|Ai1,Ai2,...,Ai(j−1))=P(Aij|Ai(j−1))P(Aij|Ai1,Ai2,...,Ai(j−1))=P(Aij|Ai(j−1))

     

        在前面我们讲MCMC采样时,也用到了相同的假设来简化模型复杂度。使用了马尔科夫假设,则我们的联合分布就好求了,即:

    P(Ai1,Ai2,...,Aini)=P(Ai1)P(Ai2|Ai1)P(Ai3|Ai2)...P(Aini|Ai(ni−1))P(Ai1,Ai2,...,Aini)=P(Ai1)P(Ai2|Ai1)P(Ai3|Ai2)...P(Aini|Ai(ni−1))

     

        而通过我们的标准语料库,我们可以近似的计算出所有的分词之间的二元条件概率,比如任意两个词w1,w2w1,w2,它们的条件概率分布可以近似的表示为:

    P(w2|w1)=P(w1,w2)P(w1)≈freq(w1,w2)freq(w1)P(w2|w1)=P(w1,w2)P(w1)≈freq(w1,w2)freq(w1)

    P(w1|w2)=P(w2,w1)P(w2)≈freq(w1,w2)freq(w2)P(w1|w2)=P(w2,w1)P(w2)≈freq(w1,w2)freq(w2)

     

        其中freq(w1,w2)freq(w1,w2)表示w1,w2w1,w2在语料库中相邻一起出现的次数,而其中freq(w1),freq(w2)freq(w1),freq(w2)分别表示w1,w2w1,w2在语料库中出现的统计次数。

        利用语料库建立的统计概率,对于一个新的句子,我们就可以通过计算各种分词方法对应的联合分布概率,找到最大概率对应的分词方法,即为最优分词。

    2. N元模型

        当然,你会说,只依赖于前一个词太武断了,我们能不能依赖于前两个词呢?即:

    P(Ai1,Ai2,...,Aini)=P(Ai1)P(Ai2|Ai1)P(Ai3|Ai1,Ai2)...P(Aini|Ai(ni−2),Ai(ni−1))P(Ai1,Ai2,...,Aini)=P(Ai1)P(Ai2|Ai1)P(Ai3|Ai1,Ai2)...P(Aini|Ai(ni−2),Ai(ni−1))

     

        这样也是可以的,只不过这样联合分布的计算量就大大增加了。我们一般称只依赖于前一个词的模型为二元模型(Bi-Gram model),而依赖于前两个词的模型为三元模型。以此类推,我们可以建立四元模型,五元模型,...一直到通用的NN元模型。越往后,概率分布的计算复杂度越高。当然算法的原理是类似的。

        在实际应用中,NN一般都较小,一般都小于4,主要原因是N元模型概率分布的空间复杂度为O(|V|N)O(|V|N),其中|V||V|为语料库大小,而NN为模型的元数,当NN增大时,复杂度呈指数级的增长。

        NN元模型的分词方法虽然很好,但是要在实际中应用也有很多问题,首先,某些生僻词,或者相邻分词联合分布在语料库中没有,概率为0。这种情况我们一般会使用拉普拉斯平滑,即给它一个较小的概率值,这个方法在朴素贝叶斯算法原理小结也有讲到。第二个问题是如果句子长,分词有很多情况,计算量也非常大,这时我们可以用下一节维特比算法来优化算法时间复杂度。

    3. 维特比算法与分词

        为了简化原理描述,我们本节的讨论都是以二元模型为基础。

        对于一个有很多分词可能的长句子,我们当然可以用暴力方法去计算出所有的分词可能的概率,再找出最优分词方法。但是用维特比算法可以大大简化求出最优分词的时间。

        大家一般知道维特比算法是用于隐式马尔科夫模型HMM解码算法的,但是它是一个通用的求序列最短路径的方法,不光可以用于HMM,也可以用于其他的序列最短路径算法,比如最优分词。

        维特比算法采用的是动态规划来解决这个最优分词问题的,动态规划要求局部路径也是最优路径的一部分,很显然我们的问题是成立的。首先我们看一个简单的分词例子:"人生如梦境"。它的可能分词可以用下面的概率图表示:

        图中的箭头为通过统计语料库而得到的对应的各分词位置BEMS(开始位置,结束位置,中间位置,单词)的条件概率。比如P(生|人)=0.17。有了这个图,维特比算法需要找到从Start到End之间的一条最短路径。对于在End之前的任意一个当前局部节点,我们需要得到到达该节点的最大概率δδ,和记录到达当前节点满足最大概率的前一节点位置ΨΨ。

        我们先用这个例子来观察维特比算法的过程。首先我们初始化有:

    δ(人)=0.26Ψ(人)=Startδ(人生)=0.44Ψ(人生)=Startδ(人)=0.26Ψ(人)=Startδ(人生)=0.44Ψ(人生)=Start

     

        对于节点"生",它只有一个前向节点,因此有:

    δ(生)=δ(人)P(生|人)=0.0442Ψ(生)=人δ(生)=δ(人)P(生|人)=0.0442Ψ(生)=人

     

         对于节点"如",就稍微复杂一点了,因为它有多个前向节点,我们要计算出到“如”概率最大的路径:

    δ(如)=max{δ(生)P(如|生),δ(人生)P(如|人生)}=max{0.01680,0.3168}=0.3168Ψ(如)=人生δ(如)=max{δ(生)P(如|生),δ(人生)P(如|人生)}=max{0.01680,0.3168}=0.3168Ψ(如)=人生

     

        类似的方法可以用于其他节点如下:

    δ(如梦)=δ(人生)P(如梦|人生)=0.242Ψ(如梦)=人生δ(如梦)=δ(人生)P(如梦|人生)=0.242Ψ(如梦)=人生

    δ(梦)=δ(如)P(梦|如)=0.1996Ψ(梦)=如δ(梦)=δ(如)P(梦|如)=0.1996Ψ(梦)=如

    δ(境)=max{δ(梦)P(境|梦),δ(如梦)P(境|如梦)}=max{0.0359,0.0315}=0.0359Ψ(境)=梦δ(境)=max{δ(梦)P(境|梦),δ(如梦)P(境|如梦)}=max{0.0359,0.0315}=0.0359Ψ(境)=梦

    δ(梦境)=δ(梦境)P(梦境|如)=0.1585Ψ(梦境)=如δ(梦境)=δ(梦境)P(梦境|如)=0.1585Ψ(梦境)=如

     

        最后我们看看最终节点End:

    δ(End)=max{δ(梦境)P(End|梦境),δ(境)P(End|境)}=max{0.0396,0.0047}=0.0396Ψ(End)=梦境δ(End)=max{δ(梦境)P(End|梦境),δ(境)P(End|境)}=max{0.0396,0.0047}=0.0396Ψ(End)=梦境

     

        由于最后的最优解为“梦境”,现在我们开始用ΨΨ反推:

    Ψ(End)=梦境→Ψ(梦境)=如→Ψ(如)=人生→Ψ(人生)=startΨ(End)=梦境→Ψ(梦境)=如→Ψ(如)=人生→Ψ(人生)=start

     

        从而最终的分词结果为"人生/如/梦境"。是不是很简单呢。

        由于维特比算法我会在后面讲隐式马尔科夫模型HMM解码算法时详细解释,这里就不归纳了。

    4. 常用分词工具

        对于文本挖掘中需要的分词功能,一般我们会用现有的工具。简单的英文分词不需要任何工具,通过空格和标点符号就可以分词了,而进一步的英文分词推荐使用nltk。对于中文分词,则推荐用结巴分词(jieba)。这些工具使用都很简单。你的分词没有特别的需求直接使用这些分词工具就可以了。

    5. 结语

        分词是文本挖掘的预处理的重要的一步,分词完成后,我们可以继续做一些其他的特征工程,比如向量化(vectorize),TF-IDF以及Hash trick,这些我们后面再讲。

     

    向量化

    在上文中,我们讲到了文本挖掘的预处理的关键一步:“分词”,而在做了分词后,如果我们是做文本分类聚类,则后面关键的特征预处理步骤有向量化或向量化的特例Hash Trick,本文我们就对向量化和特例Hash Trick预处理方法做一个总结。

    1. 词袋模型

        在讲向量化与Hash Trick之前,我们先说说词袋模型(Bag of Words,简称BoW)。词袋模型假设我们不考虑文本中词与词之间的上下文关系,仅仅只考虑所有词的权重。而权重与词在文本中出现的频率有关。

        词袋模型首先会进行分词,在分词之后,通过统计每个词在文本中出现的次数,我们就可以得到该文本基于词的特征,如果将各个文本样本的这些词与对应的词频放在一起,就是我们常说的向量化。向量化完毕后一般也会使用TF-IDF进行特征的权重修正,再将特征进行标准化。 再进行一些其他的特征工程后,就可以将数据带入机器学习算法进行分类聚类了。

        总结下词袋模型的三部曲:分词(tokenizing),统计修订词特征值(counting)与标准化(normalizing)。

        与词袋模型非常类似的一个模型是词集模型(Set of Words,简称SoW),和词袋模型唯一的不同是它仅仅考虑词是否在文本中出现,而不考虑词频。也就是一个词在文本在文本中出现1次和多次特征处理是一样的。在大多数时候,我们使用词袋模型,后面的讨论也是以词袋模型为主。

        当然,词袋模型有很大的局限性,因为它仅仅考虑了词频,没有考虑上下文的关系,因此会丢失一部分文本的语义。但是大多数时候,如果我们的目的是分类聚类,则词袋模型表现的很好。

    2. 词袋模型之向量化

        在词袋模型的统计词频这一步,我们会得到该文本中所有词的词频,有了词频,我们就可以用词向量表示这个文本。这里我们举一个例子,例子直接用scikit-learn的CountVectorizer类来完成,这个类可以帮我们完成文本的词频统计与向量化,代码如下:

    复制代码

    from sklearn.feature_extraction.text import CountVectorizer  
    vectorizer=CountVectorizer()
    corpus=["I come to China to travel", 
        "This is a car polupar in China",          
        "I love tea and Apple ",   
        "The work is to write some papers in science"] 
    print vectorizer.fit_transform(corpus)

    复制代码

        我们看看对于上面4个文本的处理输出如下:

      (0, 16)	1
      (0, 3)	1
      (0, 15)	2
      (0, 4)	1
      (1, 5)	1
      (1, 9)	1
      (1, 2)	1
      (1, 6)	1
      (1, 14)	1
      (1, 3)	1
      (2, 1)	1
      (2, 0)	1
      (2, 12)	1
      (2, 7)	1
      (3, 10)	1
      (3, 8)	1
      (3, 11)	1
      (3, 18)	1
      (3, 17)	1
      (3, 13)	1
      (3, 5)	1
      (3, 6)	1
      (3, 15)	1

        可以看出4个文本的词频已经统计出,在输出中,左边的括号中的第一个数字是文本的序号,第2个数字是词的序号,注意词的序号是基于所有的文档的。第三个数字就是我们的词频。

        我们可以进一步看看每个文本的词向量特征和各个特征代表的词,代码如下:

    print vectorizer.fit_transform(corpus).toarray()
    print vectorizer.get_feature_names()

        输出如下:

    [[0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 2 1 0 0]
     [0 0 1 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0]
     [1 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0]
     [0 0 0 0 0 1 1 0 1 0 1 1 0 1 0 1 0 1 1]]
    [u'and', u'apple', u'car', u'china', u'come', u'in', u'is', u'love', u'papers', u'polupar', u'science', u'some', u'tea', u'the', u'this', u'to', u'travel', u'work', u'write']

        可以看到我们一共有19个词,所以4个文本都是19维的特征向量。而每一维的向量依次对应了下面的19个词。另外由于词"I"在英文中是停用词,不参加词频的统计。

        由于大部分的文本都只会使用词汇表中的很少一部分的词,因此我们的词向量中会有大量的0。也就是说词向量是稀疏的。在实际应用中一般使用稀疏矩阵来存储。

        将文本做了词频统计后,我们一般会通过TF-IDF进行词特征值修订,这部分我们后面再讲。

        向量化的方法很好用,也很直接,但是在有些场景下很难使用,比如分词后的词汇表非常大,达到100万+,此时如果我们直接使用向量化的方法,将对应的样本对应特征矩阵载入内存,有可能将内存撑爆,在这种情况下我们怎么办呢?第一反应是我们要进行特征的降维,说的没错!而Hash Trick就是非常常用的文本特征降维方法。

    3.  Hash Trick

        在大规模的文本处理中,由于特征的维度对应分词词汇表的大小,所以维度可能非常恐怖,此时需要进行降维,不能直接用我们上一节的向量化方法。而最常用的文本降维方法是Hash Trick。说到Hash,一点也不神秘,学过数据结构的同学都知道。这里的Hash意义也类似。

        在Hash Trick里,我们会定义一个特征Hash后对应的哈希表的大小,这个哈希表的维度会远远小于我们的词汇表的特征维度,因此可以看成是降维。具体的方法是,对应任意一个特征名,我们会用Hash函数找到对应哈希表的位置,然后将该特征名对应的词频统计值累加到该哈希表位置。如果用数学语言表示,假如哈希函数hh使第ii个特征哈希到位置jj,即h(i)=jh(i)=j,则第ii个原始特征的词频数值ϕ(i)ϕ(i)将累加到哈希后的第jj个特征的词频数值ϕ¯ϕ¯上,即:

    ϕ¯(j)=∑i∈J;h(i)=jϕ(i)ϕ¯(j)=∑i∈J;h(i)=jϕ(i)

     

        其中JJ是原始特征的维度。

        但是上面的方法有一个问题,有可能两个原始特征的哈希后位置在一起导致词频累加特征值突然变大,为了解决这个问题,出现了hash Trick的变种signed hash trick,此时除了哈希函数hh,我们多了一个一个哈希函数:

    ξ:N→±1ξ:N→±1

     

        此时我们有

    ϕ¯(j)=∑i∈J;h(i)=jξ(i)ϕ(i)ϕ¯(j)=∑i∈J;h(i)=jξ(i)ϕ(i)

     

        这样做的好处是,哈希后的特征仍然是一个无偏的估计,不会导致某些哈希位置的值过大。

        当然,大家会有疑惑,这种方法来处理特征,哈希后的特征是否能够很好的代表哈希前的特征呢?从实际应用中说,由于文本特征的高稀疏性,这么做是可行的。如果大家对理论上为何这种方法有效,建议参考论文:Feature hashing for large scale multitask learning.这里就不多说了。

        在scikit-learn的HashingVectorizer类中,实现了基于signed hash trick的算法,这里我们就用HashingVectorizer来实践一下Hash Trick,为了简单,我们使用上面的19维词汇表,并哈希降维到6维。当然在实际应用中,19维的数据根本不需要Hash Trick,这里只是做一个演示,代码如下:

    from sklearn.feature_extraction.text import HashingVectorizer 
    vectorizer2=HashingVectorizer(n_features = 6,norm = None)
    print vectorizer2.fit_transform(corpus)

        输出如下:

      (0, 1)	2.0
      (0, 2)	-1.0
      (0, 4)	1.0
      (0, 5)	-1.0
      (1, 0)	1.0
      (1, 1)	1.0
      (1, 2)	-1.0
      (1, 5)	-1.0
      (2, 0)	2.0
      (2, 5)	-2.0
      (3, 0)	0.0
      (3, 1)	4.0
      (3, 2)	-1.0
      (3, 3)	1.0
      (3, 5)	-1.0

        大家可以看到结果里面有负数,这是因为我们的哈希函数ξξ可以哈希到1或者-1导致的。

        和PCA类似,Hash Trick降维后的特征我们已经不知道它代表的特征名字和意义。此时我们不能像上一节向量化时候可以知道每一列的意义,所以Hash Trick的解释性不强。

    4. 向量化与Hash Trick小结

        这里我们对向量化与它的特例Hash Trick做一个总结。在特征预处理的时候,我们什么时候用一般意义的向量化,什么时候用Hash Trick呢?标准也很简单。

        一般来说,只要词汇表的特征不至于太大,大到内存不够用,肯定是使用一般意义的向量化比较好。因为向量化的方法解释性很强,我们知道每一维特征对应哪一个词,进而我们还可以使用TF-IDF对各个词特征的权重修改,进一步完善特征的表示。

        而Hash Trick用大规模机器学习上,此时我们的词汇量极大,使用向量化方法内存不够用,而使用Hash Trick降维速度很快,降维后的特征仍然可以帮我们完成后续的分类和聚类工作。当然由于分布式计算框架的存在,其实一般我们不会出现内存不够的情况。因此,实际工作中我使用的都是特征向量化。

        向量化与Hash Trick就介绍到这里,下一篇我们讨论TF-IDF。

    TF-IDF概述

    在上文中我们讲到在文本挖掘的预处理中,向量化之后一般都伴随着TF-IDF的处理,那么什么是TF-IDF,为什么一般我们要加这一步预处理呢?这里就对TF-IDF的原理做一个总结。

    1. 文本向量化特征的不足

        在将文本分词并向量化后,我们可以得到词汇表中每个词在各个文本中形成的词向量,比如在文本挖掘预处理之向量化与Hash Trick这篇文章中,我们将下面4个短文本做了词频统计:

    corpus=["I come to China to travel", 
        "This is a car polupar in China",          
        "I love tea and Apple ",   
        "The work is to write some papers in science"] 

        不考虑停用词,处理后得到的词向量如下:

    [[0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 2 1 0 0]
     [0 0 1 1 0 1 1 0 0 1 0 0 0 0 1 0 0 0 0]
     [1 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0]
     [0 0 0 0 0 1 1 0 1 0 1 1 0 1 0 1 0 1 1]]

        如果我们直接将统计词频后的19维特征做为文本分类的输入,会发现有一些问题。比如第一个文本,我们发现"come","China"和“Travel”各出现1次,而“to“出现了两次。似乎看起来这个文本与”to“这个特征更关系紧密。但是实际上”to“是一个非常普遍的词,几乎所有的文本都会用到,因此虽然它的词频为2,但是重要性却比词频为1的"China"和“Travel”要低的多。如果我们的向量化特征仅仅用词频表示就无法反应这一点。因此我们需要进一步的预处理来反应文本的这个特征,而这个预处理就是TF-IDF。

    2. TF-IDF概述

        TF-IDF是Term Frequency -  Inverse Document Frequency的缩写,即“词频-逆文本频率”。它由两部分组成,TF和IDF。

        前面的TF也就是我们前面说到的词频,我们之前做的向量化也就是做了文本中各个词的出现频率统计,并作为文本特征,这个很好理解。关键是后面的这个IDF,即“逆文本频率”如何理解。在上一节中,我们讲到几乎所有文本都会出现的"to"其词频虽然高,但是重要性却应该比词频低的"China"和“Travel”要低。我们的IDF就是来帮助我们来反应这个词的重要性的,进而修正仅仅用词频表示的词特征值。

        概括来讲, IDF反应了一个词在所有文本中出现的频率,如果一个词在很多的文本中出现,那么它的IDF值应该低,比如上文中的“to”。而反过来如果一个词在比较少的文本中出现,那么它的IDF值应该高。比如一些专业的名词如“Machine Learning”。这样的词IDF值应该高。一个极端的情况,如果一个词在所有的文本中都出现,那么它的IDF值应该为0。

        上面是从定性上说明的IDF的作用,那么如何对一个词的IDF进行定量分析呢?这里直接给出一个词xx的IDF的基本公式如下:

    IDF(x)=logNN(x)IDF(x)=logNN(x)

     

        其中,NN代表语料库中文本的总数,而N(x)N(x)代表语料库中包含词xx的文本总数。为什么IDF的基本公式应该是是上面这样的而不是像N/N(x)N/N(x)这样的形式呢?这就涉及到信息论相关的一些知识了。感兴趣的朋友建议阅读吴军博士的《数学之美》第11章。

        上面的IDF公式已经可以使用了,但是在一些特殊的情况会有一些小问题,比如某一个生僻词在语料库中没有,这样我们的分母为0, IDF没有意义了。所以常用的IDF我们需要做一些平滑,使语料库中没有出现的词也可以得到一个合适的IDF值。平滑的方法有很多种,最常见的IDF平滑后的公式之一为:

    IDF(x)=logN+1N(x)+1+1IDF(x)=logN+1N(x)+1+1

     

        有了IDF的定义,我们就可以计算某一个词的TF-IDF值了:

    TF−IDF(x)=TF(x)∗IDF(x)TF−IDF(x)=TF(x)∗IDF(x)

     

        其中TF(x)TF(x)指词xx在当前文本中的词频。

    3. 用scikit-learn进行TF-IDF预处理

        在scikit-learn中,有两种方法进行TF-IDF的预处理。

        第一种方法是在用CountVectorizer类向量化之后再调用TfidfTransformer类进行预处理。第二种方法是直接用TfidfVectorizer完成向量化与TF-IDF预处理。

        首先我们来看第一种方法,CountVectorizer+TfidfTransformer的组合,代码如下:

    复制代码

    from sklearn.feature_extraction.text import TfidfTransformer  
    from sklearn.feature_extraction.text import CountVectorizer  
    
    corpus=["I come to China to travel", 
        "This is a car polupar in China",          
        "I love tea and Apple ",   
        "The work is to write some papers in science"] 
    
    vectorizer=CountVectorizer()
    
    transformer = TfidfTransformer()
    tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))  
    print tfidf

    复制代码

        输出的各个文本各个词的TF-IDF值如下:

      (0, 4)	0.442462137895
      (0, 15)	0.697684463384
      (0, 3)	0.348842231692
      (0, 16)	0.442462137895
      (1, 3)	0.357455043342
      (1, 14)	0.453386397373
      (1, 6)	0.357455043342
      (1, 2)	0.453386397373
      (1, 9)	0.453386397373
      (1, 5)	0.357455043342
      (2, 7)	0.5
      (2, 12)	0.5
      (2, 0)	0.5
      (2, 1)	0.5
      (3, 15)	0.281131628441
      (3, 6)	0.281131628441
      (3, 5)	0.281131628441
      (3, 13)	0.356579823338
      (3, 17)	0.356579823338
      (3, 18)	0.356579823338
      (3, 11)	0.356579823338
      (3, 8)	0.356579823338
      (3, 10)	0.356579823338

        现在我们用TfidfVectorizer一步到位,代码如下:

    from sklearn.feature_extraction.text import TfidfVectorizer
    tfidf2 = TfidfVectorizer()
    re = tfidf2.fit_transform(corpus)
    print re

        输出的各个文本各个词的TF-IDF值和第一种的输出完全相同。大家可以自己去验证一下。

        由于第二种方法比较的简洁,因此在实际应用中推荐使用,一步到位完成向量化,TF-IDF与标准化。

    4. TF-IDF小结

        TF-IDF是非常常用的文本挖掘预处理基本步骤,但是如果预处理中使用了Hash Trick,则一般就无法使用TF-IDF了,因为Hash Trick后我们已经无法得到哈希后的各特征的IDF的值。使用了IF-IDF并标准化以后,我们就可以使用各个文本的词特征向量作为文本的特征,进行分类或者聚类分析。

        当然TF-IDF不光可以用于文本挖掘,在信息检索等很多领域都有使用。因此值得好好的理解这个方法的思想。

    流程总结

    在对文本做数据分析时,我们一大半的时间都会花在文本预处理上,而中文和英文的预处理流程稍有不同,本文就对中文文本挖掘的预处理流程做一个总结。

    1. 中文文本挖掘预处理特点

        首先我们看看中文文本挖掘预处理和英文文本挖掘预处理相比的一些特殊点。

        首先,中文文本是没有像英文的单词空格那样隔开的,因此不能直接像英文一样可以直接用最简单的空格和标点符号完成分词。所以一般我们需要用分词算法来完成分词,在文本挖掘的分词原理中,我们已经讲到了中文的分词原理,这里就不多说。

        第二,中文的编码不是utf8,而是unicode。这样会导致在分词的时候,和英文相比,我们要处理编码的问题。

        这两点构成了中文分词相比英文分词的一些不同点,后面我们也会重点讲述这部分的处理。当然,英文分词也有自己的烦恼,这个我们在以后再讲。了解了中文预处理的一些特点后,我们就言归正传,通过实践总结下中文文本挖掘预处理流程。

    2.  中文文本挖掘预处理一:数据收集

        在文本挖掘之前,我们需要得到文本数据,文本数据的获取方法一般有两种:使用别人做好的语料库和自己用爬虫去在网上去爬自己的语料数据。

        对于第一种方法,常用的文本语料库在网上有很多,如果大家只是学习,则可以直接下载下来使用,但如果是某些特殊主题的语料库,比如“机器学习”相关的语料库,则这种方法行不通,需要我们自己用第二种方法去获取。

        对于第二种使用爬虫的方法,开源工具有很多,通用的爬虫我一般使用beautifulsoup。但是我们我们需要某些特殊的语料数据,比如上面提到的“机器学习”相关的语料库,则需要用主题爬虫(也叫聚焦爬虫)来完成。这个我一般使用ache。 ache允许我们用关键字或者一个分类算法来过滤出我们需要的主题语料,比较强大。

    3.  中文文本挖掘预处理二:除去数据中非文本部分

        这一步主要是针对我们用爬虫收集的语料数据,由于爬下来的内容中有很多html的一些标签,需要去掉。少量的非文本内容的可以直接用Python的正则表达式(re)删除, 复杂的则可以用beautifulsoup来去除。去除掉这些非文本的内容后,我们就可以进行真正的文本预处理了。

    4. 中文文本挖掘预处理三:处理中文编码问题

        由于Python2不支持unicode的处理,因此我们使用Python2做中文文本预处理时需要遵循的原则是,存储数据都用utf8,读出来进行中文相关处理时,使用GBK之类的中文编码,在下面一节的分词时,我们再用例子说明这个问题。

    5. 中文文本挖掘预处理四:中文分词

        常用的中文分词软件有很多,个人比较推荐结巴分词。安装也很简单,比如基于Python的,用"pip install jieba"就可以完成。下面我们就用例子来看看如何中文分词。

        首先我们准备了两段文本,这两段文本在两个文件中。两段文本的内容分别是nlp_test0.txt和nlp_test2.txt:

              沙瑞金赞叹易学习的胸怀,是金山的百姓有福,可是这件事对李达康的触动很大。易学习又回忆起他们三人分开的前一晚,大家一起喝酒话别,易学习被降职到道口县当县长,王大路下海经商,李达康连连赔礼道歉,觉得对不起大家,他最对不起的是王大路,就和易学习一起给王大路凑了5万块钱,王大路自己东挪西撮了5万块,开始下海经商。没想到后来王大路竟然做得风生水起。沙瑞金觉得他们三人,在困难时期还能以沫相助,很不容易。

        沙瑞金向毛娅打听他们家在京州的别墅,毛娅笑着说,王大路事业有成之后,要给欧阳菁和她公司的股权,她们没有要,王大路就在京州帝豪园买了三套别墅,可是李达康和易学习都不要,这些房子都在王大路的名下,欧阳菁好像去住过,毛娅不想去,她觉得房子太大很浪费,自己家住得就很踏实。

       我们先讲文本从第一个文件中读取,并使用中文GBK编码,再调用结巴分词,最后把分词结果用uft8格式存在另一个文本nlp_test1.txt

    中。代码如下:

    复制代码

    # -*- coding: utf-8 -*-
    
    import jieba
    
    with open('./nlp_test0.txt') as f:
        document = f.read()
        
        document_decode = document.decode('GBK')
        document_cut = jieba.cut(document_decode)
        #print  ' '.join(jieba_cut)  //如果打印结果,则分词效果消失,后面的result无法显示
        result = ' '.join(document_cut)
        result = result.encode('utf-8')
        with open('./nlp_test1.txt', 'w') as f2:
            f2.write(result)
    f.close()
    f2.close()

    复制代码

        输出的文本内容如下:

        沙 瑞金 赞叹 易 学习 的 胸怀 , 是 金山 的 百姓 有福 , 可是 这件 事对 李达康 的 触动 很大 。 易 学习 又 回忆起 他们 三人 分开 的 前一晚 , 大家 一起 喝酒 话别 , 易 学习 被 降职 到 道口 县当 县长 , 王 大路 下海经商 , 李达康 连连 赔礼道歉 , 觉得 对不起 大家 , 他 最 对不起 的 是 王 大路 , 就 和 易 学习 一起 给 王 大路 凑 了 5 万块 钱 , 王 大路 自己 东挪西撮 了 5 万块 , 开始 下海经商 。 没想到 后来 王 大路 竟然 做 得 风生水 起 。 沙 瑞金 觉得 他们 三人 , 在 困难 时期 还 能 以沫 相助 , 很 不 容易 。

        可以发现对于一些人名和地名,jieba处理的不好,不过我们可以帮jieba加入词汇如下:

    jieba.suggest_freq('沙瑞金', True)
    jieba.suggest_freq('易学习', True)
    jieba.suggest_freq('王大路', True)
    jieba.suggest_freq('京州', True)

        现在我们再来进行读文件,编码,分词,编码和写文件,代码如下:

    复制代码

    with open('./nlp_test0.txt') as f:
        document = f.read()
        
        document_decode = document.decode('GBK')
        document_cut = jieba.cut(document_decode)
        #print  ' '.join(jieba_cut)
        result = ' '.join(document_cut)
        result = result.encode('utf-8')
        with open('./nlp_test1.txt', 'w') as f2:
            f2.write(result)
    f.close()
    f2.close()   

    复制代码

        输出的文本内容如下:

        沙瑞金 赞叹 易学习 的 胸怀 , 是 金山 的 百姓 有福 , 可是 这件 事对 李达康 的 触动 很大 。 易学习 又 回忆起 他们 三人 分开 的 前一晚 , 大家 一起 喝酒 话别 , 易学习 被 降职 到 道口 县当 县长 , 王大路 下海经商 , 李达康 连连 赔礼道歉 , 觉得 对不起 大家 , 他 最 对不起 的 是 王大路 , 就 和 易学习 一起 给 王大路 凑 了 5 万块 钱 , 王大路 自己 东挪西撮 了 5 万块 , 开始 下海经商 。 没想到 后来 王大路 竟然 做 得 风生水 起 。 沙瑞金 觉得 他们 三人 , 在 困难 时期 还 能 以沫 相助 , 很 不 容易 。

        基本已经可以满足要求。同样的方法我们对第二段文本nlp_test2.txt进行分词和写入文件nlp_test3.txt。

    复制代码

    with open('./nlp_test2.txt') as f:
        document2 = f.read()
        
        document2_decode = document2.decode('GBK')
        document2_cut = jieba.cut(document2_decode)
        #print  ' '.join(jieba_cut)
        result = ' '.join(document2_cut)
        result = result.encode('utf-8')
        with open('./nlp_test3.txt', 'w') as f2:
            f2.write(result)
    f.close()
    f2.close()  

    复制代码

        输出的文本内容如下:

        沙瑞金 向 毛娅 打听 他们 家 在 京州 的 别墅 , 毛娅 笑 着 说 , 王大路 事业有成 之后 , 要 给 欧阳 菁 和 她 公司 的 股权 , 她们 没有 要 , 王大路 就 在 京州 帝豪园 买 了 三套 别墅 , 可是 李达康 和 易学习 都 不要 , 这些 房子 都 在 王大路 的 名下 , 欧阳 菁 好像 去 住 过 , 毛娅 不想 去 , 她 觉得 房子 太大 很 浪费 , 自己 家住 得 就 很 踏实 。

        可见分词效果还不错。

    6. 中文文本挖掘预处理五:引入停用词

        在上面我们解析的文本中有很多无效的词,比如“着”,“和”,还有一些标点符号,这些我们不想在文本分析的时候引入,因此需要去掉,这些词就是停用词。常用的中文停用词表是1208个,下载地址在这。当然也有其他版本的停用词表,不过这个1208词版是我常用的。

        在我们用scikit-learn做特征处理的时候,可以通过参数stop_words来引入一个数组作为停用词表。

        现在我们将停用词表从文件读出,并切分成一个数组备用:

    复制代码

    #从文件导入停用词表
    stpwrdpath = "stop_words.txt"
    stpwrd_dic = open(stpwrdpath, 'rb')
    stpwrd_content = stpwrd_dic.read()
    #将停用词表转换为list  
    stpwrdlst = stpwrd_content.splitlines()
    stpwrd_dic.close()

    复制代码

    7. 中文文本挖掘预处理六:特征处理

        现在我们就可以用scikit-learn来对我们的文本特征进行处理了,在文本挖掘预处理之向量化与Hash Trick中,我们讲到了两种特征处理的方法,向量化与Hash Trick。而向量化是最常用的方法,因为它可以接着进行TF-IDF的特征处理。在文本挖掘预处理之TF-IDF中,我们也讲到了TF-IDF特征处理的方法。这里我们就用scikit-learn的TfidfVectorizer类来进行TF-IDF特征处理。

        TfidfVectorizer类可以帮助我们完成向量化,TF-IDF和标准化三步。当然,还可以帮我们处理停用词。

        现在我们把上面分词好的文本载入内存:

    复制代码

    with open('./nlp_test1.txt') as f3:
        res1 = f3.read()
    print res1
    with open('./nlp_test3.txt') as f4:
        res2 = f4.read()
    print res2

    复制代码

        这里的输出还是我们上面分完词的文本。现在我们可以进行向量化,TF-IDF和标准化三步处理了。注意,这里我们引入了我们上面的停用词表。

    from sklearn.feature_extraction.text import TfidfVectorizer
    corpus = [res1,res2]
    vector = TfidfVectorizer(stop_words=stpwrdlst)
    tfidf = vector.fit_transform(corpus)
    print tfidf

        部分输出如下:

      (0, 44)	0.154467434933
      (0, 59)	0.108549295069
      (0, 39)	0.308934869866
      (0, 53)	0.108549295069
      ....
      (1, 27)	0.139891059658
      (1, 47)	0.139891059658
      (1, 30)	0.139891059658
      (1, 60)	0.139891059658

        我们再来看看每次词和TF-IDF的对应关系:

    复制代码

    wordlist = vector.get_feature_names()#获取词袋模型中的所有词  
    # tf-idf矩阵 元素a[i][j]表示j词在i类文本中的tf-idf权重
    weightlist = tfidf.toarray()  
    #打印每类文本的tf-idf词语权重,第一个for遍历所有文本,第二个for便利某一类文本下的词语权重
    for i in range(len(weightlist)):  
        print "-------第",i,"段文本的词语tf-idf权重------"  
        for j in range(len(wordlist)):  
            print wordlist[j],weightlist[i][j]  

    复制代码

        部分输出如下:

    -------第 0 段文本的词语tf-idf权重------
    一起 0.217098590137
    万块 0.217098590137
    三人 0.217098590137
    三套 0.0
    下海经商 0.217098590137
    .....
    -------第 1 段文本的词语tf-idf权重------
    .....
    李达康 0.0995336411066
    欧阳 0.279782119316
    毛娅 0.419673178975
    沙瑞金 0.0995336411066
    没想到 0.0
    没有 0.139891059658
    浪费 0.139891059658
    王大路 0.29860092332
    .....

     

    8. 中文文本挖掘预处理七:建立分析模型

        有了每段文本的TF-IDF的特征向量,我们就可以利用这些数据建立分类模型,或者聚类模型了,或者进行主题模型的分析。比如我们上面的两段文本,就可以是两个训练样本了。此时的分类聚类模型和之前讲的非自然语言处理的数据分析没有什么两样。因此对应的算法都可以直接使用。而主题模型是自然语言处理比较特殊的一块,这个我们后面再单独讲。

    9.中文文本挖掘预处理总结

        上面我们对中文文本挖掘预处理的过程做了一个总结,希望可以帮助到大家。需要注意的是这个流程主要针对一些常用的文本挖掘,并使用了词袋模型,对于某一些自然语言处理的需求则流程需要修改。比如我们涉及到词上下文关系的一些需求,此时不能使用词袋模型。而有时候我们对于特征的处理有自己的特殊需求,因此这个流程仅供自然语言处理入门者参考。

     

     

    转自:https://www.cnblogs.com/pinard/p/6677078.html

    展开全文
  • 而是文章的作者与众不同,写文章的不是人,而是网络机器人,或者说是人工智能,是算法通过分析大量财经文章后,学会了人如何编写财经报道,然后根据相关模式,把各种财经数据组织起来,自动的生成一篇文章,当人...

    前几年,腾讯新闻曾发出一片具有爆炸性的文章。并不是文章的内容有什么新奇之处,而是文章的作者与众不同,写文章的不是人,而是网络机器人,或者说是人工智能,是算法通过分析大量财经文章后,学会了人如何编写财经报道,然后根据相关模式,把各种财经数据组织起来,自动化的生成一篇文章,当人阅读时,根本无法意识到文章不是人写,而是电脑生成的。

    从本节开始,如何使用神经网络构造出一个能阅读,理解人类文本含义的智能程序。就如同前一章讲述的图像识别网络,本质上并不是网络能像人一样看懂了图片内涵,而是网络通过读取大量图片数据,从数据中抽取出某种固定规律,我们本章要开发的神经网络也同理,它会从大量的文本数据中分析抽取出其潜在的固定模式或规律。

    要想让网络能够分析文本,我们首先要做的是将文本进行数据化。它主要包含几个方面的内容:一种方法是将文本分割成词组,并将词组转变为向量。一种方法是将文本分割成一系列字符的组合,然后用向量表示每个字符。第三种方法是把若干个词或字符组合成一个集合,然后将他们转换成向量。无论何种情况,我们都把被向量化的对象成为token,而把整篇文章分解成token的过程叫tokenization。

    举个具体例子,假设我们有一条英文句子”The cat jump over the dog”,如果我们采用第一种方法,那么我们把句子分解成多个单词:’The’,’cat’,’jump’,’over’,’the’,’dog’。然后通过算法为每个单词构造一个向量:
    ‘the’->[0.0, 0.0, 0.4,0.0,1.0,0.0]
    ‘cat’->[0.5, 1.0, 0.5, 0.2, 0.5, 0.5, 0.0]等,后面我们会研究单词是如何转换成向量的。

    有一种把单词向量化的简单方法叫one-hot-encoding,我们在前面章节看过这种向量,它所有元素都是0,只有某个位置是1,例如上面例句中总共有5个不同单词,于是我们可以用含有5个元素的向量来表示:
    ‘The’ -> [1,0,0,0,0], ‘cat’->[0,1,0,0,0], ‘jump’->[0,0,1,0,0]以此类推。

    我们看一段如何将单词进行one-hot-encoding的代码:

    import numpy as np
    samples = ['The cat jump over the dog', 'The dog ate my homework']
    
    #我们先将每个单词放置到一个哈希表中
    token_index = {}
    for sample in samples:
        #将一个句子分解成多个单词
        for word in sample.split():
            if word not in token_index:
                token_index[word] = len(token_index) + 1
    
    #设置句子的最大长度
    max_length = 10
    results = np.zeros((len(samples), max_length, max(token_index.values()) + 1))
    for i, sample in enumerate(samples):
        for j, word in list(enumerate(sample.split()))[: max_length]:
            index = token_index.get(word)
            results[i, j, index] = 1.
            print("{0} -> {1}".format(word, results[i, j]))
    

    上面代码运行后结果如下:

    屏幕快照 2018-08-02 下午4.19.33.png

    其实这种”脏活累活“不需要我们亲自动手,keras框架提供了一系列接口帮我们省却了这些麻烦,下面代码同样能实现相同内容:

    from keras.preprocessing.text import Tokenizer
    samples = ['The cat jump over the dog', 'The dog ate my homework']
    #只考虑最常使用的前1000个单词
    tokenizer = Tokenizer(num_words = 1000)
    tokenizer.fit_on_texts(samples)
    #把句子分解成单词数组
    sequences = tokenizer.texts_to_sequences(samples)
    print(sequences)
    one_hot_vecs = tokenizer.texts_to_matrix(samples, mode='binary')
    
    word_index = tokenizer.word_index
    print("当前总共有%s个不同单词"%len(word_index))

    上面代码运行后,结果如下:

    [[1, 3, 4, 5, 1, 2], [1, 2, 6, 7, 8]]
    当前总共有8个不同单词

    one_hot_vecs对应两个含有1000个元素的向量,第一个向量的第1,3,4,5个元素为1,其余全为0,第二个向量第1,2,6,7,8个元素为1,其余全为0.

    接下来我们要看自然语言处理中一个极为关键的概念叫word embedding,也就是用非零向量来表示每一个单词。one-hot-vector对单词进行编码有很多缺陷,一是冗余过多,一大堆0,然后只有一个1,二是向量的维度过高,有多少个单词,向量就有多少维度,这会给计算带来很多麻烦,word-embedding把原来高维度的冗余向量转换为低纬度的,信息量强的向量,转换后的向量,无论单词量多大,向量的维度一般只有256维到1024维。

    单词向量化的一个关键目标是,意思相近的单词,他们对应的向量之间的距离要接近,例如”good”,”fine”都表示“好”的意思,因此这两个单词对应的向量在空间上要比较接近的,也就是说意思相近的单词,他们对应的向量在空间上的距离应该比较小。假设给定4个单词:‘cat’, ‘dog’, ‘wolf’, ‘tiger’,其中前两个猫和狗相似性强,后两个狼和狗相似性强,因此当他们转换成向量时,在空间距离上会形成两个集合,例如下图:

    屏幕快照 2018-08-02 下午5.16.10.png

    给定一个单词,我们如何生成对应向量呢?我们可以构造一个网络来实现这个目标,假设有两个单词,”good”, “fine”,我们随机给他们赋值两个向量,然后不断的训练网络,让这两个向量之间的距离变得越来越小,好在我们不用从零开始开发这样的网络,keras框架给我们提供了现成可用的类似网络,我们看下面一段代码:

    from keras.layers import Embedding
    #Embedding对象接收两个参数,一个是单词量总数,另一个是单词向量的维度
    embedding_layer = Embedding(1000, 64) 

    上面代码创建一个叫Embedding的网络层,它接收的参数格式如(samples, sequence_length),假设我们现在有32条句子,每条句子最多包含10个单词,那么我们提交的输入参数就是(32, 10),Embedding一开始会给每个单词随意生成一个含有64个元素的向量,然后通过读入大量的数据,调整每个单词对应的向量,让意思相近的单词所对应的向量在空间上的距离越来越近。

    详细理论不多说,我们先跑起来一个例子,看看如何分析影评文本所展现的情绪,我们使用的还是以前见过的IMDB数据,我们只抽取每篇影评的前20个单词,然后为单词向量化,由于影评中只有两种情绪,好和坏,好影评中所用到的单词大多含有“好”的意思,因此对应的向量在空间上会聚合在一起形成一个集合,坏影评使用的单词大多都包含“坏”的意思,于是他们对应的向量就会聚合到一起形成另一个集合,当遇到新影评时,我们也把它的前20个单词向量化,然后看这些向量靠近哪一个集合,如果靠近第一个集合,我们就预测该影评包含正能量,如果靠近第二个集合,我们就认为影评包含负能量。我们看看代码的实现:

    from keras.models import Sequential
    from keras.layers import Flatten, Dense
    
    model = Sequential()
    #在网络中添加Embedding层,专门用于把单词转换成向量
    model.add(Embedding(10000, 8, input_length=maxlen))
    
    '''
    我们给Embeding层输入长度不超过maxlen的单词向量,它为每个单词构造长度为8的向量
    它会输出格式为(samples, maxlen, 8)的结果,然后我们把它转换为(samples, maxlen*8)的
    二维格式
    '''
    model.add(Flatten())
    
    #我们在顶部加一层只含有1个神经元的网络层,把Embedding层的输出结果对应成两个类别
    model.add(Dense(1, activation='sigmoid'))
    model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics = ['acc'])
    model.summary()
    
    history = model.fit(x_train, y_train, epochs = 10, batch_size = 32, validation_split=0.2)

    运行上面代码后,我们把结果绘制出来,看看网络对检验是数据集的识别准确率:

    import matplotlib.pyplot as plt
    
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    
    epochs = range(1, len(acc) + 1)
    
    #绘制模型对训练数据和校验数据判断的准确率
    plt.plot(epochs, acc, 'bo', label = 'trainning acc')
    plt.plot(epochs, val_acc, 'b', label = 'validation acc')
    plt.title('Trainning and validation accuary')
    plt.legend()
    
    plt.show()
    plt.figure()
    
    #绘制模型对训练数据和校验数据判断的错误率
    plt.plot(epochs, loss, 'bo', label = 'Trainning loss')
    plt.plot(epochs, val_loss, 'b', label = 'Validation loss')
    plt.title('Trainning and validation loss')
    plt.legend()
    
    plt.show()

    上面代码运行后结果如下:

    屏幕快照 2018-08-03 上午8.28.29.png

    我们从上图可以看到蓝色实线看出,仅仅通过将影评的前20个单词向量化,在没使用任何调优手段的情况下,网络对校验是数据的识别准确率就达到75%左右。

    还记得前面我们使用预先训练好的网络大大提升图片识别率吗,单词向量化也一样,有人使用上面提到的Embedding网络层分析读取大量文本后,为常用的英文单词都建立了对应的向量。我们自己运用神经网络处理具体问题时,一大困难在于数据量太少,巧妇难为无米之炊,数据量太小,神经网络的精确度会受到极大的制约,如果我们手上的文本数量很少,那么为单词建立的向量就不会很准确,要弥补这些缺陷,我们可以使用别人训练好的结果。

    网络时代的一大优势就是,有人愿意把自己的劳动果实无偿分享出来。当前实现单词向量化的最好算法是由Google研究员Mikolov在2013年发明的Word2Vec算法,有人或组织就使用该算法分析大量英文文本后,为常用的单词建立向量,并把这些向量信息放在网上供人下载。另一个常用的单词向量数据库叫”GloVe”,是由斯坦福教授根据单词的统计特性开发的向量化算法对常用单词向量化后形成的数据库。

    在下一节我们将看看,如何使用预先训练的单词向量化数据”GloVe”实现原始文本的分割,量化并进行有效的分析。

    更详细的讲解和代码调试演示过程,请点击链接

    更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
    这里写图片描述

    展开全文
  • Spark的Parquet向量化读取原理

    千次阅读 2018-08-14 22:17:15
    在深度学习领域使用的针对于文本的数据结构,将文本抽象成向量后可以使用各种针对于向量的数学理论和高性能向量化计算实践(即1)。 3,SQL引擎向量化 在SQL计算引擎中使用的和Code Generation相结合的技术...

    起因:

    测试过程中,发现一个spark的一个参数设置可以带来5倍以上的性能差异

    参数:
    spark.sql.parquet.enableVectorizedReader

    SQL:

    SELECT * FROM ad_tetris_dw.ad_insight_record_hourly_test WHERE page_url = "www.chengzijianzhan.com/tetris/page/52202031760/" and date = '20180805' and hour = '08'
    

    运行结果:

    这里写图片描述

    参数设置为true运行时间22s,设置为false运行时间5.4min,于是好奇什么样的差异能带来如此大的性能提升,结果就接触到一个新的名词——向量化计算

    向量化的搜索结果

    这里写图片描述

    搜索引擎搜索“向量化”结果经总结大致分为三类:

    1,向量化运算

        能够充分利用CPU性能的编程语言/库/API
    

    2,词向量化

       在深度学习领域使用的针对于文本的数据结构,将文本抽象成向量后可以使用各种针对于向量的数学理论和高性能向量化计算实践(即1)。
    

    3,SQL引擎向量化

        在SQL计算引擎中使用的和Code Generation相结合的技术
    

    2,3依赖于1,2不是我的关注点,本文介绍向量化运算和SQL引擎向量化的主要思想和少量细节。

    向量化运算:

    解释不清楚但需要知道的前提:CPU层面存在复杂的指令优化,这些优化比高级语言层次的优化强大的多。其中做向量化计算的叫SIMD。

    向量化运算是与四则运算,函数运算相对应的概念

    1,一般运算:对一个变量进行一个四则运算或函数运算将结果存储于原来位置
    2,向量化运算:对一组变量(一个向量)进行运算将结果存储于原来位置

    举例:对于一个数组arr = [1,2,3,4,5,6,7,8,9,10]执行+1操作,一般运算需要使用循环逐个处理,而支持向量化运算的语言可以使用arr+1。到达CPU指令集层面,一般运算需要调用10次指令集,控制寄存器执行“+”操作,而向量化运算,可能只需要一次将arr加载到CPU cache,然后使用多个寄存器并行执行“+”操作。显然,这是一个时间和空间都节约的方式(向量化计算带来变量减少,代码简洁,减少调用栈)。

    因此对于性能有需求的语言和类库都会对指令进行优化,比如java语言会将简单的for循环展开为向量化指令SIMD。而python的高性能计算库numpy底层是C实现的,较比python原生有数十上百倍的速度提升。

    例子:

    这里写图片描述

    向量化运算既省时间,又省空间,因此成为板蓝根,常备。

    SQL引擎向量化

    查询执行引擎 (query execution engine) 是数据库中的一个核心组件,用于将查询计划转换为物理计划,并对其求值返回结果。查询执行引擎对数据库系统性能影响很大,目前主要的执行引擎有如下四类:Volcano-style,Block-oriented processing,Column-at-a-time,Vectored iterator model。下面分别介绍这四种执行引擎。详细介绍见http://www.infoq.com/cn/articles/an-article-mastering-sql-on-hadoop-core-technology 本文只解释最基本的火山模型和向量化模型,另两个模型都是在演化过程中的中间产物。

    火山式模型

    任何SQL引擎都要将SQL转化为执行计划,执行计划是一个由算子组成的tree或者DAG。其中每一个 operator 包含三个函数:open,next,close。Open 用于申请资源,比如分配内存,打开文件,close 用于释放资源,next 方法递归的调用子 operator 的 next 方法生成一个元组。下图描述了 select id,name,age from people where age >30 的火山模型的查询计划,该查询计划包含 User,Project,Select,Scan 四个 operator,每个 operator 的 next 方法递归调用子节点的 next,一直递归调用到叶子节点 Scan operato,Scan Operator 的 next 从文件中返回一个元组。

    这里写图片描述

    向量化模型

    火山模型清晰合理,然而存在很大性能浪费,每处理一个元组(tuple)最少需要调用一次next(),next()函数是由编译器调用虚函数实现的。虚函数调用是非常消耗性能的,何况对于OLAP要调用数亿万次。因此优化目标明确——减少虚函数调用次数。这就回到了上面说到的向量化运算。如果将逐个的元组操作转化为向量化操作,每次读取next操作读取多条数据,就可以节约大量CPU时间。例如以下是Spark的向量化读取代码:

      /**
       * Advances to the next batch of rows. Returns false if there are no more.
       */
      public boolean nextBatch() throws IOException {
        columnarBatch.reset();
        if (rowsReturned >= totalRowCount) return false;
        checkEndOfRowGroup();
    
        int num = (int) Math.min((long) columnarBatch.capacity(), totalCountLoadedSoFar - rowsReturned);
        for (int i = 0; i < columnReaders.length; ++i) {
          if (columnReaders[i] == null) continue;
          columnReaders[i].readBatch(num, columnarBatch.column(i));
        }
        rowsReturned += num;
        columnarBatch.setNumRows(num);
        numBatched = num;
        batchIdx = 0;
        return true;
      }

    顾名知意,是分 batch拉去数据的。

    这就是SQL引擎的向量化,但这还远远不够,完整的向量化模型是结合列式存储,向量化计算,代码生成的综合优化方案。

    列式存储:

    列式存储在OLAP应用中意义不必多说,减少数据读取量,按列压缩获取更高的压缩率和IO效率等等。在向量化计算层面也有意义,上面说到每次next()都读取多条数据,如果是行式存储,则意味着一次性读入多行,然而实际计算中,对于不同的字段计算过程当然是不同的,因此同时存在于CPU Cache的数据不能用同样的操作处理,但如果是列式存储,读入cache的数据就可以一并操作,这就提高了CPU Cache的命中率,性能显然会得到提升。

    Code Generation:

    一个事实是:如想实现select id,name,age from people where age >30的结果,一个大学生也可以手写代码搞出比上面的算子实现性能高得多的程序,如下图:

    这里写图片描述

    这是因为:

    1、没有虚函数调用:手写版本的代码没有虚函数调用。上面已经说过了虚函数调用是性能浪费的罪魁祸首

    2、内存和CPU寄存器中的临时数据:在Volcano模型中,每次一个算子给另外一个算子传递元组的时候,都需要将这个元组存放在内存中;而在手写版本的代码中,编译器实际上将临时数据存放在CPU寄存器中。访问内存中的数据所需要的CPU时间比直接访问在寄存器中的数据要大一个数量级!

    3、循环展开(Loop unrolling)和SIMD:当运行简单的循环时,现代编译器和CPU是令人难以置信的高效。编译器会自动展开简单的循环,甚至在每个CPU指令中产生SIMD指令来处理多个元组。CPU的特性,比如管道(pipelining)、预取(prefetching)以及指令重排序(instruction reordering)使得运行简单的循环非常地高效。然而这些编译器和CPU对复杂函数调用图的优化极少,而这些函数正是Volcano模型依赖的。

    因此,一个自然的想法就是要把SQL解析完的执行计划转化成手写代码的样子,就可以避免诸多不必要计算了。这个功能就是自动代码生成,Spark中叫whole stage code generation(WSCG),这样可以避免上述的问题1和问题3。

    关于whole-stage code generation技术的JIRA可以到SPARK-12795里查看;而关于vectorization技术的JIRA可以到SPARK-12992查看。

    代码简析:

    ParquetRecordReader和VectorizedParquetRecordReader

    列式数据存储的读取的基类是RecordReader,RecordReader是Hadoop提供的抽象类,定义如下:

    package org.apache.hadoop.mapreduce;
    
    import java.io.Closeable;
    import java.io.IOException;
    import org.apache.hadoop.classification.InterfaceAudience.Public;
    import org.apache.hadoop.classification.InterfaceStability.Stable;
    
    @Public
    @Stable
    public abstract class RecordReader<KEYIN, VALUEIN> implements Closeable {
      public RecordReader() {
      }
    
      public abstract void initialize(InputSplit var1, TaskAttemptContext var2) throws IOException, InterruptedException;
    
      public abstract boolean nextKeyValue() throws IOException, InterruptedException;
    
      public abstract KEYIN getCurrentKey() throws IOException, InterruptedException;
    
      public abstract VALUEIN getCurrentValue() throws IOException, InterruptedException;
    
      public abstract float getProgress() throws IOException, InterruptedException;
    
      public abstract void close() throws IOException;
    }

    从方法看:initialize,nextKeyValue,close,基本能够和上文火山模型的open,next,close对应。

    Hadoop原生使用的是Parquet项目提供的ParquetRecordReader,ParquetRecordReader没有实现向量化。Parquet向量化读写本应是Parquet本身应该支持的功能,但从社区JIRA上来看(如PARQUET-131 https://issues.apache.org/jira/browse/PARQUET-131)貌似出于搁置状态,各种SQL引擎都自行实现了向量化。

    Spark的Parquet向量化读取实现是:VectorizedParquetRecordReader,继承SpecificParquetRecordReaderBase,SpecificParquetRecordReaderBase继承RecordReader,在VectorizedParquetRecordReader中增加了initBatch,nextBatch,resultBatch的Batch方法。

    这里写图片描述

    ParquetFileFormat

    VectorizedParquetRecordReader的调用是在ParquetFileFormat的buildReaderWithPartitionValues方法,方法中有一个if判断,判断逻辑如下:

    if (enableVectorizedReader) {
      val vectorizedReader = new VectorizedParquetRecordReader(
        convertTz.orNull, enableOffHeapColumnVector && taskContext.isDefined)
        ......
    } else {
      logDebug(s"Falling back to parquet-mr")
      // ParquetRecordReader returns UnsafeRow
      val reader = if (pushed.isDefined && enableRecordFilter) {
        val parquetFilter = FilterCompat.get(pushed.get, null)
        new ParquetRecordReader[UnsafeRow](new ParquetReadSupport(convertTz), parquetFilter)
      } else {
        new ParquetRecordReader[UnsafeRow](new ParquetReadSupport(convertTz))
      }
      ......
    }

    根据enableVectorizedReader的真假选择Reader的实现,为真使用VectorizedParquetRecordReader,为假使用ParquetRecordReader。其中enableVectorizedReader是通过conf配置的,即我们最开始提到的参数:spark.sql.parquet.enableVectorizedReader

    val enableVectorizedReader: Boolean =
      sqlConf.parquetVectorizedReaderEnabled &&
      resultSchema.forall(_.dataType.isInstanceOf[AtomicType])

    总结

    以上就是关于向量化计算的一点学习成果。总结来说:向量化计算就是充分利用CPU计算能力(提高计算并行度,提高Cache命中率)的编程实践;SQL引擎的向量化模型是结合列式存储,代码生成和向量化编解码的综合优化方案,参考的博客很好,有时间要读。

    参考

    http://www.infoq.com/cn/articles/an-article-mastering-sql-on-hadoop-core-technology
    https://www.iteblog.com/archives/1679.html
    http://www.infoq.com/cn/articles/columnar-databases-and-vectorization
    https://www.zhihu.com/question/67652386
    https://en.wikipedia.org/wiki/Vectorization
    https://www.jianshu.com/p/ad8933dd6407
    https://animeshtrivedi.github.io/spark-parquet-reading

    展开全文
  • 为什么要使用向量化

    千次阅读 2019-03-06 12:28:35
    其实大概每个人都知道向量化后进行计算的速度比循环求解计算快,可是快多少,我们还是不太清楚。那么我就想简单的说下理论再上代码(python)吧。 比如我们有矩阵 a = [1, 2] b = [3, 4] 循环相乘就是 ...
  • 知识图谱向量化表示

    千次阅读 2017-11-04 06:40:09
    作者丨姜天文 学校丨哈工大SCIR博士生 ...知识图谱主要的目标是用来描述真实世界中间存在的各种实体和概念,以及它们之间的关联关系。...本文选择了八篇知识图谱向量表示的论文进行介绍。 Translating
  • 使用 TF-IDF 算法将文本向量化

    千次阅读 2019-06-08 22:02:21
    使用 TF-IDF 算法将文本向量化 理解 TF-IDF 算法 TF-IDF 算法 TF-IDF 算法通过分配权重来反映每个词的重要程度,根据权重对一篇文章中的所有词语从高到低进行排序,权重越高说明重要性越高,排在前几位的词就可以...
  • NLP学习路径(七):NLP文本向量化

    千次阅读 2019-04-10 19:37:17
    1、文本向量化概述 (1)含义 文本向量化就是将文本表示成一系列能够表达文本语义的向量。词语都是表达文本处理的最基本单元。当前阶段,对文本向量化大部分研究都是通过词向量化实现的。但也有一部分将文章或者...
  • 针对影响瓦斯涌出量的因素复杂多样以及各因素之间的非线性问题,采用径向基核函数把支持向量机算法中的低维空间向量集映射到高维空间,进而建立基于实验数据的煤矿瓦斯涌出量预测模型。样本数据分为训练样本、测试...
  • 7.1 文本向量化概述  文本表示是自然语言处理中的基础工作,文本表示的好坏直接影响到整个自然语言处理系统的性能。文本向量化是文本表示的一种重要方式。顾名思义,文本向量化就是将文本表示成一系列能够表达文本...
  • 综述 | 知识图谱向量化表示

    千次阅读 2017-10-25 00:00:00
    该模型已经成为了知识图谱向量化表示的 baseline,并衍生出不同的变体。 模型 △ 目标函数 △ 算法流程 正如目标函数所述,算法核心是令正例的 h+r-l 趋近于 0,...
  • 前面介绍过了word2vec的原理以及生成词向量神经网络模型的常见方法,word2vec基于分布假说理论可以很好的提取词语的语义信息,因此,利用word2vec技术计算词语间的相似度有非常好的效果。同样word2vec技术也用于计算...
  • 支持向量理论详解

    千次阅读 2017-05-05 16:43:52
    支持向量机SVM是一种原创性(非组合)的具有明显直观几何意义的分类算法,具有较高的准确率。源于Vapnik和Chervonenkis关于统计学习的早期工作(1971年),第一篇有关论文由Boser、Guyon、Vapnik发表在1992年。思想...
  • 支持向量机SVM、支持向量回归SVR详细推导

    万次阅读 多人点赞 2019-06-30 09:31:52
    文章详细介绍了支持向量机SVM及其拓展,支持向量回归SVR.并从线性分类和非线性分类的角度出发,详细推导了硬间隔、软间隔和核函数的支持向量机。
  • 词的向量化表示

    千次阅读 2017-04-24 22:07:05
    为什么使用向量表示词但是在NLP中,传统方法通常是把词转换为离散的不可分割的符号,这导致很难利用不同的词之间的关系(所有的单词之间的距离都一样),例如dog:id143,cat:id537,这使得我们需要更多的数据来...
  • 本文以事故致因理论为基础,分析危品事故形成的主要影响因素,构建了危品事故状态向量,全面描述导致危品事故发生的因素,并基于构建的状态向量进行危品事故分析预测应用.利用高维向量对事故状态进行了定义...
  • 上节基本完成了SVM的理论推倒,寻找最大间隔的目标最终转换成求解拉格朗日乘子变量alpha的求解问题,求出了alpha即可求解出SVM的权重W,有了权重也就有了最大间隔距离,但是其实上节我们有个假设:就是训练集是...
  • 基于核矩阵近似计算和正则路径,提出一个新的支持向量机模型选择方法。 -α,证明KMA-α的近似偏差界定理,并得到支持矢量机的模型近似误差界。然后,提出近似模型选择算法AMSRP。该算法应用KMA-α计算的核矩阵的...
  • 机器学习(九):支持向量机SVM(超详细理论基础)

    千次阅读 多人点赞 2019-08-04 22:01:35
    下面便会对机器学习中较为出名的一种算法SVM进行原理阐述和理论推导。可能内容比较多,还需要慢慢查看。 二,SVM是什么?  SVM的英文全称是Support Vector Machines,我们叫它支持向量机。支持向量机是我们用于分类...
  • 文本挖掘预处理之向量化与Hash Trick

    千次阅读 2017-09-26 16:13:18
    在文本挖掘的分词原理中,我们讲到了文本挖掘的预处理的关键一步:“分词”,而在做了分词后,如果我们是做文本分类聚类,则后面关键的特征预处理步骤有向量化向量化的特例Hash Trick,本文我们就对向量化和特例...
  • 向量空间搜索引擎理论

    千次阅读 2019-03-27 20:46:04
    向量空间搜索引擎使用了一个非常简单的计数对于矩阵代数去比较基于词频的文档。 向量空间搜索引擎的第一个主要组成部分是概念术语空间。简单地说,术语空间由每个唯一的单词组成出现在文档集合中。 向量空间搜索引擎...
  • 统计学习理论是建立在一套较坚实的理论基础之上的,为解决有限样本学习问题提供了一个统一的框架,它能将很多现有方法纳入其中,同时,在这一理论基础上发展了一种新的通用学习方法——支持向量机(Support Vector ...
  • 的最小值,以及递归下降过程的向量化(vectorization),分布在第三章中; (3)对《机器学习实战》中给出的实现代码进行了分析,对阅读该书LogisticRegression部分遇到的疑惑进行了解释。没有基础的朋友在...
  • 深入理解词向量-词向量的可视

    千次阅读 热门讨论 2018-08-16 17:47:49
    向量是自然语言分词在词空间中的表示,词之间的距离代表了分词之间的相似性,我们可以使用gensim,tensorflow等框架非常方便的来实现词向量。但词向量在词空间的分布到底是什么样的,如何更好的理解词向量是一个...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 80,925
精华内容 32,370
关键字:

向量化理论