从分类算法层面来看,各类语言的文本分类技术大同小异,但从整个流程来考察,不同语言的文本处理所用到的技术还是有差别的。下面给出中文语言的文本分类技术和流程,主要包括以下几个步骤:
(1)预处理:去除文本的噪声信息,例如HTML标签、文本格式转换、检测句子边界等。
(2)中文分词:使用中文分词器为文本分词,并去除停用词。
(3)构建词向量空间:统计文本词频,生成文本的词向量空间。
(4)权重策略——TF-IDF方法:使用TF-IDF发现特征词,并抽取为反映文档主题的特征。
(5)分类器:使用算法训练分类器。
(6)评价分类结果:分类器的测试结果分析。
文本处理的核心任务是要把非结构化和半结构化的文本转换为结构化的形式,即向量空间模型。文本预处理的基本步骤如下:
选择适当的范围取决于文本挖掘任务的目标:对于分类或聚类的任务,往往把整个文档作为处理单位;对于情感分析、文档自动文摘或信息检索,段落或章节更合适。
文本分类中所说的文本语料一般分为两大类。
1)训练集语料
训练集语料是指已经分好类的文本资源。中文文本分类语料库下载地址为:http://www.threedweb.cn/thread-1288-1-1.html。
本项目未分词训练语料库路径为Root\train_corpus_small\;
测试语料库路径为Root\test_corpus\。
2)测试集语料
所谓测试集就是待分类的文本语料,可以是训练集的一部分,也可以是外部来源的文本语料。一般批量获取网络文本需要使用网络爬虫下载。
不同格式的文本不论采用何种处理形式,都要统一转换为纯文本文件,例如,网页文本、Word或PDF文件都要转换为纯文本格式。
以网页文本为例,一般Python去除HTML标签,较多地使用lxml库,这是一个C语言编写的XML扩展库,适用于海量的网络文本格式转换。
样例代码:
from lxml import etree,html
#HTML文件路径,以及读取文件
path = "1.htm"
content = open(path, "rb").read()
page = html.document_fromstring(content) #解析文件
text = page.text_content() #去除所有标签
print(text) #输出去除标签后的解析结果
句子边界检测是分解整个文档,并转换成单独句子的过程。对于中文文本,它就是寻找“。”、“?”或“!”等标点符号作为断句的依据。
中文分词指的是将一个汉字序列(句子)切分成一个个单独的词。分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。中文分词是中文自然语言处理的核心问题之一。
分词是自然语言处理中最基本、最底层的模块,分词精度对后续应用模块影响很大。
文本的结构化表示简单分为四大类:词向量空间模型、主题模型、依存句法的树表示、RDF的图表示。
本项目使用jieba分词、它是专门使用Python语言开发的分词系统,占用资源较小,常识类文档的分词精度较高,对于非专业文档绰绰有余。
jieba分词简单的样例代码:
import jieba
seg_list = jieba.cut("小明1995年毕业于北京清华大学", cut_all = False)
print("Default Mode:", " ".join(seg_list)) #默认切分
seg_list = jieba.cut("小明1995年毕业于北京清华大学")
print(" ".join(seg_list))
seg_list = jieba.cut("小明1995年毕业于北京清华大学", cut_all = True)
print("Full Mode:", "/".join(seg_list)) #全切分
#搜索引擎模式
seg_list = jieba.cut_for_search("小明硕士毕业于中国科学院计算所,后在日本京都大学深造")
print("/".join(seg_list))
输出结果:
Default Mode: 小明 1995 年 毕业 于 北京 清华大学
小明 1995 年 毕业 于 北京 清华大学
Full Mode: 小/明/1995/年/毕业/于/北京/清华/清华大学/华大/大学
小明/硕士/毕业/于/中国/科学/学院/科学院/中国科学院/计算/计算所/,/后/在/日本/京都/大学/日本京都大学/深造
本项目中创建分词后,语料路径为Root\train_corpus_seg\。
import sys
import os
import jieba
#定义两个函数用于读取和保存文件
def savefile(savepath, content): #保存文件
fp = open(savepath, "wb")
fp.write(content)
fp.close()
def readfile(path): #读取文件
fp = open(path, "rb")
content = fp.read()
fp.close()
return content
#主程序
corpus_path = "train_corpus_small/" #未分词分类语料库路径
seg_path = "train_corpus_seg/" #分词后分类语料库路径
catelist = os.listdir(corpus_path) #获取corpus_path下的所有子目录
#获取每个目录下的所有文件
for mydir in catelist:
class_path = corpus_path+mydir+"/" #拼出分类子目录的路径
seg_dir = seg_path + mydir + "/" #拼出分词后的语料分类目录
if not os.path.exists(seg_dir): #是否存在目录,如果没有则创建
os.makedirs(seg_dir)
file_list = os.listdir(class_path) #获取类别目录下的所有文件
for file_path in file_list: #遍历类别目录下的文件
fullname = class_path + file_path #拼出文件名全路径
content = readfile(fullname).strip() #读取文件内容
content = content.replace("\r\n", "").strip() #删除换行和多余的空格
conten_seg = jieba.cut(content) #为文件内容分词
#将处理后的文件保存到分词后的语料目录
savefile(seg_dir + file_path, "".join(content_seg))
print("中文语料分词结束!!!")
在实际应用,为了后续生成向量空间模型的方便,这些分词后的文本信息还要转换成 文本向量信息并对象化。这里需要引入一个Scikit-Learn库的Bunch数据结构。
from sklearn.datasets.base import Bunch #导入Bunch类
#Bunch类提供一种key,value的对象形式
#target_name:所有分类名称列表
#label:每个文件的分类标签列表
#filenames:文件路径
#contents:分词后文件词向量形式
bunch = Bunch(target_name = [], label = [], filenames = [], contents = [])
wordbag_path = "train_word_bag/train_set.dat" #分词语料Bunch对象持久化文件路径
seg_path = "train_corpus_seg/" #分词后分类语料库路径
catelist = os.listdir(seg_path)
bunch.target_name.extend(catelist) #将类别信息保存到Bunch对象中
for mydir in catelist:
class_path = seg_path + mydir + "/"
file_list = os.listdir(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).strip()) #保存文件词向量
#Bunch对象持久化
file_obj = open(wordbag_path, "wb")
pickle.dump(bunch, file_obj)
file_obj.close()
print("构建文本对象结束!!!")
这样就在目录下生成了一个train_set.dat文件。此文件保存所有训练集文件的所有类信息,以及每个文件的文件名、文件所属分类和词向量。
向量空间模型把文本表示为一个向量,该向量的每个特征表示为文本中出现的每个词。通常,把训练集中出现的每个不同的字符串都作为一个维度。
由于文本在存储为向量空间时维度比较高,为节省存储空间和提高搜索效率。在文本分类之前会自动过滤掉某些字或词,这些字或词即被称为停用词。
读取停用词列表代码如下:
#读取文件
#1.读取停用列表
stopword_path = "train_word_bag\hlt_stop_words.txt"
stpwrdlst = readfile(stopword_path).splitlines()
TG-IDF的含义是词频逆文档频率,其含义是:如果某个词语或短语在一篇文章中出现的频率很高,并且在其他文章中很少出现,则认为此词或短语具有很好的类别区分能力,适合用来分类。TF-IDF的假设是,高频率应该具有较高的权重,除非它也是高文档频率。逆文档频率就是使用词条的文档频率来抵消该词的词频对权重的影响,从而得到一个较低的权重。词频和逆向文件频率读者自行百度吧。
某一特定文件内的高词语频率,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。
代码实现:
import sys
import os
from sklearn.datasets.base import Bunch #引入Bunch类
import cPickle as pickle #引入持久化类
from sklearn import feature_extraction
from sklearn.feature_extraction.text import TfidTransformer #TF-IDF向量转换类
from sklearn.feature_extraction.text import TfidVectorizer #TF-IDF向量生成类
#读取和写入Bunch对象函数
def readbunchobj(path):
file_obj = opoen(path, "rb")
bunch = pickle.load(file_obj)
file_obj.close()
return bunch
#写入Bunch对象
def writebunchobj(path, bunchobj):
file_obj = open(path, "wb")
pickle.dump(bunchobj, file_obj)
file_obj.close()
#从训练集生成TF-IDF向量词袋
#2. 导入分词后的词向量Bunch对象
path = "train_word_bag/train_set.dat" #词向量空间保存路径
bunch = readbunchobj(path)
#3. 构建TF-IDF词向量空间对象
tfidfspace = Bunch(target_name = bunch.target_name, label = bunch.label, filenames = bunch.filenames, tdm = [], vocabulary = {})
#4. 使用TfidfVectorizer初始化向量空间模型
vectorizer = TfidfVectorizer(stop_words = stpwrdlst. sublinear_tf = True, max_df = 0.5)
transformer = TfidfTransformer() #该类会统计每个词语的TF-IDF权值
#文本转为词频矩阵, 单独保存字典文件
tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
tfidfspace.vocabulary = vectorizer.vocabulary_
#持久化TF-IDF向量词袋
#5. 创建词袋的持久化
space_path = "train_word_bag/tfdifspace.dat" #词向量词袋保存路径
writebunchobj(space_path, tfidfspace)
最常用的文本分类方法有KNN最近邻算法、朴素贝叶斯算法和支持向量机算法。一般而言,KNN最近邻算法的原理最简单,分类精度尚可,但是速度慢;朴素贝叶斯算法对于短文本分类的效果最好,精度很高;支持向量机算法的优势是支持线性不可分的情况,精度上取中。
代码如下:
#2. 导入分词后的词向量Bunch对象
path = "test_word_bag/test_set.dat" #词向量空间保存路径
bunch = readbunchobj(path)
#3. 构建测试集TF-IDF向量空间
testspace = Bunch(target_name = bunch.target_name. label = bunch.label, filenames = bunch.filenames, tdm = [], vocabulary = {})
#4. 导入训练集的词袋
trainbunch = readbunchobj("train_word_bag/tfdifspace.dat")
#5. 使用TfidfVectorizer初始化向量空间模型
vectorizer = TfidfVectorizer(stop_words = stpwrdlst, sublinear_tf = True, max_df = 0.5, vocabulary = trainbunch.vocabulary) #使用训练集词袋向量
trainsformer = TfidfTransformer()
testspace.tdm = vectorizer.fit_transform(bunch.contents)
testspace.vocabulary = trainbunch.vocabulary
#创建词袋的持久化
space_path = "test_word_bag/testspace.dat" #词向量空间保存路径
writebunchobj(space_path, testspace)
执行多项式贝叶斯算法进行测试文本分类,并返回分类精度。
from sklearn.naive_bayes import MultinomialNB #导入多项式贝叶斯算法包
#导入训练集向量空间
trainpath = "train_word_bag/tfdifspace.dat"
train_set = readbunchobj(trainpath)
#导入测试集向量空间
testpath = "test_word_bag/testspace.dat"
test_set = readbunchobj(testpath)
#应用朴素贝叶斯算法
#alpha:0.01 alpha越小,迭代次数越多,精度越高
clf = MultinomialNB(alpha = 0.01).fit(train_set.tdm, train_set.label)
#预测分类结果
predicted = clf.predict(test_set.tdm)
total = len(predicted)
rate = 0
for flabel,file_name, expct_cate in zip(test_set.label, test_set.filenames, predicted):
if flabel != expct_cate:
rate += 1
print(file_name, ": 实际类别:", flable, "-->预测类别:", expct_cate)
#精度
print("error rate:", float(rate) * 100 / float(total), "&")
机器学习领域的算法评估有三个基本指标。
(1)召回率 = 系统检索到的相关文件/ 系统所有相关的文件总数
(2)准确率 = 系统检索到的相关文件 / 系统所有检索到的文件总数
(3)F-Score = (1+β2)⋅(Precision⋅Recall / β2⋅Precision+Recall)
文本分类项目的分类结果评估,代码如下:
import numpy as np
from sklearn import metrics
#定义分类精度函数
def metrics_result(actual, predict):
print('精度:{0:.3f}'.format(metrics.precision_score(actual, predict)))
print('召回:{0:.3f}'.format(metrics.recall_score(actual, predict)))
print('f1-score: {0:.3f}'.format(metrics.f1_score(actual, predict)))
metrics_result(test_set.label, predicted)