一、中文文本分类流程:

1. 预处理

2. 中文分词

3. 结构化表示-构建词向量空间

4.权重策略-TF-IDF

5. 分类器

6. 评价

二、具体细节

1.预处理

    1.1. 得到训练集语料库

    本文采用复旦中文文本分类语料库,下载链接:

    1.2 得到测试集语料库

    同样采用复旦中文文本分类语料库,下载链接:

2. 中文分词

    第1小节预处理中的语料库都是没有分词的原始语料(即连续的句子,而后面的工作需要我们把文本分为一个个单词),现在需要对这些文本进行分词,只有这样才能在基于单词的基础上,对文档进行结构化表示。中文分词有其特有的难点,最终完全解决中文分词的算法是基于概率图模型的条件随机场(CRF)。中文分词的工具有很多,但是比较著名的几个都是基于java的,这里推荐python的第三方库jieba(所采用的算法就是条件随机场)。通过pip安装jieba:打开cmd,切换到Python所在目录下,执行命令:pip install jieba    然后通过Python编程,将训练语料库和测试语料库进行分词,分词后保存的路径可以自己设置。

#!/usr/bin/env python  
# -*- coding: UTF-8 -*-  
 
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 
    '''  
# 读取文件  
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__": 
简单来说如果其他python文件调用这个文件的函数,或者把这个文件作为模块 
导入到你的工程中时,那么下面的代码将不会被执行,而如果单独在命令行中 
运行这个文件,或者在IDE(如pycharm)中运行这个文件时候,下面的代码才会运行。 
即,这部分代码相当于一个功能测试。 
 
'''  
if __name__=="__main__":
    #对训练集进行分词  
    corpus_path = "D:/work/train/train/"  # 未分词分类语料库路径  
    seg_path = "D:/work/train/train/train_corpus_seg/"  # 分词后分类语料库路径  
    corpus_segment(corpus_path,seg_path)  
  
    #对测试集进行分词  
    corpus_path = "D:/work/test/test/"  # 未分词分类语料库路径  
    seg_path = "D:/work/test/test/test_corpus_seg/"  # 分词后分类语料库路径  
    corpus_segment(corpus_path,seg_path)

3. 结构化表示-构建词向量空间

现在我们得到了分词后的训练集语料库和测试集语料库,下面我们要把这两个数据集表示为变量,从而为下面的程序调用提供服务。采用Scikit-Learn库中的Bunch数据结构来表示这两个数据集。

Bunch就相当于python中的字典。你往里面传什么,它就存什么。就相当于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 import base
bunch = base.Bunch(target_name=[], label=[], filenames=[], contents=[])

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

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

Bunch:

python 文本分割工具 python文本分类_文本文件

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

#!/usr/bin/env python  
# -*- coding: UTF-8 -*-  

import sys
reload(sys) # Python2.5 初始化后删除了 sys.setdefaultencoding 方法,我们需要重新载入 
sys.setdefaultencoding('utf-8') 
import os#python内置的包,用于进行文件目录操作,我们将会用到os.listdir函数  
import cPickle as pickle#导入cPickle包并且取一个别名pickle  
''''' 
事实上python中还有一个也叫作pickle的包,与这里的名字相同了,无所谓 
本文件代码下面会用到cPickle中的函数cPickle.dump 
'''
from sklearn.datasets import base  #一定是这样写的,下面注释的一行是错误示范
#from sklearn.datasets.base import Bunch  
 

  
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 = base.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 "构建文本对象结束!!!"  
  

wordbag_path = "D:/work/train/train_word_bag/train_set.dat"  # Bunch存储路径  
seg_path = "D:/work/train/train_corpus_seg/"  # 分词后分类语料库路径  
corpus2Bunch(wordbag_path, seg_path)  
  
    # 对测试集进行Bunch化操作:  
wordbag_path = "D:/work/test/test_word_bag/test_set.dat"  # Bunch存储路径  
seg_path = "D:/work/test/test_corpus_seg/"  # 分词后分类语料库路径  
corpus2Bunch(wordbag_path, seg_path)

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

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

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

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

4.权重策略-TF-IDF

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

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

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

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

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

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

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 = "D:/work/train/train_word_bag/hlt_stop_words.txt"  
    bunch_path = "D:/work/train/train_word_bag/train_set.dat"  
    space_path = "D:/work/train/train_word_bag/tfdifspace.dat"  
    vector_space(stopword_path,bunch_path,space_path)  
  
    bunch_path = "D:/work/test/test_word_bag/test_set.dat"  
    space_path = "D:/work/test/test_word_bag/testspace.dat"  
    train_tfidf_path="D:/work/train/train_word_bag/tfdifspace.dat"  
    vector_space(stopword_path,bunch_path,space_path,train_tfidf_path)

5. 分类器

这里我们采用的是朴素贝叶斯分类器

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

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 = "D:/work/train/train_word_bag/tfdifspace.dat"
train_set = _readbunchobj(trainpath)

# 导入测试集
testpath = "D:/work/test/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)

6.评价与小结

在实际操作过程中可以通过调节TF-IDF的阈值提高精确度或者使用其他的分类算法