python 中文文本分类(fudan)

一,中文文本分类流程:

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的样例代码:

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,中文分词。

本小节的目标是:
1,告诉你中文分词的实际操作。

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 模型  

[plain] view plain copy 
jieba.cut_for_search 方法接受两个参数:需要分词的字符串;是否使用 HMM 模型。该方法适合用于搜索引擎构建倒排索引的分词,粒度比较细  

[plain] view plain copy 
待分词的字符串可以是 unicode 或 UTF-8 字符串、GBK 字符串。注意:不建议直接输入 GBK 字符串,可能无法预料地错误解码成 UTF-8  

[plain] view plain copy 
jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode),或者用  

[plain] view plain copy 
jieba.lcut 以及 jieba.lcut_for_search 直接返回 list  

[plain] view plain copy 
jieba.Tokenizer(dictionary=DEFAULT_DICT) 新建自定义分词器,可用于同时使用不同词典。jieba.dt 为默认分词器,所有全局分词相关函数都是该分词器的映射。  

2.2.2

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

./train_corpus_seg/和./test_corpus_seg/

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

Created on Thu Oct 12 15:10:45 2017

@author: user
"""

import sys  
import os  
import jieba  
# 保存至文件  
def savefile(savepath, content):  
    with open(savepath, "wb") as fp:  
        fp.write(content)  
    ''''' 
    上面两行是python2.6以上版本增加的语法,省略了繁琐的文件close和try操作 
    2.5版本需要from __future__ import with_statement 
    '''  
# 读取文件  
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 = str(content).replace("\r\n", "")  # 删除换行  
            content = content.replace(" ", "")#删除空行、多余的空格  
            content_seg = jieba.cut(content)  # 为文件内容分词  
            savefile(seg_dir + file_path, " ".join(content_seg).encode())  # 将处理后的文件保存到分词后语料目录  
  
    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中的字典。你往里面传什么,它就存什么。

好了,解释完了。

是不是很简单?

在本篇博文中,你对Bunch能够有这种层次的理解,就足够了。如果你想继续详细透彻的理解Bunch,请见博主的另一篇博文《暂时还没写,写完在这里更新链接》

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

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类形:

# -*- coding: utf-8 -*-
"""
Created on Thu Oct 12 15:11:42 2017

@author: user
"""

import os#python内置的包,用于进行文件目录操作,我们将会用到os.listdir函数  
import pickle as pickle#导入cPickle包并且取一个别名pickle  
''''' 
事实上python中还有一个也叫作pickle的包,与这里的名字相同了,无所谓 
关于cPickle与pickle,请参考博主另一篇博文: 
python核心模块之pickle和cPickle讲解 
python核心模块之pickle和cPickle讲解 - CSDN博客 
本文件代码下面会用到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数据类型,实现了数据集的变量表示。事实上在第2节中,我们通过分词,已经将每一个文本文件表示为了一个词向量了。也许你对于什么是词向量并没有清晰的概念,这里有一篇非常棒的文章《Deep Learning in NLP (一)词向量和语言模型》,简单来讲,词向量就是词向量空间里面的一个向量。

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

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

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

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

但是在我们第2节处理的这些文件中,词向量之间的单词个数并不相同,词向量的涵盖的单词也不尽相同。他们并不在一个空间里,换句话说,就是他们之间没有可比性,例如:

词向量1:我 喜欢
相国大人,对应的词向量空间是(我,喜欢,相国大人),可以表示为(1,1,1)

词向量2:她 不
喜欢 我,对应的词向量空间是(她,不,喜欢,我),可以表示为(1,1,1,1)

两个空间不一样

因此,接下来我们要做的,就是把所有这些词向量统一到同一个词向量空间中,例如,在上面的例子中,我们可以设置词向量空间为(我,喜欢,相国大人,她,不)

这样,词向量1和词向量2分别可以表示为(1,1,1,0,0)和(1,1,0,1,1),这样两个向量就都在同一个空间里面了。可以进行比较和各种运算了。

也许你已经发现了,这样做的一个很糟糕的结果是,我们要把训练集内所有出现过的单词,都作为一个维度,构建统一的词向量空间,即使是中等大小的文本集合,向量维度也很轻易就达到数十万维。为了节省空间,我们首先将训练集中每个文本中一些垃圾词汇去掉。所谓的垃圾词汇,就是指意义模糊的词,或者一些语气助词,标点符号等等,通常他们对文本起不了分类特征的意义。这些垃圾词汇我们称之为停用词。把所有停用词集合起来构成一张停用词表格,这样,以后我们处理文本时,就可以从这个根据表格,过滤掉文本中的一些垃圾词汇了。

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

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

下面的程序,目的就是要将训练集所有文本文件(词向量)统一到同一个词向量空间中。值得一提的是,在词向量空间中,事实上不同的词,它的权重是不同的,它对文本分类的影响力也不同,为此我们希望得到的词向量空间不是等权重的空间,而是不同权重的词向量空间。我们把带有不同权重的词向量空间叫做“加权词向量空间”,也有的技术文档将其称为“加权向量词袋”,一个意思。

现在的问题是,如何计算不同词的权重呢?

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]表示,第i个词在第j个类别中的IF-IDF值(看到这里,我估计你压根就没去看那篇文章,所以你可能到现在也不知道 这是个啥玩意儿。。。)

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

# -*- coding: utf-8 -*-
"""
Created on Thu Oct 12 15:14:11 2017

