前言
这是之前的那个文章分类系统的完全重构的版本,之前那个预测的准确率不太行,为了让项目运行地更完善,决定通过训练 nlp 模型来更加精确地预测文章所属的类别,数据集是我们用爬虫精选的,模型也是通过训练集训练的,准确率能够达到 0.96,数据集的话就不分享了
贝叶斯原理
贝叶斯公式
$$
P(B_{i}|A) = \frac{P(B_{i})P(A|B_{i})}{\sum_{j=1}^{n}P(B_{j})P(A|B_{j}))}
$$
朴素贝叶斯分类器
朴素贝叶斯是一个概率分类器,它通过计算两个不同类别 X 和 Y 之间的所属同类的可能性来进行类别的预测估计,P(Y|X)为 Y 的后验概率,P(Y)为 Y 的先验概率
将一系列训练数据通过贝叶斯公式进行计算,得出一个个 P(Y|X)后验概率,分类时,对于实例 x,比较所有的 P(Y|x),最大的概率的 Y 即为实例 x 的分类
后验概率:
$$
P(Y|X) = \frac{P(Y)P(X|Y)}{P(X)}
$$
P(Y)可以通过数据集进行估计,P(X)为常数,在比较中可以忽略
文本分类算法
朴素贝叶斯分类器是一种有监督的分类器,即需要给出训练集并根据需要打好标签
对于每一个训练样本,均生成一个属性向量,各个属性相互独立,如文章的文本特征向量
计算 P(x|Y)条件概率,P(x|Y)= 包含此单词的文章总数 / 该类别下的文章数量
根据贝叶斯公式求出 P(Y|X),预测文本分类
具体实现
预处理
语料库可以根据需要自己生成,本文代码是基于复旦大学语料库的格式基础写的
复旦大学语料库:http://download.csdn.net/detail/github_36326955/9747927
语料库均为已分类好的文章作为预处理数据
中文分词
采用的是 jieba 分词
jieba.cut
方法接受四个输入参数: 需要分词的字符串;cut_all 参数用来控制是否采用全模式;HMM 参数用来控制是否使用 HMM 模型;use_paddle 参数用来控制是否使用 paddle 模式下的分词模式,paddle 模式采用延迟加载方式,通过 enable_paddle 接口安装 paddlepaddle-tiny,并且 import 相关代码;
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
为默认分词器,所有全局分词相关函数都是该分词器的映射。
curpus_segment:
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
30
31
32
33
34
35
36
37
38
39
40
|
# -*- coding: UTF-8 -*-
import os
import jieba
def readfile(path): # 读取文件
with open(path, "rb") as fp:
content = fp.read()
return content
def savefile(path, content): # 保存文件
with open(path, "wb") as fp:
fp.write(content)
def corpus_segment(corpus_path, corpus_seg_path):
catelist = os.listdir(corpus_path) # 获取 corpus_path 下所有子目录
for cate in catelist:
cate_path = corpus_path + cate + '/'
cate_seg_path = corpus_seg_path + cate + '/'
if not os.path.exists(cate_seg_path): # 不存在分词目录则创建
os.makedirs(cate_seg_path)
textlist = os.listdir(cate_path) # 获取一个分类下的所有文本
for text in textlist:
text_path = cate_path + text
content = readfile(text_path)
content = content.replace('\r\n'.encode('utf-8'), ''.encode('utf-8')).strip() # 删除换行
content = content.replace(' '.encode('utf-8'), ''.encode('utf-8')).strip() # 删除空行、多余的空格
content_seg = jieba.cut(content) # 分词
savefile(cate_seg_path + text, ' '.join(content_seg).encode('utf-8'))
print("分词结束")
if __name__ == "__main__":
# 对训练集进行分词
corpus_path = "train_corpus/" # 未分词已分类的语料库
corpus_seg_path = "train_corpus_seg/" # 已分词已分类的语料库
corpus_segment(corpus_path, corpus_seg_path)
# 对测试集进行分词
corpus_path = "test_corpus/" # 未分词已分类的语料库
corpus_seg_path = "test_corpus_seg/" # 已分词已分类的语料库
corpus_segment(corpus_path, corpus_seg_path)
|
Bunch 类型转换
对于每个分词的数据集,为了把它们变成一个个变量,可以转换为 Bunch 类型
corpus_to_Bunch:
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
30
31
32
33
34
35
36
|
# -*- coding: UTF-8 -*-
import os
import pickle
from sklearn.datasets._base import Bunch
def readfile(path): # 读取文件
with open(path, "rb") as fp:
content = fp.read()
return content
def corpus2Bunch(wordbag_path, seg_path):
catelist = os.listdir(seg_path) # 获取分类信息
bunch = Bunch(target_name = [], label = [], filenames = [], contents = [])
bunch.target_name.extend(catelist) # 填充 bunch 中的 tatget_name list
for cate in catelist:
cate_path = seg_path + cate + '/'
textlist = os.listdir(cate_path)
for text in textlist:
text_path = cate_path + text
bunch.label.append(cate)
bunch.filenames.append(text_path)
bunch.contents.append(readfile(text_path))
with open(wordbag_path, "wb") as fp:
pickle.dump(bunch, fp)
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)
|
创建 TF-IDF 词向量空间模型
数据集变成变量了,接下来是结构化的过程,我们需要把所有的词向量放入一个词向量空间当中,这里我们采用的是 TF-IDF 的权重策略,生成一个个权重矩阵,并存入文件中,tfdifspace.dat 即生成的实例文件,里面包括词向量的空间坐标和 TF-IDF 的权重矩阵
TFIDF_space:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
# -*- coding: UTF-8 -*-
import pickle
from sklearn.datasets._base import Bunch
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 fp:
bunch = pickle.load(fp)
return bunch
def writebunchobj(path, bunchobj):
with open(path, "wb") as fp:
pickle.dump(bunchobj, fp)
def vector_space(stopword_path, bunch_path, space_path, train_tfidf_path = None):
stopwordlist = readfile(stopword_path).splitlines()
bunch = readbunchobj(bunch_path)
tfidfspace = Bunch(target_name = bunch.target_name, label = bunch.label, filenames = bunch.filenames, tdm = [], vocabulary = {})
'''
tdm TF-IDF矩阵
vocabulary 词典索引
'''
if train_tfidf_path is not None:
trainbunch = readbunchobj(train_tfidf_path)
tfidfspace.vocabulary = trainbunch.vocabulary
vectorizer = TfidfVectorizer(stop_words = stopwordlist, sublinear_tf = True, max_df = 0.5, vocabulary = trainbunch.vocabulary)
'''
sublinear_tf 计算tf值采用亚线性策略
max_df 设置阙值,达到直接加入临时停用词
'''
tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
else:
vectorizer = TfidfVectorizer(stop_words = stopwordlist, sublinear_tf = True, max_df = 0.5)
tfidfspace.tdm = vectorizer.fit_transform(bunch.contents)
tfidfspace.vocabulary = vectorizer.vocabulary_ # vocabulary_ 向量空间坐标轴信息
writebunchobj(space_path, tfidfspace)
print("tf-idf词向量空间实例创建成功!!!")
if __name__ == "__main__":
stopword_path = "train_word_bag/stopwords.txt"
bunch_path = "train_word_bag/train_set.dat"
space_path = "train_word_bag/tfidfspace.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/tfidfspace.dat"
vector_space(stopword_path, bunch_path, space_path, train_tfidf_path)
|
贝叶斯分类器预测
NBayes_Predict:
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
30
31
32
33
34
|
# -*- coding: UTF-8 -*-
import pickle
from sklearn.naive_bayes import MultinomialNB # 导入多项式贝叶斯算法
from sklearn import metrics
def readbunchobj(path):
with open(path, "rb") as fp:
bunch = pickle.load(fp)
return bunch
# 导入训练集
trainpath = "train_word_bag/tfidfspace.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("预测完毕!!!")
# 计算分类精度:
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)
|
总结
整体来说流程比较清晰,数据可观,生成的模型预测率较准,不过需要大量的数据支持而且每类分类总数,文章字数应相当,不然会造成文章多或者文章字数多的概率大,导致大量文章预测错误
本项目文章源代码在 Github 上已开源:Github 源码
参考文章:
基于朴素贝叶斯的文本分类算法
python 中文文本分类