精华内容
下载资源
问答
  • 信息类文本的功能
    万次阅读 多人点赞
    2018-11-05 19:50:59

    分类概述

      分类(Classification)是指自动对数据进行标注。人们在日常生活中通过经验划分类别。但是要依据一些规则手工地对互联网上的每一个页面进行分类,是不可能的。因此,基于计算机的高效自动分类技术成为人们解决互联网应用难题的迫切需求。与分类技术类似的是聚类,聚类不是将数据匹配到预先定义的标签集合,而是通过与其他数据相关的隐含结构自动的聚集为一个或多个类别。文本分类是数据挖掘和机器学习领域的一个重要研究方向。
      分类是信息检索领域多年来一直研究的课题,一方面以搜索的应用为目的来提高有效性和某些情况下的效率;另一方面,分类也是经典的机器学习技术。在机器学习领域,分类是在有标注的预定义类别体系下进行,因此属于有监督的学习问题;相反聚类则是一种无监督的学习问题。
      文本分类(Text Classification或Text Categorization,TC),或者称为自动文本分类(Automatic Text Categorization),是指计算机将载有信息的一篇文本映射到预先给定的某一类别或某几类别主题的过程。文本分类另外也属于自然语言处理领域。本文中文本(Text)和文档(Document)不加区分,具有相同的意义。
      F. Sebastiani以如下数学模型描述文本分类任务:文本分类的任务可以理解为获得这样的一个函数Φ:D×C→{T,F},其中,D={d1,d2,…,d|D|} 表示需要进行分类的文档,C={c1,c2,…,c|C|} 表示预定义的分类体系下的类别集合,T值表示对于(dj,ci)来说 ,文档dj属于类ci,而F值表示对于(dj,ci)而言文档dj不属于类ci 。也就是说,文本分类的目标就是要寻找一个有价值的函数映射,准确的完成D×C到T/F值的函数映射,这个映射过程本质上讲就是所谓的分类器。
      文本分类的形式化定义如下:
      设i = 1,…,M为文档集合里面的M篇文档, j = 1,…,N为预先定义的N个类别主题,可以给出这样一个分类结果矩阵C=(cij);其中,矩阵中某一元素cij表示第i篇文档与第j个类别的关系。也就是说,文本自动分类可以归结为确定上面矩阵C的每一个元素的值的过程;使用一个布尔量1或0,如果cij 的值为1,则表示文档i属于第j类,如果值为0,则文档i不能被分入类别j,即:

    对于单类别的分类,即某篇文档只允许被分入一个类别中,我们可以增加限定条件,对于第j行( j = 1,…,N)的所有元素,必须满足:

      在实际应用中,根据预定义的类别不同,分类系统可以分两种:两类分类器和多类分类器。如果从文本的标注类别上来讲,文本分类又可以分为单标签和多标签两类。文本分类系统的任务简单的说:在预定义分类体系下,根据文本的内容相关性自动地判定文本与类别之间的关联。从数学角度来看,文本分类是一个函数映射过程,它将未标明类别的文本映射到预定义的类别,该映射可以是一一映射,也可以是一对多的映射,因为通常一篇文本可以同时关联到多个类别。

    分类流程

      文本分类系统可以简单的表示为如图2.1所示:

    文本分类系统示意图
      统计机器学习(Statistical Machine Learning)文本分类系统组成部分包括:
    1. 文本预处理模型(Text Preprocessing Model),
    2. 文本表示模型(Text Expressing Model),
    3. 特征选择模型( Feature Selection Model),
    4. 学习训练模型(Learning and Selection Model),
    5. 分类处理模型(Classification Processing Model),
    6. 性能评估模型(Performance Evaluation Model)。

      多数情况下的监督型机器学习算法,学习训练模型仅需在分类预测前运行一次即可;性能评估模型主要起到评估训练模型学习效果,衡量分类精度的作用。
      文本分类的一般流程如图所示:

    文本分类的一般流程

    数据采集

      数据采集是文本挖掘的基础,主要包括爬虫技术和页面处理。

    爬虫技术

      Web信息检索的第一步就是要抓取网络文档,爬虫就承担了解决这一问题的主要责任。爬虫有很多种类型,但最典型的就是网络爬虫。网络爬虫通过跟踪网页上的超链接来搜寻并下载新的页面。似乎听起来该过程比较简单,但是如何能够高效处理Web上出现的大量新页面,如何处理已抓取页面的更新页面,如何保持页面的最新性,这些问题都成为网络爬虫设计富有挑战的难题。网络爬虫抓取任务可以限定在一个比较小的范围内,例如一个公司,一个网站,或者一所大学的站点。主题网络爬虫与话题网络爬虫要采用分类技术来限制所搜寻页面属于同一主题类别。

    页面处理

      通过网络爬虫抓取的页面是最原始的Web页面,它们的格式多种多样,如HTML、XML、Adobe PDF、Microsoft Word等等。这些web页面含有大量的噪声数据,包括导航栏、广告信息、Web标签、超链接或者其他非内容格式数据等,这些数据几乎都成是阻碍文本下一步处理的因素。因此需要经过预处理去除上述噪音数据,将Web页面转化成为纯净统一的文本格式和元数据格式。Web关注内容过滤也是Web信息处理领域的一项热门研究课题。
      另外一个普遍存在的页面处理问题是编码不一致。由于计算机发展、民族语言、国家地域的不同造成现在计算机存储数据采用许多种编码格式,例如ASCII、UTF-8、GBK、BIG5等等。在实际应用过程中,在对不同编码格式的文档进行深入处理之前,必须要保证对它们编码格式进行统一转换。

    文本预处理

      文本要转化成计算机可以处理的数据结构,就需要将文本切分成构成文本的语义单元。这些语义单元可以使句子、短语、词语或单个的字。本文无论对于中文还是英文文本,统一将最小语义单元称为“词组”。对于不同的语言来说处理有所区别,下面简述最常见的中文和英文文本的处理方式。

    英文处理

      英文文本的处理相对简单,每一个单词之间有空格或标点符号隔开。如果不考虑短语,仅以单词作为唯一的语义单元的话,处理英文单词切分相对简单,只需要分割单词,去除标点符号。英文还需要考虑的一个问题是大小写转换,一般认为大小写不具有不同的意义,这就要求将所有单词的字幕都转换成小写或大写。另外,英文文本预处理更为重要的问题是词根的还原,或称词干提取。词根还原的任务就是将属于同一个词干(Stem)的派生词进行归类转化为统一形式。例如,把“computed”, “computer”, “computing”可以转化为“compute”。通过使用一个给定的词来代替一类中每一个元素,可以进一步增加类别与文档中的词之间匹配度。词根还原可以针对所有词进行,也可以针对少部分词进行或者不采用词根还原。针对所有词的词根还原可能导致分类结果的下降,这主要的原因或许是删除了不同形式单词所含有的形式意义。哪些词在哪些应用中应该词根还原尚不清楚。McCallum等人研究工作显示,词根还原可能有损于分类性能。

    中文处理

      相对于英文来说,中文的文本处理相对复杂。中文的字与字之间没有间隔,并且单个汉字具有的意义弱于词组。一般认为中文词语为最小的语义单元,当然词语可以由一个或多个汉字组成。中文文本分类首先要解决的难题就是中文分词技术,用特殊符号(例如空格符)将中文具有独立语义信息的语义单元分割开。中文分词是中文文本分类的前提条件。目前常用的中文分词算法可分为三大类:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。详细介绍见中文分词篇。

    去停用词

      停用词(Stop Word)是一类普遍纯在与文本中的常用词,并且脱离语境它们本身并不具有明显的意义。最常用的词是一些典型的功能词,这些词构成句子的结构,但对于描述文本所表述的意义几乎没有作用,并且容易造成统计偏差,影响机器学习效果。在英文中这词如:“the”、“of”、“for”、“with”、“to”等,在中文中如:“啊”、“了”、“并且”、“因此”等。由于这些词的用处太普遍,去除这些词,对于文本分类来说没有什么不利影响,相反可能改善机器学习效果。停用词去除组件的任务比较简单,只需从停用词表中剔除定义为停用词的常用词就可以了。尽管停用词去除简单,含有潜在优势,但是停用词去除与词根还原具有同样的问题,定义停用词表中应该包含哪些词确是比较困难,一般科研中停用词表的规模为几百。

    文本表示

      文本通常表现为一个由文字或字符与标点符号组成的符号串,由字或字符组成词,由词组成短语,进而形成句子、段落、篇章结构。要使计算机能够有效地处理文本串,就必需找到一种理想的文本表示方法,这种表示形式要既能够真实的反应文本的内容又能区分不同文档。
      向量空间模型(VSM,Vector Space Model)是目前文本处理应用领域使用最多且效果较好的文本表示法。VSM是20 世纪60年代末期由G. Salton等人提出的,最早用在SMART信息检索系统中。
      定义:向量空间模型:给定文档D,如下

    其中 tk 是组成文本的词元素, 1≤k≤nwk 是词元素tk 的权重,或者说词在文本中的某种重要程度。
      性质:向量空间模型满足以下两条性质:
      a) 互异性:各词组元素间属于集合元素关系,即若i≠j ,则 ti≠tj ;
      b) 无关性:各词组元素间无顺序关系和相互联系,即对于文本D,若i≠j ,交换titj 的位置,仍表示文档D。词组与词组之间不含任何关系。
      可以把词组t1, t2,…,tn 看成 n 维坐标空间,而权重w1, w2,…,wn 为相应坐标值。因此,一个文本可以表示为一个 n 维向量。

    特征选择

      在向量空间模型中,文本可以选择字、词组、短语、甚至“概念”等多种元素表示。这些元素用来表征文本的性质,区别文本的属性,因此这些元素可以被称为文本的特征。在文本数据集上一般含有数万甚至数十万个不同的词组,如此庞大的词组构成的向量规模惊人,计算机运算非常困难。进行特征选择,对文本分类具有重要的意义。特征选择就是要选择那些最能表征文本含义的词组元素。特征选择不仅可以降低问题的规模,还有助于分类性能的改善。选取不同的特征对文本分类系统的性能有不同程度的影响。已提出的文本分类特征选择方法比较多,常用的方法有:文档频率(Document Frequency,DF)、信息增益(Information Gain,IG)、 校验(CHI)和互信息(Mutual Information,MI)等方法。另外特征抽取也是一种特征降维技术,特征抽取通过将原始的特征进行变换运算,形成新的特征。详细内容见特征选择篇。

    分类模型

      文本分类模型主要分为:

    1. 规则模型;
    2. 概率模型;
    3. 几何学模型;
    4. 统计模型;

    详细参见:文本分类——常见分类模型

    知更鸟博文推荐
    上一篇 自然语言处理——文本分类平台功能及UI设计
    下一篇 中文分词——知更鸟分词(RS)设计与实现
    推荐篇 基于Kubernetes、Docker的机器学习微服务系统设计——完整版
    研究篇 RS中文分词   |  MP特征选择   |  NLV文本分类   |  快速kNN
    作者简介
    兴趣爱好机器学习、云计算、自然语言处理、文本分类、深度学习
    E-mailxsd-jj@163.com (欢迎交流)

    参考文献:
    [1] 周志华.机器学习. 清华大学出版社,2016.
    [2] 邱江涛,唐常杰,曾涛,刘胤田.关联文本分类的规则修正策略.计算机研究与发展,46(4):683- 688.
    [3] 靖红芳,王斌,杨雅辉,徐燕.基于类别分布的特征选择框架.计算机研究与发展,46(9):1586-1593
    [4] Sebastiani,F. Machine learning in automated text categorization. ACM Comput. Surv. 2002, 34(1): 1-47.
    [5]Lewis,D.,Schapire,R.,Callan,J.,Papka,R. Training algorithms for linear text classifiers. In: Proc. of the ACM SIGIR.
    [6] Joachims,T. Text categorization with support vector machines: Learning with many relevant features In: Proc. of the Machine Learning: ECML’98, 10th European Conf. on Machine Learning.
    [7] Lewis,D. A comparison of two learning algorithms for text categorization . In: Proc. of Symp. On Document Analysis and IR.

    版权声明:个人原创,请勿抄袭,欢迎引用,未经许可禁止转载. © 知更鸟
    更多相关内容
  • 深度学习之文本分类总结

    千次阅读 2022-03-21 22:21:00
    一、文本分类概况 二、文本分类的发展 三、文本分类常用的模型结构 四、文本分类模型实际中遇到的问题(Q&A)

    一、文本分类概况

    文本分类是NLP中的最基础的一个任务,很多场景中都涉及到,比如对话机器人、搜索推荐、情绪识别、内容理解,企业风控,质量检测等方向。在对话机器人中,一般的文本分类任务主要是解决 用户query 的意图,确定相关domain.在确定的 domain中进行 NLU的语义理解,进而下游更多的任务。针对 内容理解 或者风控,质检方向都是针对于用户的query进行理解和意图识别,确定是否是涉黄涉爆,等非法的输入,进而进行控制,由于本人主要是做对话NLU 相关的,具体的其他的场景 不做过多叙述,主要分为以下两种情况:

    多分类(Multi-Class)

    1.二分类, 如邮件垃圾分类,0-1分类,只有两种情况,也可以使用于据识模型中

    2.三分类,情感分类,【正面,中立,负面】,情绪识别

    3.多分类,意图识别,domain领域识别,新闻类别识别,财经、体育、娱乐等

    以上统称为多分类领域,每一个类别是独立的

    多标签分类(Multi-Lable)

    1.多领域类别,比如说,帮我打开导航、并播放一首歌曲、此时刻的domain 属于 musicX音乐 、mapU 导航领域

    2.文本段落,可能即属于金融领域 也属于政治领域

    多标签分类,主要的区别是 每一个类别不是独立的,每一个文本可以有多个标签

    区别

    1.多分类任务中一条数据只有一个标签,但这个标签可能有多种类别。比如判定某个人的性别,只能归类为"男性"、"女性"其中一个。再比如判断一个文本的情感只能归类为"正面"、"中面"或者"负面"其中一个。

    2.多标签分类任务中一条数据可能有多个标签,每个标签可能有两个或者多个类别(一般两个)。例如,一篇新闻可能同时归类为"娱乐"和"运动",也可能只属于"娱乐"或者其它类别。

    二、文本分类常见的方法

    文本分类主要是分为两种,一种是前几年兴起的 传统的机器学习方案,其次是近几年的深度学习模型(https://github.com/649453932/Chinese-Text-Classification-Pytorch)https://github.com/649453932/Chinese-Text-Classification-Pytorch

    传统的机器学习模型(浅层学习模型)

    1.以统计模型占主导,如朴素贝叶斯分类(NB), K近邻(KNN),支持向量机(SVM) 、以及树模型结构 XGBoost和LightGBM

    上文提到的模型,与早期的基于规则的方法相比,该方法在准确性和稳定性方面具有明显的优势。但是,这些方法仍然需要进行功能设计,这既耗时又昂贵。此外,它们通常会忽略文本数据中的自然顺序结构或上下文信息,这使学习单词的语义信息变得困难

    2.浅层学习模型,词袋子模型,tf-idf统计等

    浅层学习模型,主要是忽略掉了文本的语法和语序,用特定的一些符号表示一个文本或者段落。

    词袋子模型(BOW)用一组无序的单词序列来表达一段文字或者一个文档, 把整个文档集的所有出现的词都丢进袋子里面,然后 无序去重 地排出来(去掉重复的)。对每一个文档,按照词语出现的次数来表示文档

    以下例子(来源于网络):

    句子1:我/有/一个/苹果
    
    句子2:我/明天/去/一个/地方
    
    句子3:你/到/一个/地方
    
    句子4:我/有/我/最爱的/你

     统计所有的词语,放到一个袋子中,得到10个单词: “我,有,一个,苹果,明天,去,地方,你,到,最爱的“

    得到4个句子的特征如下:

    • 句子 1 特征: ( 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 )
    • 句子 2 特征: ( 1 , 0 , 1 , 0 , 1 , 1 , 1 , 0 , 0 , 0 )
    • 句子 3 特征: ( 0 , 0 , 1 , 0 , 0 , 0 , 1 , 1 , 1 , 0 )
    • 句子 4 特征: ( 2 , 1 , 0 , 0 , 0 , 0 , 0 , 1 , 0 , 1 )

    通过以上方案,亦能得到句子的浅层语义表达,可以进行基本的下游任务,如相似度计算,文本分类等任务

    缺点就是:语义表达非常稀疏,容易造成维度灾难 只能表示词语本身 ,无法体现单词语义信息,以及单词之间的关系

    TF-IDF模型:

    主要是使用词汇统计的词频特征来 作为句子的语义表征,TF表示的(Term frequency,词频) IDF 表示的是(Inverse document frequency ) 逆文档频率 。

    TF即词频(Term Frequency),每篇文档中关键词的频率(该文档单词/该文档单词总数),TF体现的是词语在文档内部的重要性,举个例子,有两篇文档 d_{1} 和d_{2},

    d1 = (A,B,C,D,A),一共是5个单词,d2 = (B,E,A,B), 一共是4个单词。得到如下的TF计算结果:

     IDF即逆文档频率(Inverse Document Frequency),文档总数/关键词t出现的文档数目,即  IDF= log(\frac{D}{1+D_{i}})          D代表文档的总体数目,分母部分D_{i} 表示的是  包括该词语的文档数目,原始公式是分母没有 +1 的,这里是采用了拉普拉斯平滑,避免了有部分新的词没有在语料库中出现而导致分母为零的情况出现。IDF体现的是 词语在文档中的重要性,如果某个词语在 文档中出现的次数越少,表示的  idf 数值越大,越重要。得到如下的,IDF 数值:

     d1 文档的向量化表示(最终的结果是 tf * idf):

    后面再通过向量的余弦相似度计算,得到语义的相似度

    深度学习模型时代(文本分类模型)

    1.Fasttext 模型

    论文:https://arxiv.org/abs/1607.01759
    代码:https://github.com/facebookresearch/fastText

    fastText是Facebook于2016年开源的一个词向量计算和文本分类工具,包含文本分类和词向量训练两个功能。把输入转化为词向量,取平均,再经过线性分类器得到类别。输入的词向量可以是预先训练好的,也可以随机初始化,跟着分类任务一起训练。最终的embedding 也是分类的产物。

     上图为模型结构图,目前多数人也在使用 fastText模型,主要是由于:

     1.模型本身比较简单,能快速的产生baseLine

     2.采用char-level(字符级别)的 n-gram作为附加特征,这里举个例子,apple 这个单词,bigram 是:[ap,pp,pl,le]  ,trigram结果是[app,ppl,ple],最终的模型输入,是把apple转化为embedding 和  bigram 、trigram也转成embedding , 最终拼接一起 作为输入。

    2.TextCNN 模型

    TextCNN是Yoon Kim小哥在2014年提出的模型,相关论文和仓库如下:

     论文:https://arxiv.org/abs/1408.5882
     代码:https://github.com/yoonkim/CNN_sentence

    TextCNN 使用的是一维的卷积操作,图像中多数使用的是二维卷积.参考代码如下:

    CNN网络结构不清楚的,可以看另一篇Blog,其中有详细的解释CNN网络结构。

    大致的步骤如下

    1.输入query 经过embedding 得到,[batch_size,seq_len,embedding_dim]

    2.设置卷积核的大小为 [filter_size * embedding_dim],filter_size一般为滑动窗口的大小(先不理解没关系),假设一共有N个卷积核,得到N个长度为,seq_len - filter_size + 1 大小的一维feature_map,比如句子长度为10,filter_size的长度为2,最终得到的feature_map的长度为10-2+1= 9

    3. feature_map进行max-pooling 得到N个 1*1的数值,最终为N维向量,作为句子的表达,最终得到 [batch_size,N]的结果,如果 batch_size = 64, N = 256 ,那么最终得到的向量为[64,256]

    4.最终结果 经过 全连接网络,线性变化,最后进行softmax分类

    相关解释

    卷积核大小为2的时候,一次处理2-gram,也就是2个单词,卷积核大小为3-gram,一次处理三个大小的单词。 所以卷积核在对文本进行卷积的操作,更像是对在提取文本在n-gram上的特征。

    取不同卷积核大小进行卷积操作的原因,可以理解为提取这个句子中多个维度不同的信息,使得特征更加的丰富。所以上文中使用了 不同filter_size  的 卷积层 进行 conv1,conv2,conv3来进行提取特征

    这个其中有些需要注意的优化的点,可以参考以下博客(一个大神写的):

    https://mp.weixin.qq.com/s?__biz=MzAxMTk4NDkwNw==&mid=2247485854&idx=1&sn=040d51b0424bdee66f96d63d4ecfbe7e&chksm=9bb980faacce09ec069afa79c903b1e3a5c0d3679c41092e16b2fdd6949aa059883474d0c2af&token=793481651&lang=zh_CN&scene=21#wechat_redirect

    CNN模型主要是 基于上下文token的编码,然后pooling出句子再进行分类,池化时候,max-pooling表现效果最好,文本分类颗粒度比较高,只需要关注部分的关键词即可

    3. Rnn+ Attention 模型

    论文:https://www.aclweb.org/anthology/P16-2034.pdf
    代码:https://github.com/649453932/Chinese-Text-Classification-Pytorch

    RNN 模型现在主流的都是LSTM模型、GRU类型的,针对颗粒度比较细的语义表征,需要使用attention进行

     H表示的是LSTM模型输出的 hidden信息,其中 w是context vector,随机初始化并随着训练更新。最后得到句子表示r ,再进行分类。

    attention 的作用 主要是 寻找句子中,对句子含义最重要,贡献最大的词语找出来

     

     4. Bert模型

    1. 不同的预训练模型,比如ELECTRA、RoBERT、WWM、ALBERT

    2. 除了 [CLS] 外还可以用 avg、max 池化做句表示,也可以各种组合起来

    3. 在领域数据上增量预训练,结合多任务的预训练

    4. Boosting的思想,使用集成蒸馏,训多个大模型集成起来后蒸馏到一个,理论上会有一定的提升(涉及到知识蒸馏)

    5. 先用多任务训,再迁移到自己的任务

    三、文本分类模型实际中遇到的问题(Q&A)

    1.模型的选择问题

    • 短文本,可以尝试下 TextCNN 模型,fastText模型,作为一个基线baseLine的效果
    • 长文本,可以尝试使用RNN 模型,如BiLSTM模型、GRU模型,后接上一个attention注意力机制

    无脑使用Bert模型  但是,要是模型上线 还是需要考虑下模型的推理性能,使用模型的知识蒸馏,或者使用多个大模型的集成,再去蒸馏到一个小模型

    2.样本类别不均衡问题   

        样本不均衡问题,对于Nlp任务来说,基本上都是老生常谈的问题,不管是在分类还是NER中经常会出现,针对这些问题的解决办法,网上也是一大堆,知乎博客,到处都是,这里只是做一个简单的整理吧。这里提一点,样本数量不均衡的本质,还是样本难易程度不一样,本质上还是一个 hard example的问题 。通常是两种方案,一个是样本数据上修改(重采样),另一个是train的时候 Loss部分的修改(重加权)

    • 重采样
      • 欠采样,去掉一些case
      • 过采样,可能会导致一些过拟合现象
      • 数据增强非常常用的方案,较为常见的方法为,EDA(增加,删除,同义词替换)、回译、Masked LM(借鉴预训练语言模型(如BERT)中的自编码语言模型,可以启发式地Mask词汇并进行预测替换)、句法交换、SIMBERT 等预训练模型生成相似度句子,等等
    • 重加权,重加权就是改变分类loss。相较于重采样,重加权loss更加灵活和方便。其常用方法
      • loss类别加权,通常根据类别数量进行加权,加权系数\alpha与类别数量成反比  
      • Focal Loss, 上述loss类别加权主要关注正负样本数量的不平衡,并没有关注难易不平衡。Focal Loss主要关注难易样本的不平衡问题,可根据对高置信度(p)样本进行降权 
        loss类别加权
                                       
      focal loss

    3.数据相关问题

    • 任务定义:先和产品定义好场景和分类任务,初略看看数据,是否有些歧义的数据,挑选出来找产品定义好,尽量规避那些歧义的定义说法,不管是去掉该类说法,还是说统一规定为某一个分类label ,总之人都区分不了的问题,别想着让模型去解决
    • 数据清洗
      • 标注错误问题,模型测试集错误的case,拿出来,规则筛选一遍,同时检查下训练集中的 case,是否有case标注错误的例子,就是训练集中是否存在同样类似的case,标注存在不一致性。
      • 去掉文本强pattern, 很多对文本无意义,出现频次较高的pattern去掉,如,对一段文本进行 意图识别,属于“财经、体育、政治”等Label,文本中出现大量的 xx报道说法,对于文本无意义的说法。
    • 大量无标注数据使用(挖掘无标注数据的价值,利用自监督和半监督学习)
      • 自监督学习,目前火热的预训练模型(Pre-model) 充分利用无标注数据,展现强大能力,进行任务级别的预训练,如,设计分类任务时候,我们在预训练时候 ,MLM的loss 可以和分类的loss一起 进行多任务学习
      • 半监督学习,使用baseLine模型 学习去预测 无标注数据,然后利用数据蒸馏知识生成大量带标签的 数据

    4.分类的损失函数选择

            

    •  sigmod分类
      • 使用于二分类,类似于常用的0、1分类,Torch版本 是 BCELoss()函数
    • softmax分类 
      • lable标签的多分类,torch版本使用的是 nn.CrossEntropyLoss()函数  

       由上图所示,对于多分类的任务,可以定义为,多个二分类,如N分类的任务,我们可以定义N个 sigmoid二分类。优点

            1.每个分类domain 单独维护不同的fc解码层,可以后续支持多意图的输出,解码空间相对于之前的softmax 扩大

            2.新分类domain增加,维护成本较小,只需要更新 fc层就可以,减少对其他的domain的干扰

            3.实际上,每一个domain的fc层,就是一个据识模型,后续可以修改训练策略,灵活的对定义的domain进行据识                                                        

    展开全文
  • 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

    展开全文
  • 自然语言处理实战:新闻文本分类(附代码)

    万次阅读 多人点赞 2020-08-13 18:06:15
    自然语言处理实战:新闻文本分类 ——本文比赛来源于天池零基础入门NLP - 新闻文本分类。 目录自然语言处理实战:新闻文本分类一、赛题理解1、学习目标2、赛题数据3、数据标签4、评测指标5、数据读取6、解题思路二...

    自然语言处理实战:新闻文本分类

    ——本文比赛来源于天池零基础入门NLP - 新闻文本分类

    一、赛题理解

      本章将会对新闻文本分类进行赛题讲解,对赛题数据进行说明,并给出解题思路。

    • 赛题名称:零基础入门NLP之新闻文本分类
    • 赛题目标:通过这道赛题可以引导大家走入自然语言处理的世界,带大家接触NLP的预处理、模型构建和模型训练等知识点。
    • 赛题任务:赛题以自然语言处理为背景,要求选手对新闻文本进行分类,这是一个典型的字符识别问题。

    1、学习目标

    • 理解赛题背景与赛题数据
    • 完成赛题报名和数据下载,理解赛题的解题思路

    2、赛题数据

      赛题以匿名处理后的新闻数据为赛题数据,数据集报名后可见并可下载。赛题数据为新闻文本,并按照字符级别进行匿名处理。整合划分出14个候选分类类别:财经、彩票、房产、股票、家居、教育、科技、社会、时尚、时政、体育、星座、游戏、娱乐的文本数据。
      赛题数据由以下几个部分构成:训练集20w条样本,测试集A包括5w条样本,测试集B包括5w条样本。为了预防选手人工标注测试集的情况,我们将比赛数据的文本按照字符级别进行了匿名处理。

    3、数据标签

      处理后的赛题训练数据如下:

    labeltext
    657 44 66 56 2 3 3 37 5 41 9 57 44 47 45 33 13 63 58 31 17 47 0 1 1 69 26 60 62 15 21 12 49 18 38 20 50 23 57 44 45 33 25 28 47 22 52 35 30 14 24 69 54 7 48 19 11 51 16 43 26 34 53 27 64 8 4 42 36 46 65 69 29 39 15 37 57 44 45 33 69 54 7 25 40 35 30 66 56 47 55 69 61 10 60 42 36 46 65 37 5 41 32 67 6 59 47 0 1 1 68

      在数据集中标签的对应的关系如下:{‘科技’: 0, ‘股票’: 1, ‘体育’: 2, ‘娱乐’: 3, ‘时政’: 4, ‘社会’: 5, ‘教育’: 6, ‘财经’: 7, ‘家居’: 8, ‘游戏’: 9, ‘房产’: 10, ‘时尚’: 11, ‘彩票’: 12, ‘星座’: 13}

    4、评测指标

      评价标准为类别f1_score的均值,选手提交结果与实际测试集的类别进行对比,结果越大越好。

    5、数据读取

      使用Pandas库完成数据读取操作,并对赛题数据进行分析。

    6、解题思路

      赛题思路分析:赛题本质是一个文本分类问题,需要根据每句的字符进行分类。但赛题给出的数据是匿名化的,不能直接使用中文分词等操作,这个是赛题的难点。
      因此本次赛题的难点是需要对匿名字符进行建模,进而完成文本分类的过程。由于文本数据是一种典型的非结构化数据,因此可能涉及到特征提取和分类模型两个部分。为了减低参赛难度,我们提供了一些解题思路供大家参考:

    • 思路1TF-IDF + 机器学习分类器
        直接使用TF-IDF对文本提取特征,并使用分类器进行分类。在分类器的选择上,可以使用SVM、LR、或者XGBoost。
    • 思路2FastText
        FastText是入门款的词向量,利用Facebook提供的FastText工具,可以快速构建出分类器。
    • 思路3WordVec + 深度学习分类器
        WordVec是进阶款的词向量,并通过构建深度学习分类完成分类。深度学习分类的网络结构可以选择TextCNN、TextRNN或者BiLSTM。
    • 思路4Bert词向量
        Bert是高配款的词向量,具有强大的建模学习能力。

    二、数据读取与数据分析

      在上一章节,我们给大家简单介绍了赛题的内容和几种解决方案。从本章开始我们将会逐渐带着大家使用思路1到思路4来完成本次赛题。在讲解工具使用的同时,我们还会讲解一些算法的原理和相关知识点,并会给出一定的参考文献供大家深入学习。

      本章主要内容为数据读取和数据分析,具体使用Pandas库完成数据读取操作,并对赛题数据进行分析构成。

    1、学习目标

    • 学习使用Pandas读取赛题数据
    • 分析赛题数据的分布规律

    2、数据读取

      赛题数据虽然是文本数据,每个新闻是不定长的,但任然使用csv格式进行存储。因此可以直接用Pandas完成数据读取的操作。

    import pandas as pd
    
    train_df = pd.read_csv('./data/train_set.csv', sep='\t', nrows=100)
    print(train_df.head())
    

      这里的read_csv由三部分构成:

    • 读取的文件路径,这里需要根据改成你本地的路径,可以使用相对路径或绝对路径;
    • 分隔符sep,为每列分割的字符,设置为\t即可;
    • 读取行数nrows,为此次读取文件的函数,是数值类型(由于数据集比较大,建议先设置为100);

    在这里插入图片描述

      上图是读取好的数据,是表格的形式。第一列为新闻的类别,第二列为新闻的字符

    3、数据分析

      在读取完成数据集后,我们还可以对数据集进行数据分析的操作。虽然对于非结构数据并不需要做很多的数据分析,但通过数据分析还是可以找出一些规律的。

      此步骤我们读取了所有的训练集数据,在此我们通过数据分析希望得出以下结论:

    • 赛题数据中,新闻文本的长度是多少?
    • 赛题数据的类别分布是怎么样的,哪些类别比较多?
    • 赛题数据中,字符分布是怎么样的?

    3.1句子长度分析

      在赛题数据中每行句子的字符使用空格进行隔开,所以可以直接统计单词的个数来得到每个句子的长度。统计并如下:

    train_df['text_len'] = train_df['text'].apply(lambda x: len(x.split(' ')))
    print(train_df['text_len'].describe())
    

      输出结果为:

    Populating the interactive namespace from numpy and matplotlib

    在这里插入图片描述

      对新闻句子的统计可以得出,本次赛题给定的文本比较长,每个句子平均由907个字符构成,最短的句子长度为2,最长的句子长度为57921

      下图将句子长度绘制了直方图,可见大部分句子的长度都几种在2000以内。

    _ = plt.hist(train_df['text_len'], bins=200)
    plt.xlabel('Text char count')
    plt.title("Histogram of char count")
    plt.savefig('./text_chart_count.png')
    plt.show()
    

    在这里插入图片描述

    3.2新闻类别分布

      接下来可以对数据集的类别进行分布统计,具体统计每类新闻的样本个数

    train_df['label'].value_counts().plot(kind='bar')
    plt.title('News class count')
    plt.xlabel("category")
    plt.savefig('./category.png')
    plt.show()
    

    在这里插入图片描述

      在数据集中标签的对应的关系如下:{‘科技’: 0, ‘股票’: 1, ‘体育’: 2, ‘娱乐’: 3, ‘时政’: 4, ‘社会’: 5, ‘教育’: 6, ‘财经’: 7, ‘家居’: 8, ‘游戏’: 9, ‘房产’: 10, ‘时尚’: 11, ‘彩票’: 12, ‘星座’: 13}

      从统计结果可以看出,赛题的数据集类别分布存在较为不均匀的情况。在训练集中科技类新闻最多,其次是股票类新闻,最少的新闻是星座新闻。

    3.3字符分布统计

      接下来可以统计每个字符出现的次数,首先可以将训练集中所有的句子进行拼接进而划分为字符,并统计每个字符的个数

    all_lines = ' '.join(list(train_df['text']))
    word_count = Counter(all_lines.split(" "))
    word_count = sorted(word_count.items(), key=lambda d:d[1], reverse = True)
    
    print("len(word_count): ", len(word_count)) # 6869
    print("word_count[0]: ", word_count[0]) # ('3750', 7482224)
    print("word_count[-1]: ", word_count[-1]) # ('3133', 1)
    

    在这里插入图片描述

      从统计结果中可以看出,在训练集中总共包括6869个字,其中编号3750的字出现的次数最多,编号3133的字出现的次数最少

      这里还可以根据字在每个句子的出现情况,反推出标点符号。下面代码统计了不同字符在句子中出现的次数,其中字符3750,字符900和字符648在20w新闻的覆盖率接近99%,很有可能是标点符号。

    train_df['text_unique'] = train_df['text'].apply(lambda x: ' '.join(list(set(x.split(' ')))))
    all_lines = ' '.join(list(train_df['text_unique']))
    word_count = Counter(all_lines.split(" "))
    word_count = sorted(word_count.items(), key=lambda d: int(d[1]), reverse = True)
    
    print("word_count[0]: ", word_count[0]) # ('3750', 197997)
    print("word_count[1]: ", word_count[1]) # ('900', 197653)
    print("word_count[2]: ", word_count[2]) # ('648', 191975)
    

    在这里插入图片描述

    4、数据分析的结论

      通过上述分析我们可以得出以下结论

    1. 赛题中每个新闻包含的字符个数平均为1000个,还有一些新闻字符较长;
    2. 赛题中新闻类别分布不均匀,科技类新闻样本量接近4w,星座类新闻样本量不到1k;
    3. 赛题总共包括7000-8000个字符

      通过数据分析,我们还可以得出以下结论:

    1. 每个新闻平均字符个数较多,可能需要截断;
    2. 由于类别不均衡,会严重影响模型的精度;

    5、本章小结

      本章对赛题数据进行读取,并新闻句子长度、类别和字符进行了可视化分析

    三、基于机器学习的文本分类

      在上一章节,我们对赛题的数据进行了读取,在本章我们将使用传统机器学习算法来完成新闻分类的过程,将会结束到赛题的核心知识点。

      在本章我们将开始使用机器学习模型来解决文本分类。机器学习发展比较广,且包括多个分支,本章侧重使用传统机器学习,从下一章开始是基于深度学习的文本分类。

    1、学习目标

    • 学会TF-IDF的原理和使用
    • 使用sklearn的机器学习模型完成文本分类

    2、机器学习模型

      机器学习是对能通过经验自动改进的计算机算法的研究。机器学习通过历史数据训练出模型对应于人类对经验进行归纳的过程,机器学习利用模型对新数据进行预测对应于人类利用总结的规律对新问题进行预测的过程

      机器学习有很多种分支,对于学习者来说应该优先掌握机器学习算法的分类,然后再其中一种机器学习算法进行学习。由于机器学习算法的分支和细节实在是太多,所以如果你一开始就被细节迷住了眼,你就很难知道全局是什么情况的。

      如果你是机器学习初学者,你应该知道如下的事情:

    1. 机器学习能解决一定的问题,但不能奢求机器学习是万能的
    2. 机器学习算法有很多种,看具体问题需要什么,再来进行选择
    3. 每种机器学习算法有一定的偏好,需要具体问题具体分析

    在这里插入图片描述

    3、文本表示方法 Part1

      在机器学习算法的训练过程中,假设给定N个样本,每个样本有M个特征,这样组成了N×M的样本矩阵,然后完成算法的训练和预测。同样的在计算机视觉中可以将图片的像素看作特征,每张图片看作hight×width×3的特征图,一个三维的矩阵来进入计算机进行计算。

      但是在自然语言领域,上述方法却不可行:文本是不定长度的。文本表示成计算机能够运算的数字或向量的方法一般称为词嵌入(Word Embedding)方法。词嵌入将不定长的文本转换到定长的空间内,是文本分类的第一步。

    3.1One-hot

      这里的One-hot与数据挖掘任务中的操作是一致的,即将每一个单词使用一个离散的向量表示。具体将每个字/词编码一个索引,然后根据索引进行赋值

      One-hot表示方法的例子如下:

    句子1:我 爱 北 京 天 安 门
    句子2:我 喜 欢 上 海
    

      首先对所有句子的字进行索引,即将每个字确定一个编号

    {
    	'我': 1, '爱': 2, '北': 3, '京': 4, '天': 5,
      '安': 6, '门': 7, '喜': 8, '欢': 9, '上': 10, '海': 11
    }
    

      在这里共包括11个字,因此每个字可以转换为一个11维度稀疏向量

    我:[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    爱:[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
    ...
    海:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
    

    3.2Bag of Words

      Bag of Words(词袋表示),也称为Count Vectors,每个文档的字/词可以使用其出现次数来进行表示。

    句子1:我 爱 北 京 天 安 门
    句子2:我 喜 欢 上 海
    

      直接统计每个字出现的次数,并进行赋值:

    句子1:我 爱 北 京 天 安 门
    转换为 [1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0]
    
    句子2:我 喜 欢 上 海
    转换为 [1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]
    

      在sklearn中可以直接CountVectorizer来实现这一步骤:

    from sklearn.feature_extraction.text import CountVectorizer
    corpus = [
        'This is the first document.',
        'This document is the second document.',
        'And this is the third one.',
        'Is this the first document?',
    ]
    vectorizer = CountVectorizer()
    vectorizer.fit_transform(corpus).toarray()
    

    3.3N-gram

      N-gram与Count Vectors类似,不过加入了相邻单词组合成为新的单词,并进行计数

      如果N取值为2,则句子1和句子2就变为:

    句子1:我爱 爱北 北京 京天 天安 安门
    句子2:我喜 喜欢 欢上 上海
    

    3.4TF-IDF

      TF-IDF 分数由两部分组成:第一部分是词语频率(Term Frequency),第二部分是逆文档频率(Inverse Document Frequency)。其中计算语料库中文档总数除以含有该词语的文档数量,然后再取对数就是逆文档频率。

    TF(t)= 该词语在当前文档出现的次数 / 当前文档中词语的总数
    
    IDF(t)= log_e(文档总数 / 出现该词语的文档总数)
    

    4、基于机器学习的文本分类

      接下来我们将对比不同文本表示算法的精度,通过本地构建验证集计算F1得分。

      Count Vectors + RidgeClassifier

    import pandas as pd
    
    from sklearn.feature_extraction.text import CountVectorizer
    from sklearn.linear_model import RidgeClassifier
    from sklearn.metrics import f1_score
    
    train_df = pd.read_csv('./data/train_set.csv', sep='\t', nrows=15000)
    
    vectorizer = CountVectorizer(max_features=3000)
    train_test = vectorizer.fit_transform(train_df['text'])
    
    clf = RidgeClassifier()
    clf.fit(train_test[:10000], train_df['label'].values[:10000])
    
    val_pred = clf.predict(train_test[10000:])
    print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
    # 0.74
    

    在这里插入图片描述

      TF-IDF + RidgeClassifier

    import pandas as pd
    
    from sklearn.feature_extraction.text import TfidfVectorizer
    from sklearn.linear_model import RidgeClassifier
    from sklearn.metrics import f1_score
    
    train_df = pd.read_csv('./data/train_set.csv', sep='\t', nrows=15000)
    
    tfidf = TfidfVectorizer(ngram_range=(1, 3), max_features=3000)
    train_test = tfidf.fit_transform(train_df['text'])
    
    clf = RidgeClassifier()
    clf.fit(train_test[:10000], train_df['label'].values[:10000])
    
    val_pred = clf.predict(train_test[10000:])
    print(f1_score(train_df['label'].values[10000:], val_pred, average='macro'))
    # 0.87
    

    在这里插入图片描述

    5、本章小结

      本章我们介绍了基于机器学习的文本分类方法,并完成了两种方法的对比

    四、基于深度学习的文本分类1

      在上一章节,我们使用传统机器学习算法来解决了文本分类问题,从本章开始我们将尝试使用深度学习方法。

      与传统机器学习不同,深度学习既提供特征提取功能,也可以完成分类的功能。从本章开始我们将学习如何使用深度学习来完成文本表示。

    1、学习目标

    • 学习FastText的使用和基础原理
    • 学会使用验证集进行调参

    2、文本表示方法 Part2

    2.1现有文本表示方法的缺陷

      在上一章节,我们介绍几种文本表示方法:

    • One-hot
    • Bag of Words
    • N-gram
    • TF-IDF

      也通过sklean进行了相应的实践,相信你也有了初步的认知。但上述方法都或多或少存在一定的问题:转换得到的向量维度很高,需要较长的训练实践;没有考虑单词与单词之间的关系,只是进行了统计

      与这些表示方法不同,深度学习也可以用于文本表示,还可以将其映射到一个低纬空间。其中比较典型的例子有:FastText、Word2Vec和Bert。在本章我们将介绍FastText,将在后面的内容介绍Word2Vec和Bert。

    2.2FastText

      FastText是一种典型的深度学习词向量的表示方法,它非常简单通过Embedding层将单词映射到稠密空间,然后将句子中所有的单词在Embedding空间中进行平均,进而完成分类操作

      所以FastText是一个三层的神经网络,输入层、隐含层和输出层

    在这里插入图片描述

      下图是使用keras实现的FastText网络结构

    在这里插入图片描述

      FastText在文本分类任务上,是优于TF-IDF的:

    • FastText用单词的Embedding叠加获得的文档向量,将相似的句子分为一类
    • FastText学习到的Embedding空间维度比较低,可以快速进行训练

      如果想深度学习,可以参考论文:

    Bag of Tricks for Efficient Text Classification

    3、基于FastText的文本分类

      FastText可以快速的在CPU上进行训练,最好的实践方法就是官方开源的版本: https://github.com/facebookresearch/fastText/tree/master/python

    • pip安装
    pip install fasttext
    
    • 源码安装
    git clone https://github.com/facebookresearch/fastText.git
    cd fastText
    sudo pip install .
    

      两种安装方法都可以安装,如果你是初学者可以优先考虑使用pip安装。

    • 分类模型
    # 基于FastText的文本分类
    import pandas as pd
    from sklearn.metrics import f1_score
    import fasttext
    
    # 转换为FastText需要的格式
    train_df = pd.read_csv('./data/train_set.csv', sep='\t', nrows=15000)
    train_df['label_ft'] = '__label__' + train_df['label'].astype(str)
    train_df[['text', 'label_ft']].iloc[:-5000].to_csv('train.csv', index=None, header=None, sep='\t')
    
    model = fasttext.train_supervised('train.csv', lr=1.0, wordNgrams=2,
                                      verbose=2, minCount=1, epoch=25, loss="hs")
    
    val_pred = [model.predict(x)[0][0].split('__')[-1] for x in train_df.iloc[-5000:]['text']]
    print(f1_score(train_df['label'].values[-5000:].astype(str), val_pred, average='macro'))
    # 0.82
    

    在这里插入图片描述

      此时数据量比较小得分为0.82,当不断增加训练集数量时,FastText的精度也会不断增加5w条训练样本时,验证集得分可以到0.89-0.90左右

    4、如何使用验证集调参

      在使用TF-IDF和FastText中,有一些模型的参数需要选择,这些参数会在一定程度上影响模型的精度,那么如何选择这些参数呢?

    • 通过阅读文档,要弄清楚这些参数的大致含义,那些参数会增加模型的复杂度
    • 通过在验证集上进行验证模型精度,找到模型在是否过拟合还是欠拟合

    在这里插入图片描述

      这里我们使用10折交叉验证每折使用9/10的数据进行训练,剩余1/10作为验证集检验模型的效果。这里需要注意每折的划分必须保证标签的分布与整个数据集的分布一致。

    label2id = {}
    for i in range(total):
        label = str(all_labels[i])
        if label not in label2id:
            label2id[label] = [i]
        else:
            label2id[label].append(i)
    

      通过10折划分,我们一共得到了10份分布一致的数据,索引分别为0到9,每次通过将一份数据作为验证集,剩余数据作为训练集,获得了所有数据的10种分割。不失一般性,我们选择最后一份完成剩余的实验,即索引为9的一份做为验证集,索引为1-8的作为训练集,然后基于验证集的结果调整超参数,使得模型性能更优。

    5、本章小结

      本章介绍了FastText的原理和基础使用,并进行相应的实践。然后介绍了通过10折交叉验证划分数据集

    五、基于深度学习的文本分类2

      在上一章节,我们通过FastText快速实现了基于深度学习的文本分类模型,但是这个模型并不是最优的。在本章我们将继续深入。
    本章将继续学习基于深度学习的文本分类。

    1、学习目标

    • 学习Word2Vec的使用和基础原理
    • 学习使用TextCNN、TextRNN进行文本表示
    • 学习使用HAN网络结构完成文本分类

    2、文本表示方法 Part3

    2.1词向量

      本节通过word2vec学习词向量。word2vec模型背后的基本思想是对出现在上下文环境里的词进行预测。对于每一条输入文本,我们选取一个上下文窗口和一个中心词,并基于这个中心词去预测窗口里其他词出现的概率。因此,word2vec模型可以方便地从新增语料中学习到新增词的向量表达,是一种高效的在线学习算法(online learning)

      word2vec的主要思路:通过单词和上下文彼此预测,对应的两个算法分别为:

    • Skip-grams (SG):预测上下文
    • Continuous Bag of Words (CBOW):预测目标单词

      另外提出两种更加高效的训练方法:

    Hierarchical softmax
    Negative sampling

      1)Skip-grams原理和网络结构

      Word2Vec模型中,主要有Skip-Gram和CBOW两种模型,从直观上理解,Skip-Gram是给定input word来预测上下文。而CBOW是给定上下文,来预测input word
    在这里插入图片描述

      Word2Vec模型实际上分为了两个部分,第一部分为建立模型,第二部分是通过模型获取嵌入词向量

      Word2Vec的整个建模过程实际上与自编码器(auto-encoder) 的思想很相似,即先基于训练数据构建一个神经网络,当这个模型训练好以后,我们并不会用这个训练好的模型处理新的任务,我们真正需要的是这个模型通过训练数据所学得的参数,例如隐层的权重矩阵——后面我们将会看到这些权重在Word2Vec中实际上就是我们试图去学习的“word vectors”。

      Skip-grams过程

      假如我们有一个句子“The dog barked at the mailman”。

      1. 首先我们选句子中间的一个词作为我们的输入词,例如我们选取“dog”作为input word;
      2. 有了input word以后,我们再定义一个叫做skip_window的参数,它代表着我们从当前input word的一侧(左边或右边)选取词的数量。如果我们设置skip_window=2,那么我们最终获得窗口中的词(包括input word在内)就是[‘The’, ‘dog’,‘barked’, ‘at’]。skip_window=2代表着选取左input word左侧2个词和右侧2个词进入我们的窗口,所以整个窗口大小span=2x2=4。另一个参数叫num_skips,它代表着我们从整个窗口中选取多少个不同的词作为我们的output word,当skip_window=2,num_skips=2时,我们将会得到两组 (input word, output word) 形式的训练数据,即 (‘dog’, ‘barked’),(‘dog’, ‘the’)。
      3. 神经网络基于这些训练数据将会输出一个概率分布,这个概率代表着我们的词典中的每个词作为input word的output word的可能性。这句话有点绕,我们来看个例子。第二步中我们在设置skip_window和num_skips=2的情况下获得了两组训练数据。假如我们先拿一组数据 (‘dog’, ‘barked’) 来训练神经网络,那么模型通过学习这个训练样本,会告诉我们词汇表中每个单词当’dog’作为input word时,其作为output word的可能性。

      也就是说模型的输出概率代表着到我们词典中每个词有多大可能性跟input word同时出现。例如:如果我们向神经网络模型中输入一个单词“Soviet“,那么最终模型的输出概率中,像“Union”, ”Russia“这种相关词的概率将远高于像”watermelon“,”kangaroo“非相关词的概率。因为”Union“,”Russia“在文本中更大可能在”Soviet“的窗口中出现。

      我们将通过给神经网络输入文本中成对的单词来训练它完成上面所说的概率计算。下面的图中给出了一些我们训练样本的例子。我们选定句子“The quick brown fox jumps over lazy dog”,设定我们的窗口大小为2(window_size=2),也就是说我们仅选输入词前后各两个词和输入词进行组合。下图中,蓝色代表input word,方框内代表位于窗口内的单词。

    在这里插入图片描述
    在这里插入图片描述

      我们的模型将会从每对单词出现的次数中习得统计结果。例如,我们的神经网络可能会得到更多类似(“Soviet“,”Union“)这样的训练样本对,而对于(”Soviet“,”Sasquatch“)这样的组合却看到的很少。因此,当我们的模型完成训练后,给定一个单词”Soviet“作为输入,输出的结果中”Union“或者”Russia“要比”Sasquatch“被赋予更高的概率。

      PS:input word和output word都会被我们进行one-hot编码。仔细想一下,我们的输入被one-hot编码以后大多数维度上都是0(实际上仅有一个位置为1),所以这个向量相当稀疏,那么会造成什么结果呢。如果我们将一个1 x 10000的向量和10000 x 300的矩阵相乘,它会消耗相当大的计算资源,为了高效计算,它仅仅会选择矩阵中对应的向量中维度值为1的索引行:

    在这里插入图片描述

      2)Skip-grams训练

      由上部分可知,Word2Vec模型是一个超级大的神经网络(权重矩阵规模非常大)。例如:我们拥有10000个单词的词汇表,我们如果想嵌入300维的词向量,那么我们的输入-隐层权重矩阵和隐层-输出层的权重矩阵都会有 10000 x 300 = 300万个权重,在如此庞大的神经网络中进行梯度下降是相当慢的。更糟糕的是,你需要大量的训练数据来调整这些权重并且避免过拟合。百万数量级的权重矩阵和亿万数量级的训练样本意味着训练这个模型将会是个灾难。

      解决方案
       • 将常见的单词组合(word pairs)或者词组作为单个“words”来处理
       • 对高频次单词进行抽样来减少训练样本的个数
       • 对优化目标采用“negative sampling”方法,这样每个训练样本的训练只会更新一小部分的模型权重,从而降低计算负担

      (1)Word pairs and "phases"

      一些单词组合(或者词组)的含义和拆开以后具有完全不同的意义。比如“Boston Globe”是一种报刊的名字,而单独的“Boston”和“Globe”这样单个的单词却表达不出这样的含义。因此,在文章中只要出现“Boston Globe”,我们就应该把它作为一个单独的词来生成其词向量,而不是将其拆开。同样的例子还有“New York”,“United Stated”等。

      在Google发布的模型中,它本身的训练样本中有来自Google News数据集中的1000亿的单词,但是除了单个单词以外,单词组合(或词组)又有3百万之多。、

      (2)对高频词抽样

      在上一部分中,对于原始文本为“The quick brown fox jumps over the laze dog”,如果使用大小为2的窗口,那么我们可以得到图中展示的那些训练样本。

    在这里插入图片描述

      但是对于“the”这种常用高频单词,这样的处理方式会存在下面两个问题:

    1. 当我们得到成对的单词训练样本时,(“fox”, “the”) 这样的训练样本并不会给我们提供关于“fox”更多的语义信息,因为“the”在每个单词的上下文中几乎都会出现
    2. 由于在文本中“the”这样的常用词出现概率很大,因此我们将会有大量的(”the“,…)这样的训练样本,而这些样本数量远远超过了我们学习“the”这个词向量所需的训练样本数

      Word2Vec通过“抽样”模式来解决这种高频词问题。它的基本思想如下:对于我们在训练原始文本中遇到的每一个单词,它们都有一定概率被我们从文本中删掉,而这个被删除的概率与单词的频率有关

      ωi 是一个单词,Z(ωi) 是 ωi 这个单词在所有语料中出现的频次,例如:如果单词“peanut”在10亿规模大小的语料中出现了1000次,那么 Z(peanut) = 1000/1000000000 = 1e - 6。

      P(ωi) 代表着保留某个单词的概率:
    在这里插入图片描述

      (3)Negative sampling

      训练一个神经网络意味着要输入训练样本并且不断调整神经元的权重,从而不断提高对目标的准确预测。每当神经网络经过一个训练样本的训练,它的权重就会进行一次调整

      所以,词典的大小决定了我们的Skip-Gram神经网络将会拥有大规模的权重矩阵,所有的这些权重需要通过数以亿计的训练样本来进行调整,这是非常消耗计算资源的,并且实际中训练起来会非常慢。

      负采样(negative sampling)解决了这个问题,它是用来提高训练速度并且改善所得到词向量的质量的一种方法。不同于原本每个训练样本更新所有的权重,负采样每次让一个训练样本仅仅更新一小部分的权重,这样就会降低梯度下降过程中的计算量。

      当我们用训练样本 ( input word: “fox”,output word: “quick”) 来训练我们的神经网络时,“ fox”和“quick”都是经过one-hot编码的。如果我们的词典大小为10000时,在输出层,我们期望对应“quick”单词的那个神经元结点输出1,其余9999个都应该输出0。在这里,这9999个我们期望输出为0的神经元结点所对应的单词我们称为“negative” word。

      当使用负采样时,我们将随机选择一小部分的negative words(比如选5个negative words)来更新对应的权重。我们也会对我们的“positive” word进行权重更新(在我们上面的例子中,这个单词指的是”quick“)。

      PS: 在论文中,作者指出指出对于小规模数据集,选择5-20个negative words会比较好,对于大规模数据集可以仅选择2-5个negative words。

      我们使用“一元模型分布(unigram distribution)”来选择“negative words”。一个单词被选作negative sample的概率跟它出现的频次有关,出现频次越高的单词越容易被选作negative words。

      每个单词被选为“negative words”的概率计算公式:

    在这里插入图片描述

      其中 f(ωi)代表着单词出现的频次,而公式中开3/4的根号完全是基于经验的。

      在代码负采样的代码实现中,unigram table有一个包含了一亿个元素的数组,这个数组是由词汇表中每个单词的索引号填充的,并且这个数组中有重复,也就是说有些单词会出现多次。那么每个单词的索引在这个数组中出现的次数该如何决定呢,有公式,也就是说计算出的负采样概率*1亿=单词在表中出现的次数。

      有了这张表以后,每次去我们进行负采样时,只需要在0-1亿范围内生成一个随机数,然后选择表中索引号为这个随机数的那个单词作为我们的negative word即可。一个单词的负采样概率越大,那么它在这个表中出现的次数就越多,它被选中的概率就越大。

      3) Hierarchical Softmax

      (1) 霍夫曼树

    输入:权值为(w1,w2,…wn)的n个节点
    输出:对应的霍夫曼树
    
    1. 将(w1,w2,…wn)看做是有n棵树的森林,每个树仅有一个节点
    2. 在森林中选择根节点权值最小的两棵树进行合并,得到一个新的树,这两颗树分布作为新树的左右子树。新树的根节点权重为左右子树的根节点权重之和
    3. 将之前的根节点权值最小的两棵树从森林删除,并把新树加入森林
    4. 重复步骤 2 和 3 直到森林里只有一棵树为止

      下面我们用一个具体的例子来说明霍夫曼树建立的过程,我们有(a,b,c,d,e,f)共6个节点,节点的权值分布是(16,4,8,6,20,3)。

      首先是最小的b和f合并,得到的新树根节点权重是7.此时森林里5棵树,根节点权重分别是16,8,6,20,7。此时根节点权重最小的6,7合并,得到新子树,依次类推,最终得到下面的霍夫曼树。
    在这里插入图片描述

      那么霍夫曼树有什么好处呢?一般得到霍夫曼树后我们会对叶子节点进行霍夫曼编码,由于权重高的叶子节点越靠近根节点,而权重低的叶子节点会远离根节点,这样我们的高权重节点编码值较短,而低权重值编码值较长。这保证的树的带权路径最短,也符合我们的信息论,即我们希望越常用的词拥有更短的编码。如何编码呢?一般对于一个霍夫曼树的节点(根节点除外),可以约定左子树编码为0,右子树编码为1。如上图,则可以得到c的编码是00。

      在word2vec中,约定编码方式和上面的例子相反,即约定左子树编码为1,右子树编码为0,同时约定左子树的权重不小于右子树的权重。

      更多原理可参考:霍夫曼树原理

      (2)Hierarchical Softmax过程

      为了避免要计算所有词的softmax概率,word2vec采样了霍夫曼树来代替从隐藏层到输出softmax层的映射。

      霍夫曼树的建立:

       • 根据标签(label)和频率建立霍夫曼树(label出现的频率越高,Huffman树的路径越短)
       • Huffman树中每一叶子结点代表一个label

    在这里插入图片描述

      如上图所示:

    在这里插入图片描述在这里插入图片描述

      注意:此时的theta是一个待定系数,它是由推导最大似然之后求解得到迭代式子。

    在这里插入图片描述

      使用gensim训练word2vec

    # 导入包
    import logging
    import random
    
    import numpy as np
    import torch
    
    logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(levelname)s: %(message)s')
    
    # set seed
    seed = 666
    random.seed(seed)
    np.random.seed(seed)
    torch.cuda.manual_seed(seed)
    torch.manual_seed(seed)
    
    # 读取少量数据
    # split data to 10 fold
    fold_num = 10
    data_file = './data/train_set.csv'
    import pandas as pd
    
    train_set = pd.read_csv(data_file, sep='\t', nrows=1000)
    train_set
    
    # data to fold
    def all_data2fold(fold_num, num=10000):
        fold_data = []
        f = pd.read_csv(data_file, sep='\t', encoding='UTF-8')
        texts = f['text'].tolist()[:num]
        labels = f['label'].tolist()[:num]
    
        total = len(labels)
    
        index = list(range(total))
        np.random.shuffle(index)
    
        all_texts = []
        all_labels = []
        for i in index:
            all_texts.append(texts[i])
            all_labels.append(labels[i])
    
        label2id = {}
        for i in range(total):
            label = str(all_labels[i])
            if label not in label2id:
                label2id[label] = [i]
            else:
                label2id[label].append(i)
    
        all_index = [[] for _ in range(fold_num)]
        for label, data in label2id.items():
            # print(label, len(data))
            batch_size = int(len(data) / fold_num)
            other = len(data) - batch_size * fold_num
            for i in range(fold_num):
                cur_batch_size = batch_size + 1 if i < other else batch_size
                # print(cur_batch_size)
                batch_data = [data[i * batch_size + b] for b in range(cur_batch_size)]
                all_index[i].extend(batch_data)
    
        batch_size = int(total / fold_num)
        other_texts = []
        other_labels = []
        other_num = 0
        start = 0
    
        for fold in range(fold_num):
            num = len(all_index[fold])
            texts = [all_texts[i] for i in all_index[fold]]
            labels = [all_labels[i] for i in all_index[fold]]
    
            if num > batch_size:
                fold_texts = texts[:batch_size]
                other_texts.extend(texts[batch_size:])
                fold_labels = labels[:batch_size]
                other_labels.extend(labels[batch_size:])
                other_num += num - batch_size
            elif num < batch_size:
                end = start + batch_size - num
                fold_texts = texts + other_texts[start: end]
                fold_labels = labels + other_labels[start: end]
                start = end
            else:
                fold_texts = texts
                fold_labels = labels
    
            assert batch_size == len(fold_labels)
    
            # shuffle
            index = list(range(batch_size))
            np.random.shuffle(index)
    
            shuffle_fold_texts = []
            shuffle_fold_labels = []
            for i in index:
                shuffle_fold_texts.append(fold_texts[i])
                shuffle_fold_labels.append(fold_labels[i])
    
            data = {'label': shuffle_fold_labels, 'text': shuffle_fold_texts}
            fold_data.append(data)
    
        logging.info("Fold lens %s", str([len(data['label']) for data in fold_data]))
    
        return fold_data
    
    
    fold_data = all_data2fold(10)
    
    # build train data for word2vec
    fold_id = 9
    
    train_texts = []
    for i in range(0, fold_id):
        data = fold_data[i]
        train_texts.extend(data['text'])
    
    logging.info('Total %d docs.' % len(train_texts))
    
    # 训练并保存模型
    logging.info('Start training...')
    from gensim.models.word2vec import Word2Vec
    
    num_features = 100     # Word vector dimensionality
    num_workers = 8       # Number of threads to run in parallel
    
    train_texts = list(map(lambda x: list(x.split()), train_texts))
    model = Word2Vec(train_texts, workers=num_workers, size=num_features)
    model.init_sims(replace=True)
    
    # save model
    model.save("./data/word2vec.bin")
    
    # 加载模型并格式化
    # load model
    model = Word2Vec.load("./data/word2vec.bin")
    
    # convert format
    model.wv.save_word2vec_format('./data/word2vec.txt', binary=False)
    

    在这里插入图片描述
    在这里插入图片描述

    2.2TextCNN

      TextCNN利用CNN(卷积神经网络)进行文本特征抽取,不同大小的卷积核分别抽取n-gram特征,卷积计算出的特征图经过MaxPooling保留最大的特征值,然后将拼接成一个向量作为文本的表示

      这里我们基于TextCNN原始论文的设定,分别采用了100个大小为2,3,4的卷积核,最后得到的文本向量大小为100*3=300维。
    在这里插入图片描述

    2.3TextRNN

      TextRNN利用RNN(循环神经网络)进行文本特征抽取,由于文本本身是一种序列,而LSTM天然适合建模序列数据。TextRNN将句子中每个词的词向量依次输入到双向双层LSTM,分别将两个方向最后一个有效位置的隐藏层拼接成一个向量作为文本的表示。

    在这里插入图片描述

    3、基于TextCNN、TextRNN的文本表示

    3.1TextCNN

    import numpy as np
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.autograd import Variable
    import torch.nn.functional as F
    
    dtype = torch.FloatTensor
    
    # Text-CNN Parameter
    embedding_size = 2 # n-gram
    sequence_length = 3
    num_classes = 2  # 0 or 1
    filter_sizes = [2, 2, 2] # n-gram window
    num_filters = 3
    
    # 3 words sentences (=sequence_length is 3)
    sentences = ["i love you", "he loves me", "she likes baseball", "i hate you", "sorry for that", "this is awful"]
    labels = [1, 1, 1, 0, 0, 0]  # 1 is good, 0 is not good.
    
    word_list = " ".join(sentences).split()
    word_list = list(set(word_list))
    word_dict = {w: i for i, w in enumerate(word_list)}
    vocab_size = len(word_dict)
    
    inputs = []
    for sen in sentences:
        inputs.append(np.asarray([word_dict[n] for n in sen.split()]))
    
    targets = []
    for out in labels:
        targets.append(out) # To using Torch Softmax Loss function
    
    input_batch = Variable(torch.LongTensor(inputs))
    target_batch = Variable(torch.LongTensor(targets))
    
    
    class TextCNN(nn.Module):
        def __init__(self):
            super(TextCNN, self).__init__()
    
            self.num_filters_total = num_filters * len(filter_sizes)
            self.W = nn.Parameter(torch.empty(vocab_size, embedding_size).uniform_(-1, 1)).type(dtype)
            self.Weight = nn.Parameter(torch.empty(self.num_filters_total, num_classes).uniform_(-1, 1)).type(dtype)
            self.Bias = nn.Parameter(0.1 * torch.ones([num_classes])).type(dtype)
    
        def forward(self, X):
            embedded_chars = self.W[X] # [batch_size, sequence_length, sequence_length]
            embedded_chars = embedded_chars.unsqueeze(1) # add channel(=1) [batch, channel(=1), sequence_length, embedding_size]
    
            pooled_outputs = []
            for filter_size in filter_sizes:
                # conv : [input_channel(=1), output_channel(=3), (filter_height, filter_width), bias_option]
                conv = nn.Conv2d(1, num_filters, (filter_size, embedding_size), bias=True)(embedded_chars)
                h = F.relu(conv)
                # mp : ((filter_height, filter_width))
                mp = nn.MaxPool2d((sequence_length - filter_size + 1, 1))
                # pooled : [batch_size(=6), output_height(=1), output_width(=1), output_channel(=3)]
                pooled = mp(h).permute(0, 3, 2, 1)
                pooled_outputs.append(pooled)
    
            h_pool = torch.cat(pooled_outputs, len(filter_sizes)) # [batch_size(=6), output_height(=1), output_width(=1), output_channel(=3) * 3]
            h_pool_flat = torch.reshape(h_pool, [-1, self.num_filters_total]) # [batch_size(=6), output_height * output_width * (output_channel * 3)]
    
            model = torch.mm(h_pool_flat, self.Weight) + self.Bias # [batch_size, num_classes]
            return model
    
    model = TextCNN()
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    # Training
    for epoch in range(5000):
        optimizer.zero_grad()
        output = model(input_batch)
    
        # output : [batch_size, num_classes], target_batch : [batch_size] (LongTensor, not one-hot)
        loss = criterion(output, target_batch)
        if (epoch + 1) % 1000 == 0:
            print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
    
        loss.backward()
        optimizer.step()
    
    # Test
    test_text = 'sorry hate you'
    tests = [np.asarray([word_dict[n] for n in test_text.split()])]
    test_batch = Variable(torch.LongTensor(tests))
    
    # Predict
    predict = model(test_batch).data.max(1, keepdim=True)[1]
    if predict[0][0] == 0:
        print(test_text, "is Bad Mean...")
    else:
        print(test_text, "is Good Mean!!")
    

    在这里插入图片描述

    3.2TextRNN

    import numpy as np
    import torch
    import torch.nn as nn
    import torch.optim as optim
    from torch.autograd import Variable
    
    dtype = torch.FloatTensor
    
    sentences = [ "i like dog", "i love coffee", "i hate milk"]
    
    word_list = " ".join(sentences).split()
    word_list = list(set(word_list))
    word_dict = {w: i for i, w in enumerate(word_list)}
    number_dict = {i: w for i, w in enumerate(word_list)}
    n_class = len(word_dict)
    
    # TextRNN Parameter
    batch_size = len(sentences)
    n_step = 2 # number of cells(= number of Step)
    n_hidden = 5 # number of hidden units in one cell
    
    def make_batch(sentences):
        input_batch = []
        target_batch = []
    
        for sen in sentences:
            word = sen.split()
            input = [word_dict[n] for n in word[:-1]]
            target = word_dict[word[-1]]
    
            input_batch.append(np.eye(n_class)[input])
            target_batch.append(target)
    
        return input_batch, target_batch
    
    # to Torch.Tensor
    input_batch, target_batch = make_batch(sentences)
    input_batch = Variable(torch.Tensor(input_batch))
    target_batch = Variable(torch.LongTensor(target_batch))
    
    class TextRNN(nn.Module):
        def __init__(self):
            super(TextRNN, self).__init__()
    
            self.rnn = nn.RNN(input_size=n_class, hidden_size=n_hidden)
            self.W = nn.Parameter(torch.randn([n_hidden, n_class]).type(dtype))
            self.b = nn.Parameter(torch.randn([n_class]).type(dtype))
    
        def forward(self, hidden, X):
            X = X.transpose(0, 1) # X : [n_step, batch_size, n_class]
            outputs, hidden = self.rnn(X, hidden)
            # outputs : [n_step, batch_size, num_directions(=1) * n_hidden]
            # hidden : [num_layers(=1) * num_directions(=1), batch_size, n_hidden]
            outputs = outputs[-1] # [batch_size, num_directions(=1) * n_hidden]
            model = torch.mm(outputs, self.W) + self.b # model : [batch_size, n_class]
            return model
    
    model = TextRNN()
    
    criterion = nn.CrossEntropyLoss()
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    # Training
    for epoch in range(5000):
        optimizer.zero_grad()
    
        # hidden : [num_layers * num_directions, batch, hidden_size]
        hidden = Variable(torch.zeros(1, batch_size, n_hidden))
        # input_batch : [batch_size, n_step, n_class]
        output = model(hidden, input_batch)
    
        # output : [batch_size, n_class], target_batch : [batch_size] (LongTensor, not one-hot)
        loss = criterion(output, target_batch)
        if (epoch + 1) % 1000 == 0:
            print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.6f}'.format(loss))
    
        loss.backward()
        optimizer.step()
    
    input = [sen.split()[:2] for sen in sentences]
    
    # Predict
    hidden = Variable(torch.zeros(1, batch_size, n_hidden))
    predict = model(hidden, input_batch).data.max(1, keepdim=True)[1]
    print([sen.split()[:2] for sen in sentences], '->', [number_dict[n.item()] for n in predict.squeeze()])
    

    在这里插入图片描述

    4、使用HAN用于文本分类

      Hierarchical Attention Network for Document Classification(HAN)基于层级注意力,在单词和句子级别分别编码并基于注意力获得文档的表示,然后经过Softmax进行分类。其中word encoder的作用是获得句子的表示,可以替换为上节提到的TextCNN和TextRNN,也可以替换为下节中的BERT。

    在这里插入图片描述

    5、本章小结

      本章介绍了Word2Vec的使用,以及TextCNN、TextRNN的原理和训练,最后介绍了用于长文档分类的HAN

    六、基于深度学习的文本分类3

    1、学习目标

    • 了解Transformer的原理和基于预训练语言模型(Bert)的词表示
    • 学会Bert的使用,具体包括pretrain和finetune

    2、文本表示方法Part4

    2.1Transformer原理

      Transformer是在"Attention is All You Need"中提出的,模型的编码部分是一组编码器的堆叠(论文中依次堆叠六个编码器),模型的解码部分是由相同数量的解码器的堆叠。

    在这里插入图片描述

      我们重点关注编码部分。他们结构完全相同,但是并不共享参数,每一个编码器都可以拆解成两部分。在对输入序列做词的向量化之后,它们首先流过一个self-attention层,该层帮助编码器在它编码单词的时候能够看到输入序列中的其他单词。self-attention的输出流向一个前向网络(Feed Forward Neural Network),每个输入位置对应的前向网络是独立互不干扰的。最后将输出传入下一个编码器。

    在这里插入图片描述

      这里能看到Transformer的一个关键特性,每个位置的词仅仅流过它自己的编码器路径。在self-attention层中,这些路径两两之间是相互依赖的。前向网络层则没有这些依赖性,但这些路径在流经前向网络时可以并行执行。

      Self-Attention中使用多头机制,使得不同的attention heads所关注的的部分不同。

    在这里插入图片描述

      编码"it"时,一个attention head集中于"the animal",另一个head集中于“tired”,某种意义上讲,模型对“it”的表达合成了的“animal”和“tired”两者。

      对于自注意力的详细计算,欢迎大家参考Jay Alammar 关于Transformer的博客,这里不再展开。

      除此之外,为了使模型保持单词的语序,模型中添加了位置编码向量。如下图所示,每行对应一个向量的位置编码。因此,第一行将是我们要添加到输入序列中第一个单词的嵌入的向量。每行包含512个值—每个值都在1到-1之间。因为左侧是用sine函数生成,右侧是用cosine生成,所以可以观察到中间显著的分隔。

    在这里插入图片描述

      编码器结构中值得提出注意的一个细节是,在每个子层中(Self-attention, FFNN),都有残差连接,并且紧跟着layer-normalization 。如果我们可视化向量和LayerNorm操作,将如下所示:

    在这里插入图片描述

    2.2基于预训练语言模型的词表示

      基于预训练语言模型的词表示由于可以建模上下文信息,进而解决传统静态词向量不能建模“一词多义”语言现象的问题。最早提出的ELMo基于两个单向LSTM,将从左到右和从右到左两个方向的隐藏层向量表示拼接学习上下文词嵌入。而GPT用Transformer代替LSTM作为编码器,首先进行了语言模型预训练,然后在下游任务微调模型参数。但GPT由于仅使用了单向语言模型,因此难以建模上下文信息。为了解决以上问题,研究者们提出了BERT,BERT模型结构如下图所示,它是一个基于Transformer的多层Encoder,通过执行一系列预训练,进而得到深层的上下文表示。

    在这里插入图片描述

      ELMo论文题目中Deep是指双向双层LSTM,而更关键的在于context。传统方法生成的单词映射表的形式,即先为每个单词生成一个静态的词向量,之后这个单词的表示就被固定住了,不会跟着上下文的变化而做出改变。事实上,由于一词多义的语言现象,静态词向量是有很大的弊端的。以bank为例,如果训练语料的足够大,事先学好的词向量中混杂着所有的语义。而当下游应用时,即使在新句子中,bank的上下文里包含money等词,我们基本可以确定bank是“银行”的语义而不是在其他上下文中的“河床”的语义,但是由于静态词向量不能跟随上下文而进行变化,所以bank的表示中还是混杂着多种语义。为了解决这一问题,ELMo首先进行了语言模型预训练,然后在下游任务中动态调整Word Embedding,因此最后输出的词表示能够充分表达单词在上下文中的特定语义,进而解决一词多义的问题。

      GPT来自于openai,是一种生成式预训练模型。GPT 除了将ELMo中的LSTM替换为Transformer 的Encoder外,更开创了NLP界基于预训练-微调的新范式。尽管GPT采用的也是和ELMo相同的两阶段模式,但GPT在第一个阶段并没有采取ELMo中使用两个单向双层LSTM拼接的结构,而是采用基于自回归式的单向语言模型。

      Google在NAACL 2018发表的论文中提出了BERT,与GPT相同,BERT也采用了预训练-微调这一两阶段模式。但在模型结构方面,BERT采用了ELMO的范式,即使用双向语言模型代替GPT中的单向语言模型,但是BERT的作者认为ELMo使用两个单向语言模型拼接的方式太粗暴,因此在第一阶段的预训练过程中,BERT提出掩码语言模型,即类似完形填空的方式,通过上下文来预测单词本身,而不是从右到左或从左到右建模,这允许模型能够自由地编码每个层中来自两个方向的信息。而为了学习句子的词序关系,BERT将Transformer中的三角函数位置表示替换为可学习的参数,其次为了区别单句和双句输入,BERT还引入了句子类型表征。BERT的输入如图所示。此外,为了充分学习句子间的关系,BERT提出了下一个句子预测任务。具体来说,在训练时,句子对中的第二个句子有50%来自与原有的连续句子,而其余50%的句子则是通过在其他句子中随机采样。同时,消融实验也证明,这一预训练任务对句间关系判断任务具有很大的贡献。除了模型结构不同之外,BERT在预训练时使用的无标签数据规模要比GPT大的多。

    在这里插入图片描述

      在第二阶段,与GPT相同,BERT也使用Fine-Tuning模式来微调下游任务。如下图所示,BERT与GPT不同,它极大的减少了改造下游任务的要求,只需在BERT模型的基础上,通过额外添加Linear分类器,就可以完成下游任务。具体来说,对于句间关系判断任务,与GPT类似,只需在句子之间加个分隔符,然后在两端分别加上起始和终止符号。在进行输出时,只需把句子的起始符号[CLS]在BERT最后一层中对应的位置接一个Softmax+Linear分类层即可;对于单句分类问题,也与GPT类似,只需要在句子两段分别增加起始和终止符号,输出部分和句间关系判断任务保持一致即可;对于问答任务,由于需要输出答案在给定段落的起始和终止位置,因此需要先将问题和段落按照句间关系判断任务构造输入,输出只需要在BERT最后一层中第二个句子,即段落的每个单词对应的位置上分别接判断起始和终止位置的分类器;最后,对于NLP中的序列标注问题,输入与单句分类任务一致,不同的是在BERT最后一层中每个单词对应的位置上接分类器即可。

    在这里插入图片描述

      更重要的是,BERT开启了NLP领域“预训练-微调”这种两阶段的全新范式。在第一阶段首先在海量无标注文本上预训练一个双向语言模型,这里特别值得注意的是,将Transformer作为特征提取器在解决并行性和长距离依赖问题上都要领先于传统的RNN或者CNN,通过预训练的方式,可以将训练数据中的词法、句法、语法知识以网络参数的形式提炼到模型当中,在第二阶段使用下游任务的数据Fine-tuning不同层数的BERT模型参数,或者把BERT当作特征提取器生成BERT Embedding,作为新特征引入下游任务。这种两阶段的全新范式尽管是来自于计算机视觉领域,但是在自然语言处理领域一直没有得到很好的运用,而BERT作为近些年NLP突破性进展的集大成者,最大的亮点可以说不仅在于模型性能好,并且几乎所有NLP任务都可以很方便地基于BERT进行改造,进而将预训练学到的语言学知识引入下游任务,进一步提升模型的性能。

    3、基于Bert的文本分类

    3.1Bert Pretrain

      预训练过程使用了Google基于Tensorflow发布的BERT源代码。首先从原始文本中创建训练数据,由于本次比赛的数据都是ID,这里重新建立了词表,并且建立了基于空格的分词器。

    class WhitespaceTokenizer(object):
        """WhitespaceTokenizer with vocab."""
        def __init__(self, vocab_file):
            self.vocab = load_vocab(vocab_file)
            self.inv_vocab = {v: k for k, v in self.vocab.items()}
    
        def tokenize(self, text):
            split_tokens = whitespace_tokenize(text)
            output_tokens = []
            for token in split_tokens:
                if token in self.vocab:
                    output_tokens.append(token)
                else:
                    output_tokens.append("[UNK]")
            return output_tokens
    
        def convert_tokens_to_ids(self, tokens):
            return convert_by_vocab(self.vocab, tokens)
    
        def convert_ids_to_tokens(self, ids):
            return convert_by_vocab(self.inv_vocab, ids)
    

      预训练由于去除了NSP预训练任务,因此将文档处理多个最大长度为256的段,如果最后一个段的长度小于256/2则丢弃。每一个段执行按照BERT原文中执行掩码语言模型,然后处理成tfrecord格式。

    def create_segments_from_document(document, max_segment_length):
        """Split single document to segments according to max_segment_length."""
        assert len(document) == 1
        document = document[0]
        document_len = len(document)
    
        index = list(range(0, document_len, max_segment_length))
        other_len = document_len % max_segment_length
        if other_len > max_segment_length / 2:
            index.append(document_len)
    
        segments = []
        for i in range(len(index) - 1):
            segment = document[index[i]: index[i+1]]
            segments.append(segment)
    
        return segments
    

      在预训练过程中,也只执行掩码语言模型任务,因此不再计算下一句预测任务的loss。

    (masked_lm_loss, masked_lm_example_loss, masked_lm_log_probs) = get_masked_lm_output(
        bert_config, model.get_sequence_output(), model.get_embedding_table(),
        masked_lm_positions, masked_lm_ids, masked_lm_weights)
    
    total_loss = masked_lm_loss
    

      为了适配句子的长度,以及减小模型的训练时间,我们采取了BERT-mini模型,详细配置如下。

    {
      "hidden_size": 256,
      "hidden_act": "gelu",
      "initializer_range": 0.02,
      "vocab_size": 5981,
      "hidden_dropout_prob": 0.1,
      "num_attention_heads": 4,
      "type_vocab_size": 2,
      "max_position_embeddings": 256,
      "num_hidden_layers": 4,
      "intermediate_size": 1024,
      "attention_probs_dropout_prob": 0.1
    }
    

      由于我们的整体框架使用Pytorch,因此需要将最后一个检查点转换成Pytorch的权重。

    def convert_tf_checkpoint_to_pytorch(tf_checkpoint_path, bert_config_file, pytorch_dump_path):
        # Initialise PyTorch model
        config = BertConfig.from_json_file(bert_config_file)
        print("Building PyTorch model from configuration: {}".format(str(config)))
        model = BertForPreTraining(config)
    
        # Load weights from tf checkpoint
        load_tf_weights_in_bert(model, config, tf_checkpoint_path)
    
        # Save pytorch-model
        print("Save PyTorch model to {}".format(pytorch_dump_path))
        torch.save(model.state_dict(), pytorch_dump_path)
    

      预训练消耗的资源较大,硬件条件不允许的情况下建议直接下载开源的模型

    3.2Bert Finetune

    在这里插入图片描述

      微调将最后一层的第一个token即[CLS]的隐藏向量作为句子的表示,然后输入到softmax层进行分类。

    sequence_output, pooled_output = \
        self.bert(input_ids=input_ids, token_type_ids=token_type_ids)
    
    if self.pooled:
        reps = pooled_output
    else:
        reps = sequence_output[:, 0, :]  # sen_num x 256
    
    if self.training:
        reps = self.dropout(reps)
    

    4、本章小结

      本章介绍了Bert的原理和使用,具体包括pretrain和finetune两部分

    【github地址】

      源码和数据已经在Github项目中给出,网址为:暂定。

    【参考资料】

    1、https://github.com/datawhalechina/team-learning
    2、https://mp.weixin.qq.com/s/I-yeHQopTFdNk67Ir_iWiA
    3、https://github.com/hecongqing/2018-daguan-competition
    4、Task 2: Word Vectors and Word Senses (附代码)(Stanford CS224N NLP with Deep Learning Winter 2019)

    展开全文
  • A Survey on Text Classification: From Shallow to Deep Learning ...根据所涉及的文本以及用于特征提取和分类的模型创建用于文本分类的分类法。然后,详细讨论这些类别中的每一个类别,涉及支持预测测试的技术.
  • 我本次对4类文本进行分类((所有截图代码和数据集最后附带免费下载地址)) 主要步骤: 1.各种读文件,写文件 2.使用jieba分词将中文文本切割 3.对处理之后的文本开始用TF-IDF算法进行单词权值的计算 4....
  • 卷积神经网络文本分类

    万次阅读 2018-03-24 10:14:23
    卷积神经网络(CNN)在图像处理领域取得了很大的成绩,它的卷积和池化结构能很好提取图像的信息,而在 NLP 领域循环神经网络(RNN)则使用的更多,RNN 及其各种变种因拥有记忆功能使得它们更擅长处理上下文。...
  • 使用Pytorch的LSTM文本分类

    千次阅读 2020-07-20 17:54:31
    在训练之前,我们为检查点和指标构建保存和加载功能。 对于检查点,将保存模型参数和优化器; 对于度量,可以保存火车损耗,有效损耗和全局步长,以便以后可以轻松地重建图表。 We train the LSTM with 10 epochs ...
  • 文本分类六十年

    千次阅读 2020-11-27 19:29:08
    作者| Lucy出品 | AI科技大本营文本分类是自然语言处理中最基本而且非常有必要的任务,大部分自然语言处理任务都可以看作是个分类任务。近年来,深度学习所取得的前所未有的成功,使得该...
  • fastText原理和文本分类实战,看这一篇就够了

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

    千次阅读 多人点赞 2019-03-21 11:06:17
    Text-CNN 1.文本分类 转眼学生生涯就结束了,在家待就业期间正好有一段空闲期,可以对曾经感兴趣的一些知识点进行总结。 ...本文介绍NLP中文本分类任务中核心流程进行了系统的介绍,文末给出一个...文本分类是自...
  • NLP-使用CNN进行文本分类

    万次阅读 多人点赞 2018-04-20 22:39:57
    CNN最初用于处理图像问题,但是在自然语言处理中,使用CNN进行文本分类也可以取得不错的效果。 在文本中,每个词都可以用一个行向量表示,一句话就可以用一个矩阵来表示,那么处理文本就与处理图像是类似的了。 ...
  • 文本编辑器上传图片的功能

    千次阅读 2022-03-28 09:51:50
    今天,我们来讲一下富文本编辑器上传图片功能的操作。首先,在这里需要引入一个js插件 (config.js),在插件里面写下配置图片上传的路径。 接下来,既然要实现上传图片的功能,那我们就需要一个东西是用来接收...
  • python实现文本分类

    万次阅读 多人点赞 2018-05-23 11:13:38
    一、中文文本分类流程:1. 预处理2. 中文分词3. 结构化表示-构建词向量空间4.权重策略-TF-IDF5. 分类器6. 评价二、具体细节1.预处理 1.1. 得到训练集语料库 本文采用复旦中文文本分类语料库,下载链接:...
  • 从键盘输入若干学生信息,写入文本文件中,再从该文本文件中读出学生的信息。 具体要求如下: (1)应定义学生Student,成员数据包括学号、姓名和成绩等; (2)建议用友元函数为学生重载输入输出流的<<和>...
  • NLPIR文本分析工具的功能和特色介绍

    千次阅读 2019-11-05 15:39:52
    通常用于研究分析的数据中,非结构化的数据所占比例更高,这些非结构化的数据中也包含着重要的信息量,要运用文本分析工具来完成信息提取。文本分析是自然语言处理的一个小分支,是指从文本中抽取特征词进行量化以...
  • 本文将详细讲解数据预处理、Jieba分词和文本聚类知识,这篇文章可以说是文本挖掘和自然语言处理的入门文章。两万字基础文章,希望对您有所帮助。欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列...
  • 文本分类的算法总结

    万次阅读 2017-04-25 09:23:29
    本文对文本分类中的常用算法进行了小结,比较它们之间的优劣,为算法的选择提供依据。 一、决策树(Decision Trees) 优点: 1、决策树易于理解和解释.人们在通过解释后都有能力去理解决策树所表达的意义。 2、...
  • 文本分类常用算法比较

    万次阅读 2015-04-03 16:10:48
    本文对文本分类中的常用算法进行了小结,比较它们之间的优劣,为算法的选择提供依据。  一、决策树(Decision Trees) 优点:  1、决策树易于理解和解释.人们在通过解释后都有能力去理解决策树所表达的意义。 ...
  • 文本数据的机器学习自动分类方法

    万次阅读 2017-10-08 21:45:00
    ...本文为第一部分,着重介绍文本预处理以及特征抽取的方法。...随着互联网技术的迅速发展与普及,如何对...而在这些数据中,文本数据又是数量最大的一。“文本分类是指在给定分类体系下,根据文本内容自动确
  • 作者丨杨敏单位丨中国科学院深圳先进技术研究院助理研究员研究方向丨自然语言处理文本建模方法大致可以分为两:(1)忽略词序、对文本进行浅层语义建模(代表模型包括 LDA,EarthMover’s distance等);...
  • 情感分析(Sentiment Analysis)是为一个文本分类问题,即使用判定给定的一段文本信息表达的情感属于积极情绪,还是消极情绪.本实践使用 IMDB 电影评论数据集,使用双向 LSTM 对电影评论进行情感分析.。
  • 1 文本分类 是自然语言处理(NLP)领域里一项基本任务。而文本呢的长度过长对文本智能解析带来了巨大的挑战。 用传统的监督学习模型对一段文文本进行分类的基本过程: 一段原始文本→(数据预处理)→处理后的...
  • 若该文为原创文章,未经允许不得转载 原博主博客地址:https://blog.csdn.net/qq21497936 ...本文章博客地址: 各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究 目录 前话 ...在文本小部...
  • 文本格式是什么意思

    千次阅读 2021-07-27 08:05:06
    一般来说,计算机可以分为文本文件和二进制文件两文本,是指书面语言的表现形式,从文学角度说,通常是具有完整、系统含义(Message)的一个句子或多个句子的组合。一个文本可以是一个句子(...
  • 常常在想,自然语言处理到底在做的是一件什么样的事情?到目前为止,我所接触到的NLP其实都是在做一件事情,即将自然语言转化为一种计算机能够理解的形式。这一点在知识图谱、信息抽取、文本摘要这...
  • 07.文本分析

    千次阅读 2021-01-02 19:07:59
    2、句子切分、分词、词性标注、语法分析(1)句子切分和分词(2)词性标注(3)语法树3、文本索引和检索4、文本分类(classification)(1)文本分类系统的主要功能模块(2)使用朴素贝叶斯算法实现文本分类(3)...
  • 【论文复现】使用RCNN进行文本分类

    千次阅读 2018-11-26 12:56:31
    继续之前的文本分类任务系列,前面介绍了几种:CNN,RNN,fasttext。今天的主角是RCNN 对了,顺带可以了解一下RNN与CNN在NLP中的区别于共同点,这个问题在昨天面试中也被问到了。这也是今天这篇论文提出的原因之一吧...
  • 信息技术的分类方法有哪些?

    千次阅读 2019-03-04 11:32:09
    信息技术是指各种已经或即将转化为信息设备的信息技术,这类信息技术有时就是指各种具体的实物形态的信息产品,如复印机、电话机、电子计算机等;软信息技术则是指那些不具明显物质承担者,但双是人类在长期从事...
  • 城市轨道交通六号线一工程乘客信息系统(PIS)是依托多媒体网络技术, 以计算机系统为核心,通过设置在站厅、站台、列车客室的显示终端,让乘客及 时准确地了解列车运营信息和公共媒体信息的多媒体综合信息系统;...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 555,060
精华内容 222,024
热门标签
关键字:

信息类文本的功能