@author: user
"""

# 引入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就是这样一个索引字典 
   vocabulary={"我":0,"喜欢":1,"相国大人":2},你可以简单的理解为:vocabulary就是词向量空间的坐标轴,索引值相当于表明了第几 
   个维度。 
   我们现在就是要构建一个词向量空间,因此在初始时刻,这个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_ 

   他俩还有一个叫做vocabulary(后面没有下划线)的参数,这个参数和我们第45中讲到的意思是一样的。 
   那么vocabulary_和vocabulary的区别是什么呢? 
   vocabulary_:是CountVectorizer()和TfidfVectorizer()的内部成员,表示最终得到的词向量空间坐标 
   vocabulary:是创建CountVectorizer和TfidfVectorizer类对象时,传入的参数,它是我们外部输入的空间坐标,不写的话,函数就从 
   输入文档中自己构造。 
   一般情况它俩是相同的,不一般的情况没遇到过。 
   ''' 
#构建一个快乐地一步到位的玩意儿,专业一点儿叫做:使用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词向量空间上:

# -*- coding: utf-8 -*-
"""
Created on Thu Oct 12 15:14:11 2017

@author: user
"""
# 引入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节和刚才的代码几乎完全一样,因此我们可以将这两个代码文件统一为一个:

# -*- coding: utf-8 -*-
"""
Created on Thu Oct 12 15:14:11 2017

@author: user
"""

from sklearn.datasets.base import Bunch  
import pickle 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)   

对测试集进行了上述处理后,接下来的步骤,变得非常简单。

# -*- coding: utf-8 -*-
"""
Created on Thu Oct 12 15:15:46 2017

@author: user
"""

import pickle 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)  


print(metrics.classification_report(test_set.label, predicted))

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

6,评价与小结

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

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

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

2,文本分类算法的研究

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

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

https://github.com/sunxiangguo/chinese_text_classification

需要说明的是,在工程代码和本篇博文中,细心的你已经发现了,我们所有的路径前面都有一个点“.
/”,这主要是因为我们不知道您会将工程建在哪个路径内,因此这个表示的是你所在项目的目录,本篇博文所有路径都是相对路径。因此你需要自己注意一下。工程里面语料库是空的,因为上传资源受到容量的限制。你需要自己添加。

7,进一步的讨论:

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

那么,我们也许想问,有没有更好,更新的技术?答案是有的。未来,博主会集中介绍两种技术:

1.利用LDA模型进行文本分类

2.利用深度学习进行文本分类

利用深度学习进行文本分类,要求你必须对深度学习的理论有足够多的掌握。

为此,你可以参考博主的其他博文,

例如下面的这个系列博文《卷积神经网络CNN理论到实践》。

这是一些列的博文。与网上其他介绍CNN的博文不同的是:

  1. 我们会全方位,足够深入的为你讲解CNN的知识。包括很多,你之前在网上找了很多资料也没搞清楚的东西。
  2. 我们会利用CNN做文本分类的实践。
  3. 我们会绘制大量精美的示意图。保证博文的高质量和美观。

以上就是“python 中文文本分类(fudan)”的全部内容,希望对你有所帮助。

关于Python技术储备

学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。

在这里插入图片描述

二、Python必备开发工具

img

三、Python视频合集

观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

四、实战案例

光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

五、Python练习题

检查学习结果。

img

六、面试资料

我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

img

最后祝大家天天进步!!

上面这份完整版的Python全套学习资料已经上传至CSDN官方,朋友如果需要可以直接微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值