精华内容
下载资源
问答
  • 文本分类

    千次阅读 2016-11-01 12:38:39
    文本分类: 预处理 特征选择 DF (Document Frequency) 信息增益 (Information Gain, IG) 熵 (Entropy) 相对熵 (Relative Entropy) χ² 统计量 (Chi-Square) 互信息 (Mutual Information) Robertson & Sparck Jones...

    From My Github - 文本分类

    文本分类:

    1. 预处理
    2. 特征选择
      • DF (Document Frequency)
      • 信息增益 (Information Gain, IG)
      • 熵 (Entropy)
      • 相对熵 (Relative Entropy)
      • χ² 统计量 (Chi-Square)
      • 互信息 (Mutual Information)
      • Robertson & Sparck Jones公式
      • 发生比 (Odds)
      • Term Strength
      • 性能比较
    3. 计算权重
      • tf-idf
    4. 归一化
    5. 训练集、测试集的划分
      • 注意
      • 留出法(hold-out)
      • 交叉验证法(cross validation)
      • 自助法(bootstrapping)
    6. 类别不平衡问题的解决
    7. 分类
    8. 模型评估

    1. 预处理

    这一步包含以下几个内容:

    • 去除标签:这种情况适用于你拿到的数据是带有html标签的时候。推荐使用Python的BeautifulSoup包。
    • 数据清洗:去掉不合适的噪声文档或文档内垃圾数据.
    • 分词,词性标注、过滤:一般来说文本分类使用的是词特征,所以我们需要对文章进行分词;
      为什么要标注词性呢?因为往往具有类别表征能力的词特征往往是名词、动词等(并不绝对)。中文分词工具
      广泛使用的是jieba分词
    • 停用词去除:停用词,比如‘的’‘了’,是一定需要去除的,因为它不具有任何类别表征能力。
    • 词干化:英文单词需要词干化。
    • 词频统计:词频统计是我们后面进行特征提取特征权值计算的基础。

    实践经验

    词频统计时,你需要统计两种词频:语料库词频文档词频,推荐使用的数据结构是python中的dict:

    # 数据存储示例
    corpus_freq_dict = {
        word1: freq1,
        word2: freq2,
        ....
    }
    
    doc_freq_dict = {
        doc1: {
            word1: freq1,
            word2: freq2,
            ...
        },
        doc2: {
            word1: freq1,
            word2: freq2,
            ...
        },
        ...
    }

    2. 特征选择

    用词做特征,不做特征选择这一步的话,很容易出现上万维、甚至几十万维,
    这么多的维度对计算来说可能就是个灾难。即使你的计算资源足够应付,
    那也是对资源的浪费,因为真正对分类起作用的词,可能就只是一少部分。

    一般将维度选择在1000~5000维,这个和特征选择的方法有很大关系。

    特征选择有很多方式,包括:

    • DF (Document Frequency)
    • 信息增益 (Information Gain, IG)
    • (Entropy)
    • 相对熵 (Relative Entropy)
    • χ² 统计量 (Chi-Square)
    • 互信息 (Mutual Information)
    • Robertson & Sparck Jones公式
    • 发生比 (Odds)
    • Term Strength

    我们一一介绍:

    (1)DF (Document Frequency)

    原理

    DF即文档频率:所有文档集合中出现特征term的文档数目。

    • Term的DF小于某个阈值去掉(太少,没有代表性)
    • Term的DF大于某个阈值也去掉(太多,没有区分度)

    基本假设是:低频(DF低)term既对分类预测作用不显著,也不会影响整体性能;如果低频term碰巧是噪声,它也会提高分类准确度。


    评价

    这种方法最简单,而且很容易扩大到很大规模的语料库,因为它的时间复杂度是关于训练文档数的线性复杂度。效果也不错,当你没有足够的计算资源时,你能使用它用很低的成本获得较高的准确度。然而,它通常被认为是一种提高精度的临时方法。

    (2)信息增益(Information Gain)

    回忆一下的定义:

    假设有一个变量X,它可能的取值有n多种,分别是x1x2,……,xn
    每一种取到的概率分别是P1P2,……,Pn,那么X的熵就定义为:

    熵


    再回忆一下条件熵的定义:

    条件熵


    信息增益指的是熵的变化。这里我们需要计算的是一个term为整个分类所能提供的信息增益。
    (即不考虑任何特征的熵和考虑该特征后的熵的差值)。公式如下:

    信息增益

    注意:考虑特征term后的熵用条件熵表示。

    条件熵越小,则信息增益越大,则特征越重要。这很容易理解,
    因为条件熵越小,说明了在此特征下,类别的不确定性越低。

    (3)熵 (Entropy)

    公式如下:

    熵

    你可以看到它其实是条件熵的一部分。

    • 熵越大,说明分布越均匀,越有可能出现在多个类别中。
    • 熵越小,说明分布越倾斜,越有可能出现在单个类别中。

    所以熵越小,特征越重要。

    (4)相对熵 (Relative Entropy)

    回忆一下相对熵定义

    相对熵(relative entropy)又称为KL散度(Kullback–Leibler divergence,简称KLD)。
    它度量两个概率分布PQ差别的非对称性。(离散随机变量)公式:

    相对熵


    这里我们需要度量的是文本类别的概率分布在出现了某个特定词汇条件下的文本类别的概率分布之间的距离。
    因此,我们此时的公式为:

    相对熵

    相对熵越大,特征词对文本类别分布的影响也越大,特征就越重要。

    (5)χ² 统计量 (Chi-Square)

    我们先说结论

    我们使用卡方统计度量term类别独立性的缺乏程度,卡方越大,独立性越小,
    相关性越大,特征越重要。公式:

    卡方统计公式

    上式其实只是检验了term t与类别c的相关性,在多分类下我们需检验t与所有类别的相关性,然后取加权平均(权值为类的概率),公式:

    多分类卡方计算

    注意Max(Chi-square)的使用也很常见,它不过是取多个类别计算出的卡方的最大值罢了。


    推导:

    卡方检验最基本的思想就是通过观察实际值与理论值的偏差来确定理论的正确与否。具体做的时候常常先假设两个变量确实是独立的(“原假设”),然后观察实际值(观察值)与理论值(这个理论值是指“如果两者确实独立”的情况下应该有的值)的偏差程度。如果偏差足够小,我们就认为误差是很自然的样本误差,是测量手段不够精确导致或者偶然发生的,两者确确实实是独立的,此时就接受原假设;如果偏差大到一定程度,使得这样的误差不太可能是偶然产生或者测量不精确所致,我们就认为两者实际上是相关的,即否定原假设,而接受备择假设。

    理论值为E,实际值为x偏差程度的计算公式为:

    卡方偏差

    这个式子就是卡方检验使用的差值衡量公式。当提供了数个样本的观察值x1x2……xi……,xn之后,代入到式中就可以求得卡方值,用这个值与事先设定的阈值比较,如果大于阈值(即偏差很大),就认为原假设不成立,反之则认为原假设成立。

    文本分类的特征选择阶段,一般使用“词t与类别c不相关”来做原假设,计算出的卡方值越大,说明对原假设的偏离越大,我们越倾向于认为原假设的反面情况是正确的。

    对照下表:

    table

    根据原假设,类别c中包含t的文档比例应与所有文档中包含t的文档比例相同。故,A的理论值A'应为:

    A'

    所以A'A的差值根据差值公式有:

    A-A'

    同理,我们可以算出D(B, B'), D(C, C'), D(D, D')

    所以,卡方等于:

    最终卡方

    注意:这里N=(A+B+C+D)。我们的目的是比较所有term的卡方值大小,你会发现(A+C),(B+D),N在所有term的卡方计算中都是一样的,所以可以去掉这几项,所以你会得到我们结论中的第一个公式。

    你可能会有疑问,为什么对于二分类,只需检验与类c的独立性,不考虑与类~c的独立性。你可以自己推导下Chi(term,~c)的公式,它与Chi(term,c)的公式一模一样。这也很符合常理,只有的两类,你检验c,就是检验了~c

    (6)互信息 (Mutual Information)

    数学概念

    互信息被定义为:

    MI

    它度量联合概率P(AB)和边缘概率之积P(A)P(B)的距离。这也很容易理解,当AB相互独立时,P(AB)P(A)P(B)是相等的,此时互信息最小,为0


    文本分类中

    在文本分类中,我们度量P(t^c)P(t)P(c)的距离。公式如下:

    互信息

    互信息越大,距离越小,term与重要。实践场景中下互信息的结果是比较差的。


    评价

    互信息的缺点在于得分很容易受term的边缘概率P(t)的影响。假设两个term的条件概率P(t|c)相等,根据公式,低频term得分却更高。因此互信息并不适合在频率分布差别较大的情况下。

    (7)Robertson & Sparck Jones公式

    RSJ

    (8)发生比 (Odds Ratio)

    定义

    Odds Ratio compares the odds of a feature occurring in one category with the odds for it occurring in another category. It gives a positive score to features that occur more often in one category than in the other, and a negative score if it occurs more in the other. A score of zero means the the odds for a feature to occur in one category is exactly the same as the odds for it to occur in the other, since ln (1) = 0.

    公式:

    OR

    (公式并没看懂0.0)

    发生比绝对值越大,说明term受类分布影响越大,特征越重要。

    (9)Term Strength

    Term Strength is a technique for Feature Selection in Text Mining

    • it doesn’t need a pre-defined list of Stop Words - it discovers them automatically.
    • so it’s a technique for vocabulary reduction in text retrieval.
    • this method estimates term importance based on how often a term appears in “related” documents.

    Strength of a term t

    • measures how informative a word is for identifying two related documents.
    • s(t)=P(t∈y∣t∈x)
    • for two related documents x,y what’s the probability that t belongs to y given it belongs to x?
    • estimate s(t) on training data using Maximum Likelihood Estimation.(此处存疑惑)

    What does it mean “related”?

    • if we know the labels of these documents, then related are those that belong to the same category.
    • what about Unsupervised Learning?
      • use Cosine Similarity to find most related documents.
      • set some threshold t and let all pairs with cosine >t be related.

    (10)性能比较

    KNN

    LLSF

    注意:LLSF(Linear Least Square Fit)是最小二乘拟合的简写。

    从上图我们可以得到以下结论

    • 我们可以看到在特征超过2000维时,CHIIGDF的效果都是很好的(实验证明它们具有较强的相关性),其中CHI的效果最好。TSMI的效果比较差,其中MI的效果最差。
    • TSMI准确率都是可以达到CHI的,只不过需要的term更多。


    总结一下

    比较

    注释favoring common terms意思是favoring common terms or rare terms

    从上表我们可以得出以下结论

    1. Common terms are indeed informative for text categorization tasks.
    2. Using category information for feature selection does not seem to be crucial for excellent performance.

    注意:对性能比较有任何疑问请参见原始论文A comparative study on feature selection in text categorization

    3. 计算权重

    (1)tf-idf

    简介

    TF-IDF(term frequency–inverse document frequency)是一种用于资讯检索与文本挖掘的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降


    原理及公式

    TF-IDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TF-IDF实际上是:TF * IDF
    - TF词频(Term Frequency),它表示词条在文档d中出现的频率。
    - IDF逆向文件频率(Inverse Document Frequency)。IDF的主要思想是:如果包含词条t的文档越少,IDF越大,则说明词条t具有很好的类别区分能力。

    公式

    TFIDF

    注意:idf分母文件数加1是为了防止出现分母为0发生。

    4. 归一化

    关于归一化请看这篇笔记 归一化

    注意:对计算的tf-idf矩阵进行归一化是按sample方向进行的,归一化每一个文档到一个单位长度。其实对tf-idf矩阵按词(特征)进行归一化在计算tfidf值时已经进行了。

    为什么要对sample进行归一化?
    tfidf用来区分同一文档的不同词。假设doc1计算出的tfidf值[1, 2, 3], doc2计算出的tfidf值[2, 4, 6],假设要聚类,不按sample方向归一化的话,很可能认为这两个文档不相似;实际上它们中的词占(文档)比一样,应该是相似的。

    5. 训练集、测试集的划分

    (1)注意

    在划分训练集和测试集的时候需要考虑或者注意以下几点:

    • 划分前先shuffle一下,打乱example顺序。
    • 训练集和测试集(以及验证集)要互斥。
    • 在划分的时候尽量保持数据分布一致。

    (2)留出法(hold-out)

    定义:直接将数据集D划分为两个互斥的集合,其中一个集合作为训练集S,另一个集合作为测试集T。在S上训练出模型后,用T来评估其测试误差,作为对泛化误差的估计。

    注意:留出法你需要考虑是否要还要划分出验证集。验证集用来调模型超参数;一般来讲会在验证集中将模型参数调至最优,然后才在测试集上测试。这么做是为了防止模型在测试集上过拟合。如果数据量较小,可以不用划分验证集。

    (3)交叉验证法(cross validation)

    定义:交叉验证法先将数据集D划分为k个大小相近的互斥子集,每个子集Di尽可能保持数据分布一致性,即从D中通过分层采样得到。然后每次用k个子集的并集作为训练集,余下的那个子集作为测试集;这样可以获得k组训练/测试集,从而进行k次训练测试,最终返回k个测试结果的均值。

    注意:交叉验证法是不需要验证集的。它比留出法多了些计算消耗,但不浪费数据。数据量不多,不想额外再分出验证集,可以使用此方法。

    (4)自助法(bootstrapping)

    定义:给定包含m个样本的数据集D,我们对它进行采样产生数据集D':每次随即从D中挑选一个样本,将其拷贝放入D',然后再将该样本放回初始数据集中,使此样本在下次仍可能被采样到;重复m次,我们得到了包含m个样本的数据集D'。初始数据集中有约36.8%的样本未出现在D'中。于是我们将D'作为训练集(m个),D - D'作为测试集。

    这三种方法我只说了定义,更为详细的介绍请见我的这篇笔记:模型评估与选择——评估方法

    6. 类别不平衡问题的解决

    见这篇笔记 分类类别不平衡

    7. 分类

    8. 模型评估


    Ref

    特征选择方法之信息增益

    条件熵 wikipedia

    相对熵 wikipedia

    文本分类特征选择方法——卡方检验信息增益

    特征选择之互信息

    互信息_wikipedia

    Term Strength - ML Wiki

    A comparative study on feature selection in text categorization

    TF-IDF_wikipedia

    Text feature extraction (tf-idf)

    展开全文
  • 文本分类概述

    千次阅读 2015-01-29 18:16:34
     自动文本分类(Automatic Text Categorization),或者简称为文本分类,是计算机将一篇文章归于预先给定的某一或某几的过程。  文本分类是按照预先定义的主题类别,为文档集合中的每个文档确定一个类别.文本...

    概览

      自动文本分类(Automatic Text Categorization),或者简称为文本分类,是指计算机将一篇文章归于预先给定的某一类或某几类的过程。

      文本分类是指按照预先定义的主题类别,为文档集合中的每个文档确定一个类别.文本分类是文本挖掘的一个重要内容。

      所谓文本分类,是指对所给出的文本,给出预定义的一个或多个类别标号,对文本进行准确、高效的分类.它是许多数据管理任务的重要组成部分。

      文本分类是指按预先指定的标准对文档进行归类这样用户不仅可以方便地浏览文档而且可以通过类别来查询所需的文档。

      文本分类是指在给定的分类体系下,根据文语义元是统计语义方法中的原子,是不可分本的内容自动确定文本类别的过程.当前的文本割的最小单位,在文本分类中语义元是词。

      文本分类(Text categorization)是指在给定分类体系下,根据文本内容自动确定文本类别的过程.20世纪90年代以前,占主导地位的文本分类方法一直是基于知识工程的分类方法,即由专业人员手工进行分类.人工分类非常费时,效率非常低.90年代以来,众多的统计方法和机器学习方法应用于自动文本分类,文本分类技术的研究引起了研究人员的极大兴趣.目前在国内也已经开始对中文文本分类进行研究,并在信息检索、Web文档自动分类、数字图书馆、自动文摘、分类新闻组、文本过滤、单词语义辨析以及文档的组织和管理等多个领域得到了初步的应用.

     

    历史

      文本分类的研究可以追溯到上世纪六十年代,早期的文本分类主要是基于知识工程(Knowledge Engineering),通过手工定义一些规则来对文本进行分类,这种方法费时费力,且必须对某一领域有足够的了解,才能写出合适的规则。到上世纪九十年代,随着网上在线文本的大量涌现和机器学习的兴起,大规模的文本(包括网页)分类和检索重新引起研究者的兴趣。文本分类系统首先通过在预先分类好的文本集上训练,建立一个判别规则或分类器,从而对未知类别的新样本进行自动归类。大量的结果表明它的分类精度比得上专家手工分类的结果,并且它的学习不需要专家干预,能适用于任何领域的学习,使得它成为目前文本分类的主流方法。

     

      1971 年,Rocchio 提出了在用户查询中不断通过用户的反馈来修正类权重向量,来构成简单的线性分类器。Mark vanUden、Mun等给出了其他的一些修改权重的方法。1979 年,van Rijsbergen对信息检索领域的研究做了系统的总结,里面关于信息检索的一些概念,如向量空间模型(Vector Space Model)和评估标准如准确率(Precision)、回召率(Recall),后来被陆续地引入文本分类中,文中还重点地讨论了信息检索的概率模型,而后来的文本分类研究大多数是建立在概率模型的基础上。

     

     1992 年,Lewis 在他的博士论文《Representation and Learning in Information Retrieval》中系统地介绍了文本分类系统实现方法的各个细节,并且在自己建立的数据集Reuters22173(后来去掉一些重复的文本修订为Reuters21578数据集)上进行了测试。这篇博士论文是文本分类领域的经典之作。后来的研究者在特征的降维和分类器的设计方面作了大量的工作,Yiming Yang 对各种特征选择方法,包括信息增益(Information Gain)、互信息(Mutual Information)、统计量等,从实验上进行了分析和比较。她在1997年还对文献上报告的几乎所有的文本分类方法进行了一次大阅兵,在公开数据集Reuters21578和OHSUMED上比较了各个分类器的性能,对后来的研究起到了重要的参考作用。

    1995 年,Vipnik 基于统计理论提出了支持矢量机(Support Vector Machine)方法,基本思想是寻找最优的高维分类超平面。由于它以成熟的小样本统计理论作为基石,因而在机器学习领域受到广泛的重视。Thorsten Joachims第一次将线性核函数的支持矢量机用于文本分类,与传统的算法相比,支持矢量机在分类性能上有了非常大的提高,并且在不同的数据集上显示了算法的鲁棒性。至今,支持矢量机的理论和应用仍是研究的热点。

    在支持矢量机出现的同时,1995年及其后,以Yoav Freund 和Robert E. Schapire发表的关于AdaBoost的论文为标志,机器学习算法的研究出现了另一个高峰。RobertE.Schapire从理论和试验上给出AdaBoost算法框架的合理性。其后的研究者在这个框架下给出了许多的类似的Boosting算法,比较有代表性的有Real AdaBoost,Gentle Boost,LogitBoost等。这些Boosting算法均己被应用到文本分类的研究中,并且取得和支持矢量机一样好的效果。

      总而言之,尽管机器学习理论对于文本分类的研究起了不可低估的作用,在这之前文本分类的研究曾一度处于低潮,但是文本分类的实际应用和它自身的固有的特性给机器学习提出新的挑战,这使得文本分类的研究仍是信息处理领域一个开放的、重要的研究方向。

     

    中文文本分类

      相比于英文文本分类,中文文本分类的一个重要的差别在于预处理阶段:中文文本的读取需要分词,不像英文文本的单词那样有空格来区分。从简单的查词典的方法,到后来的基于统计语言模型的分词方法,中文分词的技术已趋于成熟。比较有影响力的当属中国科学院计算所开发的汉语词法分析系统ICTCLAS,现已公开发布供中文文本分类的研究使用。

      在很长一段时间内,中文文本分类的研究没有公开的数据集,使得分类算法难以比较。现在一般采用的中文测试集有:北京大学建立的人民日报语料库、清华大学建立的现代汉语语料库等。

      其实一旦经过预处理将中文文本变成了样本矢量的数据矩阵,那么随后的文本分类过程和英文文本分类相同,也就是随后的文本分类过程独立于语种。因此,当前的中文文本分类主要集中在如何利用中文本身的一些特征来更好地表示文本样本

    关键技术及方法

      分词技术

      对于中文文本而言,因为词与词之间没有明显的切分标志,所以首先需要对中文文本进行分词.现在的分词方法虽然有多种,但归纳起来不外乎两种:一类是机械式分词法,一般以分词词典为依据,通过文档中的汉字串和词表中的词逐一匹配来完成词的切分.另一类是理解式分词法,即利用汉语的语法知识和语义知识以及心理学知识进行分词,需要建立分词数据库、知识库和推理库.后者可谓是理想的方法,但在语法分析、语义分析乃至篇章理解还没有得到解决之前,其分词系统主要采用机械分词法,或者介于二者之间的某种分词方法。

      文本表示

      计算机并不具有人类的智慧,不能读懂文字,所以必须把文本转化成计算机能够理解的形式,即进行文本表示.目前文本表示模型主要是Gerard Salton和McGill于1969年提出的向量空间模型(VSM)。向量空间模型的基本思想是把文档简化为特征项的权重为分量的向量表示:(w1,w2,…,wn),其中wi为第i个特征项的权重,一般选取词作为特征项,权重用词频表示.词频分为绝对词频和相对词频.绝对词频,即用词在文本中出现的频率表示文本;相对词频,即为归一化的词频,其计算方法主要运用TF-IDF公式。

      除了向量空间模型外,还有概率模型.概率模型也考虑词与词的相关性,把文本集中的文档分为相关文档和无关文档.以数学理论中的概率论为原理,通过赋予特征词某个概率值来表示这些词在相关文档和无关文档之间出现的概率,然后计算文档间相关的概率,系统据此概率做出决策。

      特征选择与特征抽取

      由于文本数据的半结构化甚至于无结构化的特点,当用特征向量对文档进行表示的时候,特征向量通常会达到几万维甚至于几十万维.寻求一种有效的特征降维方法,降低特征空间的维数,提高分类的效率和精度,成为文本自动分类中至关重要的问题.降维技术总的可以分为两类:特征选择和特征抽取。

      文本分类算法

      研究文本自动分类的核心问题是如何构造分类函数(分类器),分类函数需要通过某种算法进行学习获得.分类是重要的数据挖掘方法,在文本分类中,几乎存在着和一般分类同样多的方法.在众多的文本分类算法中,重点介绍了Rocchio算法、朴素贝叶斯分类算法、K-近邻算法、决策树算法、神经网络算法和支持向量机算法

    展开全文
  • python 中文文本分类

    万次阅读 多人点赞 2017-02-06 11:31:21
    写这篇博文用了很多时间和精力,如果这...即已经分好文本资料(例如:语料库里是一系列txt文章,这些文章按照主题归入到不同分类的目录中,如 .\art\21.txt) 推荐语料库:复旦中文文本分类语料库,下载链接: ...

    写这篇博文用了很多时间和精力,如果这篇博文对你有帮助,希望您可以打赏给博主相国大人。哪怕只捐1毛钱,也是一种心意。通过这样的方式,也可以培养整个行业的知识产权意识。我可以和您建立更多的联系,并且在相关领域提供给您更多的资料和技术支持。

    赏金将用于拉萨儿童图书公益募捐

    手机扫一扫,即可:



    目标读者:初级入门学生。本文假定,你对python已经有了最基本的掌握。

    如果你希望能够对python有更多的掌握,可以参考博主的系列博文:

    python高手的自修课


    本文提供了python2.7和python3.6的代码,博客内容的讲解使用的是python2.7,在博客后面给出的源代码github链接中,我们给出了python2.7和python3.6的代码。其中github的master分支是python3.6,github的python2.7分支是python2.7.


    一,中文文本分类流程:

    1. 预处理
    2. 中文分词
    3. 结构化表示--构建词向量空间
    4. 权重策略--TF-IDF
    5. 分类器
    6. 评价

    二,具体细节

    1,预处理

    1.1得到训练集语料库

    即已经分好类的文本资料(例如:语料库里是一系列txt文章,这些文章按照主题归入到不同分类的目录中,如 .\art\21.txt)
    推荐语料库:复旦中文文本分类语料库,下载链接:http://download.csdn.net/detail/github_36326955/9747927

    将下载的语料库解压后,请自己修改文件名和路径,例如路径可以设置为 ./train_corpus/,
    其下则是各个类别目录如:./train_corpus/C3-Art,……,\train_corpus\C39-Sports

    1.2得到测试语料库

    也是已经分好类的文本资料,与1.1类型相同,只是里面的文档不同,用于检测算法的实际效果。测试预料可以从1.1中的训练预料中随机抽取,也可以下载独立的测试语料库,复旦中文文本分类语料库测试集链接:http://download.csdn.net/detail/github_36326955/9747929
    路径修改参考1.1,例如可以设置为 ./test_corpus/

    1.3其他

    你可能希望从自己爬取到的网页等内容中获取新文本,用本节内容进行实际的文本分类,这时候,你可能需要将html标签去除来获取文本格式的文档,这里提供一个基于python 和lxml的样例代码:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    """
    @version: python2.7.8 
    @author: XiangguoSun
    @contact: sunxiangguodut@qq.com
    @file: html_demo.py
    @time: 2017/2/6 12:25
    @software: PyCharm
    """
    import sys
    from lxml import html
    # 设置utf-8 unicode环境
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    def html2txt(path):
        with open(path,"rb") as f:
            content=f.read() 
        r'''
        上面两行是python2.6以上版本增加的语法,省略了繁琐的文件close和try操作
        2.5版本需要from __future__ import with_statement
        新手可以参考这个链接来学习http://zhoutall.com/archives/325
        '''
        page = html.document_fromstring(content) # 解析文件
        text = page.text_content() # 去除所有标签
        return text
    
    if __name__  =="__main__":
        # htm文件路径,以及读取文件
        path = "1.htm"
        text=html2txt(path)
        print text	 # 输出去除标签后解析结果

    2,中文分词

    2.1概述

    第1小节预处理中的语料库都是没有分词的原始语料(即连续的句子,而后面的工作需要我们把文本分为一个个单词),现在需要对这些文本进行分词,只有这样,才能在 基于单词的基础上,对文档进行结构化表示。
    中文分词有其特有的难点(相对于英文而言),最终完全解决中文分词的算法是基于概率图模型的条件随机场(CRF)。(可以参考博主的另一篇博文)
    当然,在实际操作中,即使你对于相关算法不甚了解,也不影响你的操作,中文分词的工具有很多。但是比较著名的几个都是基于java的,这里推荐python的第三方库jieba(所采用的算法就是条件随机场)。对于非专业文档绰绰有余。如果你有强迫症,希望得到更高精度的分词工具,可以使用开源项目Anjs(基于java),你可以将这个开源项目与python整合。
    关于分词库的更多讨论可以参考这篇文章: https://www.zhihu.com/question/19651613

    你可以通过pip安装jieba:打开cmd,切换到目录  .../python/scripts/,执行命令:pip install jieba
    或者你也可以在集成开发平台上安装jieba,例如,如果你用的是pycharm,可以点击file-settings-project:xxx-Projuect Interpreter.其他平台也都有类似的安装方法。

    2.2分词操作

    不要担心下面的代码你看不懂,我会非常详细的进行讲解,确保python入门级别水平的人都可以看懂:
    2.2.1

    首先讲解jieba分词使用方法(详细的和更进一步的,可以参考这个链接):

    jieba.cut 方法接受三个输入参数: 需要分词的字符串;cut_all 参数用来控制是否采用全模式;HMM 参数用来控制是否使用 HMM 模型
    jieba.cut_for_search 方法接受两个参数:需要分词的字符串;是否使用 HMM 模型。该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细
    待分词的字符串可以是 unicode 或 UTF-8 字符串、GBK 字符串。注意:不建议直接输入 GBK 字符串,可能无法预料地错误解码成 UTF-8
    jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode),或者用
    jieba.lcut 以及 jieba.lcut_for_search 直接返回 list
    jieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定义分词器,可用于同时使用不同词典。jieba.dt 为默认分词器,所有全局分词相关函数都是该分词器的映射。

    实例代码:

    import jieba
    
    seg_list = jieba.cut("我来到北京清华大学", cut_all=True)
    print("Full Mode: " + "/ ".join(seg_list))  # 全模式
    
    seg_list = jieba.cut("我来到北京清华大学", cut_all=False)
    print("Default Mode: " + "/ ".join(seg_list))  # 精确模式
    
    seg_list = jieba.cut("他来到了网易杭研大厦")  # 默认是精确模式
    print(", ".join(seg_list))
    
    seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")  # 搜索引擎模式
    print(", ".join(seg_list))

    输出:
    
    【全模式】: 我/ 来到/ 北京/ 清华/ 清华大学/ 华大/ 大学
    
    【精确模式】: 我/ 来到/ 北京/ 清华大学
    
    【新词识别】:他, 来到, 了, 网易, 杭研, 大厦    (此处,“杭研”并没有在词典中,但是也被Viterbi算法识别出来了)
    
    【搜索引擎模式】: 小明, 硕士, 毕业, 于, 中国, 科学, 学院, 科学院, 中国科学院, 计算, 计算所, 后, 在, 日本, 京都, 大学, 日本京都大


    2.2.2

    接下来,我们将要通过python编程,来将1.1节中的 ./train_corpus/原始训练语料库和1.2节中的./test_corpus/原始测试语料库进行分词,分词后保存的路径可以设置为

    ./train_corpus_seg/和./test_corpus_seg/

    代码如下,思路很简单,就是遍历所有的txt文本,然后将每个文本依次进行分词。你唯一需要注意的就是写好自己的路径,不要出错。下面的代码已经给出了非常详尽的解释,初学者也可以看懂。如果你还没有明白,或者在运行中出现问题(其实根本不可能出现问题,我写的代码,质量很高的。。。),都可以发邮件给我,邮件地址在代码中,或者在博文下方评论中给出。

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    """
    @version: python2.7.8 
    @author: XiangguoSun
    @contact: sunxiangguodut@qq.com
    @file: corpus_segment.py
    @time: 2017/2/5 15:28
    @software: PyCharm
    """
    import sys
    import os
    import jieba
    # 配置utf-8输出环境
    reload(sys)
    sys.setdefaultencoding('utf-8')
    # 保存至文件
    def savefile(savepath, content):
        with open(savepath, "wb") as fp:
            fp.write(content)
        '''
        上面两行是python2.6以上版本增加的语法,省略了繁琐的文件close和try操作
        2.5版本需要from __future__ import with_statement
        新手可以参考这个链接来学习http://zhoutall.com/archives/325
        '''
    # 读取文件
    def readfile(path):
        with open(path, "rb") as fp:
            content = fp.read()
        return content
    
    def corpus_segment(corpus_path, seg_path):
        '''
        corpus_path是未分词语料库路径
        seg_path是分词后语料库存储路径
        '''
        catelist = os.listdir(corpus_path)  # 获取corpus_path下的所有子目录
        '''
        其中子目录的名字就是类别名,例如:
        train_corpus/art/21.txt中,'train_corpus/'是corpus_path,'art'是catelist中的一个成员
        '''
    
        # 获取每个目录(类别)下所有的文件
        for mydir in catelist:
            '''
            这里mydir就是train_corpus/art/21.txt中的art(即catelist中的一个类别)
            '''
            class_path = corpus_path + mydir + "/"  # 拼出分类子目录的路径如:train_corpus/art/
            seg_dir = seg_path + mydir + "/"  # 拼出分词后存贮的对应目录路径如:train_corpus_seg/art/
    
            if not os.path.exists(seg_dir):  # 是否存在分词目录,如果没有则创建该目录
                os.makedirs(seg_dir)
    
            file_list = os.listdir(class_path)  # 获取未分词语料库中某一类别中的所有文本
            '''
            train_corpus/art/中的
            21.txt,
            22.txt,
            23.txt
            ...
            file_list=['21.txt','22.txt',...]
            '''
            for file_path in file_list:  # 遍历类别目录下的所有文件
                fullname = class_path + file_path  # 拼出文件名全路径如:train_corpus/art/21.txt
                content = readfile(fullname)  # 读取文件内容
                '''此时,content里面存贮的是原文本的所有字符,例如多余的空格、空行、回车等等,
                接下来,我们需要把这些无关痛痒的字符统统去掉,变成只有标点符号做间隔的紧凑的文本内容
                '''
                content = content.replace("\r\n", "")  # 删除换行
                content = content.replace(" ", "")#删除空行、多余的空格
                content_seg = jieba.cut(content)  # 为文件内容分词
                savefile(seg_dir + file_path, " ".join(content_seg))  # 将处理后的文件保存到分词后语料目录
    
        print "中文语料分词结束!!!"
    
    '''
    如果你对if __name__=="__main__":这句不懂,可以参考下面的文章
    http://imoyao.lofter.com/post/3492bc_bd0c4ce
    简单来说如果其他python文件调用这个文件的函数,或者把这个文件作为模块
    导入到你的工程中时,那么下面的代码将不会被执行,而如果单独在命令行中
    运行这个文件,或者在IDE(如pycharm)中运行这个文件时候,下面的代码才会运行。
    即,这部分代码相当于一个功能测试。
    如果你还没懂,建议你放弃IT这个行业。
    '''
    if __name__=="__main__":
        #对训练集进行分词
        corpus_path = "./train_corpus/"  # 未分词分类语料库路径
        seg_path = "./train_corpus_seg/"  # 分词后分类语料库路径
        corpus_segment(corpus_path,seg_path)
    
        #对测试集进行分词
        corpus_path = "./test_corpus/"  # 未分词分类语料库路径
        seg_path = "./test_corpus_seg/"  # 分词后分类语料库路径
        corpus_segment(corpus_path,seg_path)

    截止目前,我们已经得到了分词后的训练集语料库和测试集语料库,下面我们要把这两个数据集表示为变量,从而为下面程序调用提供服务。我们采用的是Scikit-Learn库中的Bunch数据结构来表示这两个数据集。你或许对于Scikit-Learn和Bunch并不是特别了解,而官方的技术文档有两千多页你可能也没耐心去看,好在你们有相国大人。下面我们 以这两个数据集为背景,对Bunch做一个非常通俗的讲解,肯定会让你一下子就明白。

    首先来看看Bunch:

    Bunch这玩意儿,其实就相当于python中的字典。你往里面传什么,它就存什么。

    好了,解释完了。

    是不是很简单?

    接下来,让我们看看的我们的数据集(训练集)有哪些信息:

    1,类别,也就是所有分类类别的集合,即我们./train_corpus_seg/和./test_corpus_seg/下的所有子目录的名字。我们在这里不妨把它叫做target_name(这是一个列表)

    2,文本文件名。例如./train_corpus_seg/art/21.txt,我们可以把所有文件名集合在一起做一个列表,叫做filenames

    3,文本标签(就是文本的类别),不妨叫做label(与2中的filenames一一对应)

    例如2中的文本“21.txt”在./train_corpus_seg/art/目录下,则它的标签就是art。

    文本标签与1中的类别区别在于:文本标签集合里面的元素就是1中类别,而文本标签集合的元素是可以重复的,因为./train_corpus_seg/art/目录下有好多文本,不是吗?相应的,1中的类别集合元素显然都是独一无二的类别。

    4,文本内容(contens)。

    上一步代码我们已经成功的把文本内容进行了分词,并且去除掉了所有的换行,得到的其实就是一行词袋。


    那么,用Bunch表示,就是:

    from sklearn.datasets.base import Bunch
    bunch = Bunch(target_name=[],label=[],filenames=[],contents=[]) 

    我们在Bunch对象里面创建了有4个成员:
    target_name:是一个list,存放的是整个数据集的类别集合。
    label:是一个list,存放的是所有文本的标签。
    filenames:是一个list,存放的是所有文本文件的名字。
    contents:是一个list,分词后文本文件(一个文本文件只有一行)


    如果你还没有明白,看一下下面这个图,你总该明白了:

    Bunch:


    下面,我们将文本文件转为Bunch类形:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    """
    @version: python2.7.8 
    @author: XiangguoSun
    @contact: sunxiangguodut@qq.com
    @file: corpus2Bunch.py
    @time: 2017/2/7 7:41
    @software: PyCharm
    """
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    import os#python内置的包,用于进行文件目录操作,我们将会用到os.listdir函数
    import cPickle as pickle#导入cPickle包并且取一个别名pickle
    '''
    事实上python中还有一个也叫作pickle的包,与这里的名字相同了,无所谓
    关于cPickle与pickle,请参考博主另一篇博文:
    python核心模块之pickle和cPickle讲解
    http://blog.csdn.net/github_36326955/article/details/54882506
    本文件代码下面会用到cPickle中的函数cPickle.dump
    '''
    from sklearn.datasets.base import Bunch
    #这个您无需做过多了解,您只需要记住以后导入Bunch数据结构就像这样就可以了。
    #今后的博文会对sklearn做更有针对性的讲解
    
    
    def _readfile(path):
        '''读取文件'''
        #函数名前面带一个_,是标识私有函数
        # 仅仅用于标明而已,不起什么作用,
        # 外面想调用还是可以调用,
        # 只是增强了程序的可读性
        with open(path, "rb") as fp:#with as句法前面的代码已经多次介绍过,今后不再注释
            content = fp.read()
        return content
    
    def corpus2Bunch(wordbag_path,seg_path):
        catelist = os.listdir(seg_path)# 获取seg_path下的所有子目录,也就是分类信息
        #创建一个Bunch实例
        bunch = Bunch(target_name=[], label=[], filenames=[], contents=[])
        bunch.target_name.extend(catelist)
        '''
        extend(addlist)是python list中的函数,意思是用新的list(addlist)去扩充
        原来的list
        '''
        # 获取每个目录下所有的文件
        for mydir in catelist:
            class_path = seg_path + mydir + "/"  # 拼出分类子目录的路径
            file_list = os.listdir(class_path)  # 获取class_path下的所有文件
            for file_path in file_list:  # 遍历类别目录下文件
                fullname = class_path + file_path  # 拼出文件名全路径
                bunch.label.append(mydir)
                bunch.filenames.append(fullname)
                bunch.contents.append(_readfile(fullname))  # 读取文件内容
                '''append(element)是python list中的函数,意思是向原来的list中添加element,注意与extend()函数的区别'''
        # 将bunch存储到wordbag_path路径中
        with open(wordbag_path, "wb") as file_obj:
            pickle.dump(bunch, file_obj)
        print "构建文本对象结束!!!"
    
    if __name__ == "__main__":#这个语句前面的代码已经介绍过,今后不再注释
        #对训练集进行Bunch化操作:
        wordbag_path = "train_word_bag/train_set.dat"  # Bunch存储路径
        seg_path = "train_corpus_seg/"  # 分词后分类语料库路径
        corpus2Bunch(wordbag_path, seg_path)
    
        # 对测试集进行Bunch化操作:
        wordbag_path = "test_word_bag/test_set.dat"  # Bunch存储路径
        seg_path = "test_corpus_seg/"  # 分词后分类语料库路径
        corpus2Bunch(wordbag_path, seg_path)

    3,结构化表示--向量空间模型

    在第2节中,我们对原始数据集进行了分词处理,并且通过绑定为Bunch数据类型,实现了数据集的变量表示。也许你对于什么是词向量并没有清晰的概念,这里有一篇非常棒的文章《Deep Learning in NLP (一)词向量和语言模型》,简单来讲,词向量就是词向量空间里面的一个向量。

    你可以类比为三维空间里面的一个向量,例如:

    如果我们规定词向量空间为:(我,喜欢,相国大人),这相当于三维空间里面的(x,y,z)只不过这里的x,y,z的名字变成了“我”,“喜欢”,“相国大人”


    现在有一个词向量是:我 喜欢  喜欢相国大人

    表示在词向量空间中就变为:(1,2,1),归一化后可以表示为:(0.166666666667 0.333333333333 0.166666666667)表示在刚才的词向量空间中就是这样:



    接下来我们要做的,就是把所有这些词统一到同一个词向量空间中。

    为了节省空间,我们首先将训练集中每个文本中一些垃圾词汇去掉。所谓的垃圾词汇,就是指意义模糊的词,或者一些语气助词,标点符号等等,通常他们对文本起不了分类特征的意义。这些垃圾词汇我们称之为停用词。把所有停用词集合起来构成一张停用词表格,这样,以后我们处理文本时,就可以从这个根据表格,过滤掉文本中的一些垃圾词汇了。

    你可以从这里下载停用词表:hlt_stop_words.txt

    存放在这里路径中:train_word_bag/hlt_stop_words.txt


    下面的程序,目的就是要将训练集所有文本文件统一到同一个词向量空间中。

    下面的一节主要目标是希望得到两个东西:

    1.词典(单词和单词对应的序号)

    2.权重矩阵tdm,其中,权重矩阵是一个二维矩阵,tdm[i][j]表示,第j个词(即词典中的序号)在第i个类别中的IF-IDF值(下文有讲解)。

    事实上,tdm的每一列都是一个单词在各个类别中的全职。我们把这每一列当作词向量。



    4,权重策略--TF-IDF

    什么是TF-IDF?今后有精力我会在这里更新补充,现在,先给你推荐一篇非常棒的文章《使用scikit-learn工具计算文本TF-IDF值


    下面,我们假定你已经对TF-IDF有了最基本的了解。请你动动你的小脑袋瓜想一想,我们把训练集文本转换成了一个TF-IDF词向量空间,姑且叫它为A空间吧。那么我们还有测试集数据,我们以后实际运用时,还会有新的数据,这些数据显然也要转到词向量空间,那么应该和A空间为同一个空间吗?

    是的。

    即使测试集出现了新的词汇(不是停用词),即使新的文本数据有新的词汇,只要它不是训练集生成的TF-IDF词向量空间中的词,我们就都不予考虑。这就实现了所有文本词向量空间“大一统”,也只有这样,大家才在同一个世界里。才能进行下一步的研究。


    下面的程序就是要将训练集所有文本文件(词向量)统一到同一个TF-IDF词向量空间中(或者叫做用TF-IDF算法计算权重的有权词向量空间)。这个词向量空间最终存放在train_word_bag/tfdifspace.dat中。

    这段代码你可能有点看不懂,因为我估计你可能比较懒,还没看过TF-IDF(尽管我刚才已经给你推荐那篇文章了)。你只需要明白,它把一大坨训练集数据成功的构建了一个TF-IDF词向量空间,空间的各个词都是出自这个训练集(去掉了停用词)中,各个词的权值也都一并保存了下来,叫做权重矩阵。

    需要注意的是,你要明白,权重矩阵是一个二维矩阵,a[i][j]表示,第j个词在第i个类别中的IF-IDF值(看到这里,我估计你压根就没去看那篇文章,所以你可能到现在也不知道 这是个啥玩意儿。。。)


    请记住权重矩阵这个词,代码解释中我会用到。

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    """
    @version: python2.7.8 
    @author: XiangguoSun
    @contact: sunxiangguodut@qq.com
    @file: vector_space.py
    @time: 2017/2/7 17:29
    @software: PyCharm
    """
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    # 引入Bunch类
    from sklearn.datasets.base import Bunch
    import cPickle as pickle#之前已经说过,不再赘述
    from sklearn.feature_extraction.text import TfidfVectorizer#这个东西下面会讲
    
    # 读取文件
    def _readfile(path):
        with open(path, "rb") as fp:
            content = fp.read()
        return content
    
    # 读取bunch对象
    def _readbunchobj(path):
        with open(path, "rb") as file_obj:
            bunch = pickle.load(file_obj)
        return bunch
    
    # 写入bunch对象
    def _writebunchobj(path, bunchobj):
        with open(path, "wb") as file_obj:
            pickle.dump(bunchobj, file_obj)
    
    #这个函数用于创建TF-IDF词向量空间
    def vector_space(stopword_path,bunch_path,space_path):
    
        stpwrdlst = _readfile(stopword_path).splitlines()#读取停用词
        bunch = _readbunchobj(bunch_path)#导入分词后的词向量bunch对象
        #构建tf-idf词向量空间对象
        tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label, filenames=bunch.filenames, tdm=[], vocabulary={})
        '''
        在前面几节中,我们已经介绍了Bunch。
        target_name,label和filenames这几个成员都是我们自己定义的玩意儿,前面已经讲过不再赘述。
        下面我们讲一下tdm和vocabulary(这俩玩意儿也都是我们自己创建的):
        tdm存放的是计算后得到的TF-IDF权重矩阵。请记住,我们后面分类器需要的东西,其实就是训练集的tdm和标签label,因此这个成员是
        很重要的。
        vocabulary是词典索引,例如
        vocabulary={"我":0,"喜欢":1,"相国大人":2},这里的数字对应的就是tdm矩阵的列
        我们现在就是要构建一个词向量空间,因此在初始时刻,这个tdm和vocabulary自然都是空的。如果你在这一步将vocabulary赋值了一个
        自定义的内容,那么,你是傻逼。
        '''
    
        '''
        与下面这2行代码等价的代码是:
        vectorizer=CountVectorizer()#构建一个计算词频(TF)的玩意儿,当然这里面不只是可以做这些
        transformer=TfidfTransformer()#构建一个计算TF-IDF的玩意儿
        tfidf=transformer.fit_transform(vectorizer.fit_transform(corpus))
        #vectorizer.fit_transform(corpus)将文本corpus输入,得到词频矩阵
        #将这个矩阵作为输入,用transformer.fit_transform(词频矩阵)得到TF-IDF权重矩阵
    
        看名字你也应该知道:
        Tfidf-Transformer + Count-Vectorizer  = Tfidf-Vectorizer
        下面的代码一步到位,把上面的两个步骤一次性全部完成
    
        值得注意的是,CountVectorizer()和TfidfVectorizer()里面都有一个成员叫做vocabulary_(后面带一个下划线)
        这个成员的意义,与我们之前在构建Bunch对象时提到的自己定义的那个vocabulary的意思是一样的,只不过一个是私有成员,一个是外部输入,原则上应该保持一致。显然,我们在第45行中创建tfidfspace中定义的vocabulary就应该被赋值为这个vocabulary_
    
        '''
        #构建一个快乐地一步到位的玩意儿,专业一点儿叫做:使用TfidfVectorizer初始化向量空间模型
        #这里面有TF-IDF权重矩阵还有我们要的词向量空间坐标轴信息vocabulary_
        vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5)
        '''
        关于参数,你只需要了解这么几个就可以了:
        stop_words:
        传入停用词,以后我们获得vocabulary_的时候,就会根据文本信息去掉停用词得到
        vocabulary:
        之前说过,不再解释。
        sublinear_tf:
        计算tf值采用亚线性策略。比如,我们以前算tf是词频,现在用1+log(tf)来充当词频。
        smooth_idf:
        计算idf的时候log(分子/分母)分母有可能是0,smooth_idf会采用log(分子/(1+分母))的方式解决。默认已经开启,无需关心。
        norm:
        归一化,我们计算TF-IDF的时候,是用TF*IDF,TF可以是归一化的,也可以是没有归一化的,一般都是采用归一化的方法,默认开启.
        max_df:
        有些词,他们的文档频率太高了(一个词如果每篇文档都出现,那还有必要用它来区分文本类别吗?当然不用了呀),所以,我们可以
        设定一个阈值,比如float类型0.5(取值范围[0.0,1.0]),表示这个词如果在整个数据集中超过50%的文本都出现了,那么我们也把它列
        为临时停用词。当然你也可以设定为int型,例如max_df=10,表示这个词如果在整个数据集中超过10的文本都出现了,那么我们也把它列
        为临时停用词。
        min_df:
        与max_df相反,虽然文档频率越低,似乎越能区分文本,可是如果太低,例如10000篇文本中只有1篇文本出现过这个词,仅仅因为这1篇
        文本,就增加了词向量空间的维度,太不划算。
        当然,max_df和min_df在给定vocabulary参数时,就失效了。
        '''
    
        #此时tdm里面存储的就是if-idf权值矩阵
        tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
        tfidfspace.vocabulary = vectorizer.vocabulary_
    
        _writebunchobj(space_path, tfidfspace)
        print "if-idf词向量空间实例创建成功!!!"
    
    if __name__ == '__main__':
        stopword_path = "train_word_bag/hlt_stop_words.txt"#停用词表的路径
        bunch_path = "train_word_bag/train_set.dat"  #导入训练集Bunch的路径
        space_path = "train_word_bag/tfdifspace.dat"  # 词向量空间保存路径
        vector_space(stopword_path,bunch_path,space_path)

    上面的代码运行之后,会将训练集数据转换为TF-IDF词向量空间中的实例,保存在train_word_bag/tfdifspace.dat中,具体来说,这个文件里面有两个我们感兴趣的东西,一个是vocabulary,即词向量空间坐标,一个是tdm,即训练集的TF-IDF权重矩阵。


    接下来,我们要开始第5步的操作,设计分类器,用训练集训练,用测试集测试。在做这些工作之前,你一定要记住,首先要把测试数据也映射到上面这个TF-IDF词向量空间中,也就是说,测试集和训练集处在同一个词向量空间(vocabulary相同),只不过测试集有自己的tdm,与训练集(train_word_bag/tfdifspace.dat)中的tdm不同而已。

    同一个世界,同一个梦想。

    至于说怎么弄,请看下节。


    5,分类器

    这里我们采用的是朴素贝叶斯分类器,今后我们会详细讲解它。

    现在,你即便不知道这是个啥玩意儿,也一点不会影响你,这个分类器我们有封装好了的函数,MultinomialNB,这玩意儿获取训练集的权重矩阵和标签,进行训练,然后获取测试集的权重矩阵,进行预测(给出预测标签)。


    下面我们开始动手实践吧!


    首先,我们要把测试数据也映射到第4节中的那个TF-IDF词向量空间上:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    """
    @version: python2.7.8 
    @author: XiangguoSun
    @contact: sunxiangguodut@qq.com
    @file: test.py
    @time: 2017/2/8 11:39
    @software: PyCharm
    """
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    # 引入Bunch类
    from sklearn.datasets.base import Bunch
    import cPickle as pickle
    from sklearn.feature_extraction.text import TfidfVectorizer
    
    def _readfile(path):
        with open(path, "rb") as fp:
            content = fp.read()
        return content
    
    def _readbunchobj(path):
        with open(path, "rb") as file_obj:
            bunch = pickle.load(file_obj)
        return bunch
    
    def _writebunchobj(path, bunchobj):
        with open(path, "wb") as file_obj:
            pickle.dump(bunchobj, file_obj)
    
    def vector_space(stopword_path,bunch_path,space_path,train_tfidf_path):
    
        stpwrdlst = _readfile(stopword_path).splitlines()
        bunch = _readbunchobj(bunch_path)
        tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label, filenames=bunch.filenames, tdm=[], vocabulary={})
    
        #导入训练集的TF-IDF词向量空间
        trainbunch = _readbunchobj(train_tfidf_path)
        tfidfspace.vocabulary = trainbunch.vocabulary
    
        vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5,vocabulary=trainbunch.vocabulary)
        tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
        _writebunchobj(space_path, tfidfspace)
        print "if-idf词向量空间实例创建成功!!!"
    
    if __name__ == '__main__':
        stopword_path = "train_word_bag/hlt_stop_words.txt"#停用词表的路径
        bunch_path = "test_word_bag/test_set.dat"   # 词向量空间保存路径
        space_path = "test_word_bag/testspace.dat"   # TF-IDF词向量空间保存路径
        train_tfidf_path="train_word_bag/tfdifspace.dat"
        vector_space(stopword_path,bunch_path,space_path,train_tfidf_path)

    你已经发现了,这段代码与第4节几乎一模一样,唯一不同的就是在第39~41行中,我们导入了第4节中训练集的IF-IDF词向量空间,并且第41行将训练集的vocabulary赋值给测试集的vocabulary,第43行增加了入口参数vocabulary,原因在上一节中都已经说明,不再赘述。

    考虑到第4节和刚才的代码几乎完全一样,因此我们可以将这两个代码文件统一为一个:


    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    """
    @version: python2.7.8 
    @author: XiangguoSun
    @contact: sunxiangguodut@qq.com
    @file: TFIDF_space.py
    @time: 2017/2/8 11:39
    @software: PyCharm
    """
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    from sklearn.datasets.base import Bunch
    import cPickle as pickle
    from sklearn.feature_extraction.text import TfidfVectorizer
    
    def _readfile(path):
        with open(path, "rb") as fp:
            content = fp.read()
        return content
    
    def _readbunchobj(path):
        with open(path, "rb") as file_obj:
            bunch = pickle.load(file_obj)
        return bunch
    
    def _writebunchobj(path, bunchobj):
        with open(path, "wb") as file_obj:
            pickle.dump(bunchobj, file_obj)
    
    def vector_space(stopword_path,bunch_path,space_path,train_tfidf_path=None):
    
        stpwrdlst = _readfile(stopword_path).splitlines()
        bunch = _readbunchobj(bunch_path)
        tfidfspace = Bunch(target_name=bunch.target_name, label=bunch.label, filenames=bunch.filenames, tdm=[], vocabulary={})
    
        if train_tfidf_path is not None:
            trainbunch = _readbunchobj(train_tfidf_path)
            tfidfspace.vocabulary = trainbunch.vocabulary
            vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5,vocabulary=trainbunch.vocabulary)
            tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
    
        else:
            vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5)
            tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
            tfidfspace.vocabulary = vectorizer.vocabulary_
    
        _writebunchobj(space_path, tfidfspace)
        print "if-idf词向量空间实例创建成功!!!"
    
    if __name__ == '__main__':
    
        stopword_path = "train_word_bag/hlt_stop_words.txt"
        bunch_path = "train_word_bag/train_set.dat"
        space_path = "train_word_bag/tfdifspace.dat"
        vector_space(stopword_path,bunch_path,space_path)
    
        bunch_path = "test_word_bag/test_set.dat"
        space_path = "test_word_bag/testspace.dat"
        train_tfidf_path="train_word_bag/tfdifspace.dat"
        vector_space(stopword_path,bunch_path,space_path,train_tfidf_path)


    哇哦,你好棒!现在连注释都不用,就可以看懂代码了。。。

    对测试集进行了上述处理后,接下来的步骤,变得如此轻盈和优雅。


    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    """
    @version: python2.7.8 
    @author: XiangguoSun
    @contact: sunxiangguodut@qq.com
    @file: NBayes_Predict.py
    @time: 2017/2/8 12:21
    @software: PyCharm
    """
    import sys
    reload(sys)
    sys.setdefaultencoding('utf-8')
    
    import cPickle as pickle
    from sklearn.naive_bayes import MultinomialNB  # 导入多项式贝叶斯算法
    
    
    # 读取bunch对象
    def _readbunchobj(path):
        with open(path, "rb") as file_obj:
            bunch = pickle.load(file_obj)
        return bunch
    
    # 导入训练集
    trainpath = "train_word_bag/tfdifspace.dat"
    train_set = _readbunchobj(trainpath)
    
    # 导入测试集
    testpath = "test_word_bag/testspace.dat"
    test_set = _readbunchobj(testpath)
    
    # 训练分类器:输入词袋向量和分类标签,alpha:0.001 alpha越小,迭代次数越多,精度越高
    clf = MultinomialNB(alpha=0.001).fit(train_set.tdm, train_set.label)
    
    # 预测分类结果
    predicted = clf.predict(test_set.tdm)
    
    for flabel,file_name,expct_cate in zip(test_set.label,test_set.filenames,predicted):
        if flabel != expct_cate:
            print file_name,": 实际类别:",flabel," -->预测类别:",expct_cate
    
    print "预测完毕!!!"
    
    # 计算分类精度:
    from sklearn import metrics
    def metrics_result(actual, predict):
        print '精度:{0:.3f}'.format(metrics.precision_score(actual, predict,average='weighted'))
        print '召回:{0:0.3f}'.format(metrics.recall_score(actual, predict,average='weighted'))
        print 'f1-score:{0:.3f}'.format(metrics.f1_score(actual, predict,average='weighted'))
    
    metrics_result(test_set.label, predicted)


    出错的这个,是我故意制造的,(因为实际分类精度100%,不能很好的说明问题)
    效果图:


    请注意,上面的截图中的结果,未必会跟你的一样。我之所以有这么高的准确率,一方面是把每个数据集做了精简的处理。另一方面是调试了TF-IDF的阈值。 

    当然,你也可以采用其他分类器,比如KNN


    6,评价与小结

    评价部分的实际操作我们已经在上一节的代码中给出了。这里主要是要解释一下代码的含义,以及相关的一些概念。

    截止目前,我们已经完成了全部的实践工作。接下来,你或许希望做的是:

    1,分词工具和分词算法的研究

    2,文本分类算法的研究

    这些内容,博主会在今后的时间里,专门研究并写出博文。


    整个工程的完整源代码到这里下载:

    https://github.com/sunxiangguo/chinese_text_classification

    需要说明的是,在工程代码和本篇博文中,细心的你已经发现了,我们所有的路径前面都有一个点“. /”,这主要是因为我们不知道您会将工程建在哪个路径内,因此这个表示的是你所在项目的目录,本篇博文所有路径都是相对路径。github代码有两个分支分别是master和python2.7分支。


    三,进一步的讨论

    我们的这些工作究竟实不实用?这是很多人关心的问题。事实上,本博文的做法,是最经典的文本分类思想。也是你进一步深入研究文本分类的基础。在实际工作中,用本文的方法,已经足够胜任各种情况了。

    那么,我们也许想问,有没有更好,更新的技术?答案是有的。未来,博主会集中介绍两种技术:
    1.利用LDA模型进行文本分类
    2.利用深度学习进行文本分类

    利用深度学习进行文本分类,要求你必须对深度学习的理论有足够多的掌握。
    为此,你可以参考博主的其他博文,
    例如下面的这个系列博文《 卷积神经网络CNN理论到实践》。
    这是一些列的博文。与网上其他介绍CNN的博文不同的是:
    1. 我们会全方位,足够深入的为你讲解CNN的知识。包括很多,你之前在网上找了很多资料也没搞清楚的东西。
    2. 我们会利用CNN做文本分类的实践。
    3. 我们会绘制大量精美的示意图。保证博文的高质量和美观。

    welcome!

    Xiangguo Sun 
    sunxiangguodut@qq.com 
    http://blog.csdn.net/github_36326955 

    Welcome to my blog column: Dive into ML/DL!

    (click here to blog column Dive into ML/DL)

    这里写图片描述

    I devote myself to dive into typical algorithms on machine learning and deep learning, especially the application in the area of  computational personality .

    My research interests include computational personality, user portrait, online social network, computational society, and ML/DL. In fact you can find the internal connection between these concepts: 

    这里写图片描述

    In this blog column, I will introduce some typical algorithms about machine learning and deep learning used in OSNs(Online Social Networks), which means we will include NLP, networks community, information diffusion,and individual recommendation systemApparently, our ultimate target is to dive into user portrait , especially the issues on your personality analysis.


    All essays are created by myself, and copyright will be reserved by me. You can use them for non-commercical intention and if you are so kind to donate me, you can scan the QR code below. All donation will be used to the library of charity for children in Lhasa.


    赏金将用于拉萨儿童图书公益募捐
    社会公益,听IT人的声音

    手机扫一扫,即可: 

    附:《春天里,我们的拉萨儿童图书馆,需要大家的帮助


    click here to blog column Dive into ML/DL

    展开全文
  • CNN文本分类

    千次阅读 2018-09-20 08:33:46
    文本分类是NLP领域的一个重要的子任务,文本分类的目标是自动的将文本打上已经定义好的标签,常见的文本分类任务有:垃圾邮件过滤、情感分析、新闻分类等等。 代码是来自 ... 大家可以自行下载阅读,下面仅仅是自己对...

    文本分类是NLP领域的一个重要的子任务,文本分类的目标是自动的将文本打上已经定义好的标签,常见的文本分类任务有:垃圾邮件过滤、情感分析、新闻分类等等。

    代码是来自

    https://github.com/gaussic/text-classification-cnn-rnn

    大家可以自行下载阅读,下面仅仅是自己对代码的一个解读,仅此而已,若有不合适的地方,希望大家多多指出,共同交流

    1、任务

    对给定的一个文本进行盗窃类型的判断

    2、类型种类

    入户盗窃、扒窃、一般盗窃

    代码的具体阅读如下所示:

    1、文件的位置

    base_dir = 'data/cnews'
    
    train_dir = os.path.join(base_dir, 'cnews.train.txt')
    
    test_dir = os.path.join(base_dir, 'cnews.test.txt')
    
    val_dir = os.path.join(base_dir, 'cnews.val.txt')
    
    vocab_dir = os.path.join(base_dir, 'cnews.vocab.txt')
    
    
    
    print ('train_dir',train_dir)
    
    print ('test_dir',test_dir)
    
    print ('val_dir',val_dir)
    
    print ('vocab_dir',vocab_dir)

    打印出的结果如下所示:

    os.path.join()函数的作用是将各个路径进行拼接操作

    2、主函数

    if __name__ == '__main__':
        if len(sys.argv) != 2 or sys.argv[1] not in ['train', 'test']:
            raise ValueError("""usage: python run_cnn.py [train / test]""")
        categories = ['入户盗窃', '扒窃', '一般盗窃']
    
        if not os.path.exists(vocab_dir):  # 如果不存在词汇表,重建
            build_vocab(train_dir, vocab_dir, config.vocab_size)
    
        categories, cat_to_id = read_category(categories)  ##将文本的标签进行id转换
        words, word_to_id = read_vocab(vocab_dir) ##字、字对应的id号
    
    #该处是使用提前训练好的字向量,但此处注释掉了,此次不进行讲解
    #embedding = embed(word_to_id)  
    print('Configuring CNN model...')
        config = TCNNConfig(embedding)
        config.vocab_size = len(words)
    print('vocab_size',config.vocab_size) ##2426

    程序运行时的命令行:

    python run_cnn.py train

    (1)build_vocab()函数:

    该函数的主要功能是建立字典

    def build_vocab(train_dir, vocab_dir, vocab_size=5000):
        """根据训练集构建词汇表,存储"""
        data_train, _ = read_file(train_dir)  ##data_train存储的是一个一个的文本
        print(data_train)
    
        all_data = []
        for content in data_train:
            all_data.extend(content)
    
        counter = Counter(all_data)
        count_pairs = counter.most_common(vocab_size - 1)
        words, _ = list(zip(*count_pairs))
        # 添加一个 <PAD> 来将所有文本pad为同一长度
        words = ['<PAD>'] + list(words)
    open_file(vocab_dir, mode='w').write('\n'.join(words) + '\n')

    该函数主要是构建词汇表,使用字符级的表示,这一函数会将词汇表存储下来,避免每一次重复处理

    def read_file(filename):
        """读取文件数据"""
        contents, labels = [], []
        with open_file(filename) as f:
            for line in f:
                try:
                    label, content = line.strip().split('\t')
                    if content:
                        contents.append(list(native_content(content)))
                        labels.append(native_content(label))
                except:
                    pass
        return contents, labels

    (2)open_file()函数:

    def open_file(filename, mode='r'):
        """
        常用文件操作,可在python2和python3间切换.
        mode: 'r' or 'w' for read or write
        """
        if is_py3:
            return open(filename, mode, encoding='utf-8', errors='ignore')
        else:
            return open(filename, mode)
    

    例子:

    文本中存储的内容如下所示:

    入户盗窃 被告人周×于2014年1月26日,趁无人之机,用事先偷取的钥匙进入被害人李×租住地,窃取“科龙牌”KFR-26W/VGFDBP-3型壁挂式空调1台、“美的牌”F40-15A4型热水机1台及椅子6把、茶几1张。

    data_train, _ = read_file()之后:

    data_train = ['二、被告人周×于2014年1月26日,趁无人之机,用事先偷取的钥匙进入被害人李×租住地,窃取“科龙牌”KFR-26W/VGFDBP-3型壁挂式空调1台、“美的牌”F40-15A4型热水机1台及椅子6把、茶几1张。']

    _存储的是labels:['入户盗窃']

    执行完build_vocab()函数之后新生成的字典文件的内容如下所示:

    <PAD>

    1

    2

    4

    6

    ……

    (3)read_category()函数

    def read_category(categories):
        """读取分类目录,固定"""
        categories = ['入户盗窃', '扒窃', '一般盗窃']
        categories = [native_content(x) for x in categories]
    
        cat_to_id = dict(zip(categories, range(len(categories)))) #将类别分别进行id的转换
    
    return categories, cat_to_id

    该函数的目的主要是将类别进行id的转换,即使用数字对分类的类别进行表示。将分类目录固定,转换为{类别:id}表示

    (4)read_vocab()函数

    def read_vocab(vocab_dir):
        """读取词汇表"""
        with open_file(vocab_dir) as fp:
            # 如果是py2 则每个值都转化为unicode
            words = [native_content(_.strip()) for _ in fp.readlines()]
        word_to_id = dict(zip(words, range(len(words))))
        return words, word_to_id

    该函数是从已经建立好的字典文件中读取每一个字,同时进行id的转换

    3、模型参数的设置

     模型参数设置的调用:

    config = TCNNConfig()

    模型参数设置的类:

    class TCNNConfig(object): ##神经网络相关参数的设定
        """CNN配置参数"""
        def __init__(self,embedding):
            self.embedding = embedding
        embedding_dim = 100  # 词向量维度
        seq_length = 600  # 序列长度  即文本设置的长度
        num_classes = 3   # 类别数 3类
        num_filters = 256  # 卷积核数目
        kernel_size = 5  # 卷积核尺寸
        vocab_size = 5000  # 词汇表达小
    
        hidden_dim = 128  # 全连接层神经元
    
        dropout_keep_prob = 0.5  # dropout保留比例
        learning_rate = 1e-3  # 学习率
    
        batch_size = 64  # 每批训练大小
        num_epochs = 10  # 总迭代轮次
    
        print_per_batch = 100  # 每多少轮输出一次结果
        save_per_batch = 10  # 每多少轮存入tensorboard
    
        ##提前训练好的词向量可以放在这个里面,进行后续得网络的输入
    # embedding = tf.get_variable('embedding', [vocab_size,embedding_dim]) 

    4、网络层的理解

    (1)模型的调用:

    model = TextCNN(config)

    (2)模型的定义

    class TextCNN(object):
        """文本分类,CNN模型"""
        def __init__(self, config):
            self.config = config
    
            # 三个待输入的数据,首先定义我们传递给我们网络的输入数据
    ##seq_length :文本的长度
    ##num_classes 标签的数量
            self.input_x = tf.placeholder(tf.int32, [None, self.config.seq_length], name='input_x') 
            self.input_y = tf.placeholder(tf.float32, [None, self.config.num_classes], name='input_y')  		self.keep_prob = tf.placeholder(tf.float32, name='keep_prob')
    
            self.cnn()
    
        def cnn(self):
            """CNN模型"""
            # 词向量映射
            with tf.device('/cpu:0'):
                #下面是输入的向量时随机进行初始化的
                embedding = tf.get_variable('embedding', [self.config.vocab_size, self.config.embedding_dim]) ##词向量的初始值为随机值
                # print('embedding',embedding) ##embedding <tf.Variable 'embedding:0' shape=(2426, 100) dtype=float32_ref>
                embedding_inputs = tf.nn.embedding_lookup(embedding, self.input_x)
                #
                # ##下面是使用训练好的字向量进行训练
                # embed = tf.Variable(initial_value=self.config.embedding, dtype=tf.float32)
                # print('self.config.embedding', embed)
                # embedding_inputs = tf.nn.embedding_lookup(embed, self.input_x)
    
            # print('self.input_x',self.input_x)  ##Tensor("input_x:0", shape=(?, 600), dtype=int32)
            with tf.name_scope("cnn"):
                # CNN layer
                conv = tf.layers.conv1d(embedding_inputs, self.config.num_filters, self.config.kernel_size, name='conv') ##256,5
                # print('embedding_inputs',embedding_inputs) ##Tensor("embedding_lookup:0", shape=(?, 600, 100), dtype=float32, device=/device:CPU:0)
                # print('conv',conv)  ##Tensor("cnn/conv/BiasAdd:0", shape=(?, 596, 256), dtype=float32)  596 = 600-5+1
                # global max pooling layer
                gmp = tf.reduce_max(conv, reduction_indices=[1], name='gmp') ##取最大值 是在第二维上进行最大值的取出
                # print('gmp',gmp)##Tensor("cnn/gmp:0", shape=(?, 256), dtype=float32)
    
            with tf.name_scope("score"):
                # 全连接层,后面接dropout以及relu激活
                fc = tf.layers.dense(gmp, self.config.hidden_dim, name='fc1')
                fc = tf.contrib.layers.dropout(fc, self.keep_prob)
                fc = tf.nn.relu(fc)
    
                # 分类器
                self.logits = tf.layers.dense(fc, self.config.num_classes, name='fc2')
                # print('self.logits',self.logits) #self.logits Tensor("score/fc2/BiasAdd:0", shape=(?, 3), dtype=float32)
                self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits), 1)  # 预测类别
    
            with tf.name_scope("optimize"):
                # 损失函数,交叉熵
                cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=self.logits, labels=self.input_y) ##返回的是一个向量
                # print('cross_entropy',cross_entropy) #cross_entropy Tensor("optimize/Reshape_2:0", shape=(?,), dtype=float32)
                self.loss = tf.reduce_mean(cross_entropy) ##对向量求均值,计算loss 损失函数
                '''
                ,logits是作为softmax的输入。经过softmax的加工,就变成“归一化”的概率(设为q),然后和labels代表的概率分布(设为q),于是,整个函数的功能就是前面的计算labels(概率分布p)和logits(概率分布q)之间的交叉熵
                '''
                # 优化器
                self.optim = tf.train.AdamOptimizer(learning_rate=self.config.learning_rate).minimize(self.loss)
    
            with tf.name_scope("accuracy"):
                # 准确率
                correct_pred = tf.equal(tf.argmax(self.input_y, 1), self.y_pred_cls) ##bool型
                self.acc = tf.reduce_mean(tf.cast(correct_pred, tf.float32))  ##数据类型的转换,同时进行计算准确率
    

    代码的理解:

    tf.placeholder创建一个占位符变量,第一个维度表示的是批量大小,使用None表示,第二个参数是输入张量的形状

    tf.nn.softmax_cross_entropy_with_logits是一个损失的函数,计算每个类的交叉熵损失,给定我们的分数和正确的输入标签,对损失进行计算平均值

    损失是我们训练模型好坏的衡量标准,目的是降低损失,减少网络的误差,分类问题的标准损失函数是交叉熵损失

     

    CNN网络:

    代码中的卷积神经网络时最基本的网络,有input layer、convolutional layer、max-pooling layer以及最后的输出的softmax layer

    (1)input layer层

    使用随机初始化的embedding作为输入层输入的信息

    embedding = tf.get_variable('embedding', [self.config.vocab_size, self.config.embedding_dim]) ##词向量的初始值为随机值

    embedding_inputs = tf.nn.embedding_lookup(embedding, self.input_x)

    此时的输入的shape为:shape=(?, 600, 100)

    (2)convolutional layer层

    conv = tf.layers.conv1d(embedding_inputs, self.config.num_filters, self.config.kernel_size, name='conv')

    参数的大小:

    embedding_inputs:shape=(?, 600, 100)

    self.config.num_filters:256  卷积核数目  卷积出的最后一个维度  256个卷积核

    self.config.kernel_size:5  卷积核尺寸 一维卷积窗口大大小

     

    输出的维度大小:

    shape=(?, 596, 256)

    其中596 = 600-5+1

    256个卷积核的初始化的大小是随机的,所以对句子进行256个卷积结果是不同的,即使初始化卷积核的大小都是一样的,但是根据标签进行调参时,也会将卷积核矩阵中的内容进行训练修改,此时的卷积核矩阵中的数据相当于传统神经网络的权重参数W

    对句子中的每一个字的向量的每一维度都进行参数的学习

     

    tf.layers.conv1d()功能:

    生成卷积核,对输入层进行卷积,产生输出的tensor

    (3)max-pooling layer层

    gmp = tf.reduce_max(conv, reduction_indices=[1], name='gmp') ##取最大值 是在第二维上进行最大值的取出

    print('gmp',gmp)##Tensor("cnn/gmp:0", shape=(?, 256), dtype=float32)

     

    (4)softmax layer层

    # 全连接层,后面接dropout以及relu激活

    fc = tf.layers.dense(gmp, self.config.hidden_dim, name='fc1')

    fc = tf.contrib.layers.dropout(fc, self.keep_prob)

    fc = tf.nn.relu(fc)

    # 分类器

    self.logits = tf.layers.dense(fc, self.config.num_classes, name='fc2')

    print('self.logits',self.logits) #self.logits Tensor("score/fc2/BiasAdd:0", shape=(?, 3), dtype=float32)

    self.y_pred_cls = tf.argmax(tf.nn.softmax(self.logits), 1)  # 预测类别

    在自然语言处理中,我们假设一个序列是600个单词,每个单词的词向量是300维,那么一个序列输入到网络中就是(600,300),当我使用Conv1D进行卷积的时候,实际上就完成了直接在序列上的卷积,卷积的时候实际是以(3,300)进行卷积,又因为每一行都是一个词向量,因此使用Conv1D(kernel_size=3)也就相当于使用神经网络进行了n_gram=3的特征提取了。这也是为什么使用卷积神经网络处理文本会非常快速有效的内涵。

    5、模型的训练

    if sys.argv[1] == 'train':

        train()

    else:

        test()

    具体的train()函数代码如下所示:

    def train():
        print("Configuring TensorBoard and Saver...")
        # 配置 Tensorboard,重新训练时,请将tensorboard文件夹删除,不然图会覆盖
        tensorboard_dir = 'tensorboard/textcnn'
        if not os.path.exists(tensorboard_dir):
            os.makedirs(tensorboard_dir)
    
        tf.summary.scalar("loss", model.loss)
        tf.summary.scalar("accuracy", model.acc)
        merged_summary = tf.summary.merge_all()
        writer = tf.summary.FileWriter(tensorboard_dir)
    
        # 配置 Saver
        saver = tf.train.Saver()
        if not os.path.exists(save_dir):
            os.makedirs(save_dir)
    
        print("Loading training and validation data...")
        # 载入训练集与验证集
        start_time = time.time()
        x_train, y_train = process_file(train_dir, word_to_id, cat_to_id, config.seq_length)
        x_val, y_val = process_file(val_dir, word_to_id, cat_to_id, config.seq_length)
        time_dif = get_time_dif(start_time)
        print("Time usage:", time_dif)
    
        # 创建session
        session = tf.Session()
        session.run(tf.global_variables_initializer())
        writer.add_graph(session.graph)
    
        print('Training and evaluating...')
        start_time = time.time()
        total_batch = 0  # 总批次
        best_acc_val = 0.0  # 最佳验证集准确率
        last_improved = 0  # 记录上一次提升批次
        require_improvement = 1000  # 如果超过1000轮未提升,提前结束训练
    
        flag = False
        for epoch in range(config.num_epochs):
            print('Epoch:', epoch + 1)
            batch_train = batch_iter(x_train, y_train, config.batch_size)
            for x_batch, y_batch in batch_train:
                feed_dict = feed_data(x_batch, y_batch, config.dropout_keep_prob)
    
                if total_batch % config.save_per_batch == 0:
                    # 每多少轮次将训练结果写入tensorboard scalar
                    s = session.run(merged_summary, feed_dict=feed_dict)
                    writer.add_summary(s, total_batch)
    
                if total_batch % config.print_per_batch == 0:
                    # 每多少轮次输出在训练集和验证集上的性能
                    feed_dict[model.keep_prob] = 1.0
                    loss_train, acc_train = session.run([model.loss, model.acc], feed_dict=feed_dict)
                    loss_val, acc_val = evaluate(session, x_val, y_val)  # todo
    
                    if acc_val > best_acc_val:
                        # 保存最好结果
                        best_acc_val = acc_val
                        last_improved = total_batch
                        saver.save(sess=session, save_path=save_path)
                        improved_str = '*'
                    else:
                        improved_str = ''
    
                    time_dif = get_time_dif(start_time)
                    msg = 'Iter: {0:>6}, Train Loss: {1:>6.2}, Train Acc: {2:>7.2%},' \
                          + ' Val Loss: {3:>6.2}, Val Acc: {4:>7.2%}, Time: {5} {6}'
                    print(msg.format(total_batch, loss_train, acc_train, loss_val, acc_val, time_dif, improved_str))
    
                session.run(model.optim, feed_dict=feed_dict)  # 运行优化
                total_batch += 1
    
                if total_batch - last_improved > require_improvement:
                    # 验证集正确率长期不提升,提前结束训练
                    print("No optimization for a long time, auto-stopping...")
                    flag = True
                    break  # 跳出循环
            if flag:  # 同上
                break
    

    对train()函数进行分析:

    (1)process_file()函数

    def process_file(filename, word_to_id, cat_to_id, max_length=600):
        """将文件转换为id表示"""
        contents, labels = read_file(filename)
        '''
        一个句子放在一个列表中,且字与字之间是分开放的,将所有的句子放在一个列表contents中
        '''
        data_id, label_id = [], []
        for i in range(len(contents)):
            data_id.append([word_to_id[x] for x in contents[i] if x in word_to_id])
            label_id.append(cat_to_id[labels[i]])
    
        # 使用keras提供的pad_sequences来将文本pad为固定长度
        x_pad = kr.preprocessing.sequence.pad_sequences(data_id, max_length)
        y_pad = kr.utils.to_categorical(label_id, num_classes=len(cat_to_id))  # 将标签转换为one-hot表示
    
        return x_pad, y_pad

    该函数主要实现了将训练语料、开发集语料、测试集语料中的每一句话中的每一个字进行id的转换,同时做padding,将所有的句子长度进行统一长度

    将数据集从文字转换为固定长度的id序列表示

    对于CNN,输入与输出都是固定的,当每一个句子长短不一是,需要做定长处理,超过的截断,不足的补0,注意补充的0对后面的结果没有影响,因为后面的max-pooling只会输出最大值,补0的项会被过滤掉

    (2)batch_iter()函数

    def batch_iter(x, y, batch_size=64):
        """生成批次数据"""
        data_len = len(x)
        ##可以将语料分成多少个batch_size
        num_batch = int((data_len - 1) / batch_size) + 1
    
        ##做shuffle 即进行对语料的前后顺序进行打乱 在整个语料上进行打乱操作
        indices = np.random.permutation(np.arange(data_len)) 
        x_shuffle = x[indices]
        y_shuffle = y[indices]
    
        for i in range(num_batch):
            start_id = i * batch_size
            ##只有在最后一个batch上,才有可能取data_len,前面的都是取前者
            end_id = min((i + 1) * batch_size, data_len) 
            yield x_shuffle[start_id:end_id], y_shuffle[start_id:end_id]

    该函数主要是为神经网络的训练准备经过shuffle的批次的数据

    网络的大致结构如下所示:

     

    CNN网络的理解:

    1、输入层

    输入层输入的是句子中的字对应的向量,假设句子有n个字,向量的维度为k,则输入的是一个nxk的矩阵(在CNN中可以看作一副高度为n、宽度为k的图像),在本文中输入的矩阵大小为:600x100,一个句子的最大长度设置为600,每一个字的字向量长度设置为100维

    对于未登录词,映射到PAD上

    输入的矩阵可以是静态的,也可以是动态的。静态指的是字向量是固定不变的,而动态则是在模型训练过程中,字向量也被当做是可以进行优化的参数,通常把反向误差传播导致字向量中值发生变化的这一过程称为Finetune。若字向量是随机初始化的,不仅训练得到了CNN分类模型,还得到了字向量这个副产品,如果已经有训练的字向量,那么其实是一个迁移学习的过程

    网址:https://blog.csdn.net/zbc1090549839/article/details/53055386

    Embedding layer:通过一个隐藏层,将one-hot编码的词投影到一个低维空间中,本质上是特征提取器,在指定维度中编码语义特征,这样,语义相近的词,它们的欧氏距离或余弦距离也比较近

    运行python run_cnn.py train,显示如下所示:

    运行python run_cnn.py test,结果如下所示:

     

     

    文中有不对的地方,欢迎指出,共同交流

     

     

    展开全文
  • fastText原理和文本分类实战,看这一篇就够了

    万次阅读 多人点赞 2019-03-19 11:19:48
    fastText是一个快速文本分类算法,与基于神经网络的分类算法相比有两大优点: 1、fastText在保持高精度的情况下加快了训练速度和测试速度 2、fastText不需要预训练好的词向量,fastText会自己训练词向量 3、fastText...
  • NLP之文本分类

    万次阅读 2018-09-26 15:08:07
    文本自动分类简称文本分类(text categorization),是模式识别与自然语言处理密切结合的研究课题。传统的文本分类是基于文本内容的,研究如何将文本自动划分成政治的、经济的、军事的、体育的、娱乐的等各种类型。 ...
  • 文本分类入门

    千次阅读 热门讨论 2012-03-04 02:08:57
    最近要做文本分类相关的课程project,因此上网找了一下文本分类的资料,下面这个感觉比较通俗易懂,收录在这里。 来源 http://www.blogjava.net/zhenandaci/category/31868.html?Show=All 文本分类入 门(一)文本...
  • 大话文本分类

    千次阅读 2018-02-14 09:49:26
    常见的文本分类应用有:新闻文本分类、信息检索、情感分析、意图判断等。本文主要针对文本分类的方法进行简单总结。传统机器学习方法分类问题一般的步骤可以分为特征提取、模型构建、算法寻优、交叉验证等。对于文本...
  •   在2017年9到12月份参加了kaggle平台上的一个文本分类比赛:Spooky author identification,这个比赛会给出三个恐怖小说作家作品里的一些英文句子。参赛者所要做的是用训练数据训练出合适的模型,让模型在测试...
  • python中文文本分类

    千次阅读 2019-01-07 09:27:31
    一,中文文本分类流程: 预处理 中文分词 结构化表示-构建词向量空间 权重策略—TF-IDF 分类器 评价. 二,具体实现 1. 预处理 1.1 打标签: 对评论数据打好标签,这里将汽车...
  • 文本分类与SVM

    千次阅读 2016-06-08 15:20:19
    1、基础知识 1. 1 样本整理 文本分类属于有监督的学习,所以需要整理样本。根据业务需求,确定样本标签...如下面的整理的样本,1为正,-1为反(为了能便于展示,这里使用了一些即时聊天工具中的文本,里面的一些
  • XGBoost文本分类实战

    千次阅读 2019-07-16 08:56:25
    一、将收集到的语料进行文本预处理 1)train.txt预处理为train.csv,格式为id,内容,标签 使用excel打开train.txt然后选择分隔符为英文逗号,这样内容在一列,然后再为他们添加id,从1-900,接着添加标签,0,1,...
  • 深度学习在文本分类中的应用

    万次阅读 2016-05-17 20:16:02
    引言文本分类这个在NLP领域是一个很普通而应用很广的课题,而且已经...在传统的文本分类词袋模型中,在将文本转换成文本向量的过程中,往往会造成文本向量维度过大的问题,当然也有其他的压缩了维度的一些分类方法。然
  • 文本分类综述

    千次阅读 2012-12-19 23:02:36
    之前一段时间弄过文本分类的事情,现在发个文总结一下。 文本分类问题的定义是根据一篇文档的内容,从预定义的类别标号里选择相应的类别。 中文文本分类的基本步骤是中文分词、特征提取、训练模型、预测类别等步骤,...
  • 文本分类(TC, Text Categorization):在给定的分类体系下,根据文本内容自动的确定文本关联的类别。从数学角度看,文本分类是一个映射的过程,它将未标明类别的文本映射到已有的类别中,该映射可以是一对一或一对多...
  • 但在文本分类中单纯使用TF-IDF来判断一个特征是否有区分度是不够的。 1)它没有考虑特征词在间的分布。也就是说该选择的特征应该在某出现多,而其它出现少,即考察各类的文档频率的差异。如果一个特征词,在...
  • 文本分类中的文本特征表示

    万次阅读 2018-01-11 16:49:45
    目前,针对文本话题分类的研究还是很热的,主要包括微博,知乎等大型话题社区,论坛网站。之前知乎针对该问题在著名的机器学习比赛网上,还开展了比赛,有关技术和code有很多。文本话题分析主要是应用是对文本进行...
  • 文本分类和聚类有什么区别

    千次阅读 2011-12-15 21:01:02
    聚类就是将一组的文章或文本信息进行相似性的比较,将比较相似的文章或文本信息归为同一组的技术。分类和聚类都是将相似对象归类的过程。区别是,分类是事先定义好类别,类别数不变。分类器需要由人工标注的分类训练...
  • 自然语言处理——文本分类概述

    万次阅读 2018-11-05 19:50:59
    内容提要分类概述分类流程数据采集爬虫技术页面处理文本预处理英文处理中文处理停用词去除文本表示特征选择 分类概述   分类(Classification)是自动对数据进行标注。人们在日常生活中通过经验划分类别。但是要...
  • 基于SVM的中文文本分类方法

    千次阅读 2017-06-15 16:50:21
    基于SVM的中文文本分类方法 1、文本分类简介 文本分类(Text Classification)是将文本文档与规定好的类别进行匹配的过程。文本分类可以分为训练和分类两个阶段,其对应的流程图如下面的图1.1和图1.2所示: 图...
  • 2.1 文本挖掘与文本分类的概念

    千次阅读 2017-05-22 11:04:07
    文本挖掘是从大量文本数据中抽取事先未知的、可理解的、最终可用的知识的过程,同时运用这些知识更好地组织信息以便将来参考。简言之,文本挖掘就是从非结构化的文本中寻找知识的过程。 文本挖掘的7个主要...
  • 中文文本分类

    千次阅读 2012-01-29 15:45:07
    哈工大社会计算与信息检索研究中心 中文文本分类介绍 概况介绍 中文文本自动分类是自然语言处理的经典研究方向,有着极其重要的应用价值。文本分类的核心技术为构建一个具有高准确度和较高速度的分类器,高...
  • 2.2 文本分类项目

    千次阅读 2017-05-22 11:05:17
    预处理:去除文本的噪声信息,例如HTML标签、文本格式转换、检测句子边界等中文分词:使用中文分词器为文本分词,并去除停用词构建词向量空间:统计文本词频,生成文本的词向量空间权重策略——TF-IDF方法:使用TF-...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 259,907
精华内容 103,962
热门标签
关键字:

信息类文本指什么