最近在做微信公众号采集开发,将要对公众号文章数据(mysql里),进行文本分类,查了很多资料,找到一篇适合新手的一篇博文:https://blog.csdn.net/github_36326955/article/details/54891204,本人提供了人工分好类的文章训练集语料库(1000多篇)和测试集语料库(11篇),和大家一起学习。
一、中文文本分类流程
1、预处理
2、中文分词
3、结构化表示--构建词向量空间
4、权重策略--TF-IDF
5、分类器选择
6、评价
二,具体细节
1,预处理
# 公众号文章类别
d_category = {'1': '人工智能', '2': '人文社科', '3': '信息系统', '4': '先进防御', '5': '兵器', '6': '农业', '7': '前沿交叉', '8': '反恐安全',
'9': '基础科学', '10': '教育', '11': '核生化', '12': '模拟仿真', '13': '海战装备', '14': '生物医学', '15': '电子元器件',
'16': '空战装备', '17': '管理与政策', '18': '经济', '19': '综合保障', '20': '网络空间', '21': '能原材料', '22': '航天',
'23': '试验鉴定', '24': '防务策略', '25': '其他', '26': '太空探索', '27': '招标采购', '28': '区块链', '29': '地理科学'
}
1.1,得到训练集语料库
即已经分好类的文本资料(例如:语料库里是一系列已经分词的txt文章,这些文章按照实际内容归入到不同分类的目录中,为了方便演示,如上所有分类都用数字代表,如 .\1\21.txt),下载链接:https://pan.baidu.com/s/1NgkcztR0d-QPRn9toM1wCA 提取码: dw8h ,将下载的语料库解压后,请自己修改文件名和路径,例如路径可以设为 ./train_corpus/,其下则是各个类别目录如:./train_corpus/1,……,\train_corpus\2
1.2,得到测试语料库
也是已经分好类的文本资料,与1.1类型相同,只是里面的文档不同,用于检测算法的实际效果。下载链接: https://pan.baidu.com/s/18P8hkOEvughxJ9B8M3fbHg 提取码: wqjj ,测试预料可以从1.1中的训练预料中随机抽取,也可以下载独立的测试语料库路径修改参考1.1,上面提供的是独立的测试语料,例如可以设置为 ./test_corpus/
2,中文分词
本文使用的分词工具是jieba,最近新出来的一个分词工具pkuseg。两者区别参考:流程是从mysql读取数据——结巴分词——按分类文件夹存入txt。截止目前,我们已经得到了分词后的训练集语料库和测试集语料库,下面我们要把这两个数据集表示为变量,从而为下面程序调用提供服务。我们采用的是Scikit-Learn库中的Bunch数据结构来表示这两个数据集。
首先来看看Bunch:
Bunch这玩意儿,其实就相当于python中的字典。你往里面传什么,它就存什么。
好了,解释完了。
是不是很简单?
接下来,让我们看看的我们的数据集(训练集)有哪些信息:
image.png
那么,用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类形:
# -*- coding: UTF-8 -*-
import os
import pickle
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 = Bunch(target_name=[], label=[], filenames=[], contents=[])
bunch.target_name.extend(catelist)
# 获取每个目录下所有的文件
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)
if __name__ == "__main__": # 这个语句前面的代码已经介绍过,今后不再注释
# 对训练集进行Bunch化操作:
wordbag_path = "train_word_bag/train_set.dat" # Bunch存储路径
seg_path = "train_corpus/" # 分词后分类语料库路径
corpus2Bunch(wordbag_path, seg_path)
# 对测试集进行Bunch化操作:
wordbag_path = "test_word_bag/test_set.dat" # Bunch存储路径
seg_path = "test_corpus/" # 分词后分类语料库路径
corpus2Bunch(wordbag_path, seg_path)
3,结构化表示--向量空间模型
在第2节中,我们对原始数据集进行了分词处理,并且通过绑定为Bunch数据类型,实现了数据集的变量表示。
4,权重策略--TF-IDF
我们把训练集文本转换成了一个TF-IDF词向量空间,姑且叫它为A空间吧。那么我们还有测试集数据,我们以后实际运用时,还会有新的数据,这些数据显然也要转到词向量空间,那么应该和A空间为同一个空间吗?
是的。
即使测试集出现了新的词汇(不是停用词),即使新的文本数据有新的词汇,只要它不是训练集生成的TF-IDF词向量空间中的词,我们就都不予考虑。这就实现了所有文本词向量空间“大一统”,也只有这样,大家才在同一个世界里。才能进行下一步的研究。
下面的程序就是要将训练集所有文本文件(词向量)统一到同一个TF-IDF词向量空间中(或者叫做用TF-IDF算法计算权重的有权词向量空间)。这个词向量空间最终存放在train_word_bag/tfdifspace.dat中。
把训练集数据成功的构建了一个TF-IDF词向量空间,空间的各个词都是出自这个训练集(去掉了停用词)中,各个词的权值也都一并保存了下来,叫做权重矩阵。
需要注意的是,权重矩阵是一个二维矩阵,a[i][j]表示,第j个词在第i个类别中的IF-IDF值
# 引入Bunch类
from sklearn.datasets.base import Bunch
import 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)
上面的代码运行之后,会将训练集数据转换为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词向量空间上:
import 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 file_name, expct_cate in zip(test_set.filenames, predicted):
# if flabel != expct_cate:
print(file_name, " -->预测类别:", expct_cate)
print("预测完毕!!!")
结果:
image.png
6,评价与小结
评价部分的实际操作我们已经在上一节的代码中给出了。这里主要是要解释一下代码的含义,以及相关的一些概念。
截止目前,我们已经完成了全部的实践工作。接下来,你或许希望做的是:
1,分词工具和分词算法的研究
2,文本分类算法的研究