基于multinomial-nb的文本分类
一、数据处理
1. 数据获取
搜狗实验室 搜狐新闻数据 下载地址:http://www.sogou.com/labs/resource
2. 数据预处理
下载后数据为GBK格式,使用UltraEdit转码为UTF8格式,再整理为json方便处理
接着,由url前缀对应的新闻类型得到分类,共得到15类,取分布均匀的11个类,如下
在对每个新闻取前100字,并通过jieba分词,并去掉词性为‘x’的词(标点),得到最终数据
(1)新闻文本数据,每行 1 条新闻,每条新闻由若干个词组成,词之间以空格隔开,训练文本 17600 行,测试文本 4324 行;
(2)新闻标签数据,每行 1 个数字,对应这条新闻所属的类别编号,训练标签 17600行,测试标签 4324 行
二、离散型朴素贝叶斯——multinomial-nb算法
1. 文本识别中的NB
对于文本分类任务,其输入是一组文档,输出则是文档的类型,而朴素贝叶斯在文本分类任务中表现得很出色。NB是一个概率分类器,他告诉我们结果在类中的可能性,也就是对每一个元素,我们使用公式计算其概率,并取最高的概率所对应的类作为其分类结果。
初始公式如下:
先验概率P(c)被计算为训练数据在c类中的文档所占的百分比。
(公式1)
其中Nc是数据中具有c类的文档总数,而Ndoc是文档总数。
由于文档d被表示为一组特征(词语)w,所以类中每个单词的初始似然公式为:
即结果为类别c中给定词语的计数与类别c中所有单词的比
然而,如果类别c中没有给定的词语,那么该概率将等于0,而最后我们要将所有的特征似然相乘,就会导致整个类别的概率也为0。因此,采用拉普拉斯平滑技术,公式如下:(公式2)
其中V是我们所有词语的集合,由于每个概率值很小(比如0.0001)若干个很小的概率值直接相乘,得到的结果会越来越小。为了避免计算过程出现下溢(underflower),引入对数函数Log,在 log space中进行计算。然后使用词袋模型的每个单词wi 出现频率作为特征,得到如下公式(公式3)
2. 训练朴素贝叶斯分类器
训练朴素贝叶斯的过程其实就是计算先验概率和似然函数的过程。
2.1 先验概率P?的计算
P?的意思是:在所有的文档中,类别为c的文档出现的概率有多大?假设训练数据中一共有Ndoc篇文档,只要数一下类别c的文档有多少个就能计算p?了,类别c的文档共有Nc篇,先验概率的计算公式如下:
关于先验信息理解,可参考:这篇文章。
2.2 似然函数P(wi|c)的计算
由于是用词袋模型表示一篇文档d,对于文档d中的每个单词wi,找到训练数据集中所有类别为c的文档,数一数 单词wi在这些文档(类别为c)中出现的次数:count(wi,c)
然后,再数一数训练数据集中类别为c的文档一共有多少个单词.计算 二者之间的比值,就是似然函数的值。似然函数计算公式如下:
其中V,就是词库。(有些单词在词库中,但是不属于类别C,那么 count(w,c)=0)
注意,实际训练还需要使用拉普拉斯平滑。
3. 代码实现
下面,我将从代码运行顺序进行代码实现。
3.1 执行函数
class Implementation():
'''
执行函数
accuary:计算正确率
main:主函数
readfile:读取数据集
'''
def __init__(self):
self.labels = dict()
@staticmethod
def accuracy(prediction, test):
acc = 0
test_list = list(test)
for idx, result in enumerate(prediction):
if result == test_list[idx]:
acc += 1
return acc / len(test)
def main(self):
x_train, y_train, x_test, y_test = self.readFile()
nb = MultinominalNB()
"""
save:保存训练模型
cached:读取训练模型
"""
nb.fit(x_train, y_train, save=True)
predictions = nb.predict(x_test, cached=False)
print('Accuracy: ', self.accuracy(predictions, y_test))
def readFile(self):
x_train = open('mydata/train_contents.txt', encoding="utf-8").read().split('\n')
y_train = open('mydata/train_labels.txt', encoding="utf-8").read().split('\n')
x_test = open('mydata/test_contents.txt', encoding="utf-8").read().split('\n')
y_test = open('mydata/test_labels.txt', encoding="utf-8").read().split('\n')
print('Train data: ', len(x_train), 'Testing data: ', len(x_test), 'Total: ', len(x_train)+len(x_test))
return x_train, y_train, x_test, y_test
该函数作为执行函数,包含计算正确率,读取数据集,训练模型等方法。
在 main()方法中,MultinominalNB()为已经搭建好的类,在后文论述,其中nb.fit()对模型进行训练,参数save控制其是否保存训练模型,nb.predict()对测试集进行分类,并返回分类结果predictions,其中参数cached控制是否读取训练模型进行分类。predictions传入方法accuracy()中
accuracy()将accuracy()与测试集标签进行对比,并返还正确率比值。
3.2 fit()训练
fit()方法实现了模型的训练。
def fit(self, x, y, save=False):
self.docs = x
self.classes = y
num_doc = len(self.docs)
uniq_cls = np.unique(self.classes)
self.vocab = self.buildGlobalVocab()
vocab_cnt = len(self.vocab)
t = time()
for _cls in uniq_cls:
cls_docs_num = self.countCls(_cls)
self.logprior[_cls] = np.log(cls_docs_num / num_doc)
self.buildClassVocab(_cls)
class_vocab_counter = Counter(self.class_vocab[_cls])
class_vocab_cnt = len(self.class_vocab[_cls])
for word in self.vocab:
w_cnt = class_vocab_counter[word]
self.loglikelihood[word, _cls] = np.log((w_cnt + 1) / (class_vocab_cnt + vocab_cnt))
if save:
self.saveModel()
print('Training finished at {} mins.'.format(round((time() - t) / 60, 2)))
在main中有
nb = MultinominalNB()
nb.fit(x_train, y_train, save=True)
首先读入数据集x_train, y_train, 并在第一次训练时令save=True,
self.docs = x
self.classes = y
fit中先把x_train保存在docs中,y_train保存在classes中
num_doc = len(self.docs)
uniq_cls = np.unique(self.classes)
计算训练集中文档的数目、标签种类,输出如下
17600
['1' '10' '11' '2' '3' '4' '5' '6' '7' '8' '9']
self.vocab = self.buildGlobalVocab()
vocab_cnt = len(self.vocab)
利用方法**buildGlobalVocab()**建立所有词的词库,该方法将在下面论述。返回词库保存在vocab中,并计算词库中所有词语的数量 部分输出如下:
['下' '中式' '乔氏' '了' '亨德利' '以' '以上' '以及' '佛' '佛山' '佛山市' '全天' '全家福' '全封闭'
'全球' '八球' '冠军' '决战' '出炉' '初步' '后' '吨' '含' '周一' '周三' '回暖' '国际' '图文' '在'
'复苏' '外汇' '大师' '大桥' '大通' '实施' '巩义' '已经' '年' '幸福' '广佛' '开始' '张广豪' '总体'
'执行' '挑战赛' '摩根' '数据' '施工' '旅游' '日' '日报' '日讯' '时刻' '显现' '暨首届' '月' '来自' '杯'
'梁宝忠' '汽车' '河南' '照片' '环球' '球杆' '疲软' '的' '站' '第二季度' '第二阶段' '第四站' '管制' '米'
'经历' '经济' '维修' '翠' '花山' '英伦' '表现' '表示' '讯' '货车' '起' '车辆通行' '进行' '迹象' '选手'
'邀请赛' '都市网' '销售量' '长沙' '陈' '限制' '陕西' '陕西省' '零售' '颁奖仪式' '预选赛' '高度' '鲨鱼']
58513
for _cls in uniq_cls:
cls_docs_num = self.countCls(_cls)
self.logprior[_cls] = np.log(cls_docs_num / num_doc)
self.buildClassVocab(_cls)
class_vocab_counter = Counter(self.class_vocab[_cls])
class_vocab_cnt = len(self.class_vocab[_cls])
对于每一类文档,首先利用方法**countCls()计算当前标签种类的数量,并计算先验概率(公式1),保存在logprior[_cls]**中,*self.buildClassVocab(_cls)*方法建立当前种类的词库,并将其保存在class_vacab中,下面计数,在类cls中每个词语的数量保存在class_vocab_counter中,计算当前类词库词语总数保存至class_vocab_cnt。
for word in self.vocab:
w_cnt = class_vocab_counter[word]
self.loglikelihood[word, _cls] = np.log((w_cnt + 1) / (class_vocab_cnt + vocab_cnt))
利用公式2计算似然函数
if save:
self.saveModel()
print('Training finished at {} mins.'.format(round((time() - t) / 60, 2)))
最后,判断是否保存,并计算训练所用时间
3.3 buildGlobalVocab(self) 建立全部词库
def buildGlobalVocab(self):
vocab = []
for doc in self.docs:
vocab.extend(self.cleanDoc(doc))
return np.unique(vocab)
对训练集中每个文档doc,先将doc中词语分离出来,这里用到了方法cleanDoc,并将重复的词去掉,最终返回。
3.4 cleanDoc() 分离文档中的词语
def cleanDoc(doc):
return re.split(' ', doc)
vocab_cnt = cleanDoc(x_train_test[1])
print(x_train_test[1])
print(vocab_cnt)
输出如下:
幸福 时刻 全家福 旅游 照片 年 月 日 陕西省 翠 花山 全家福 旅游 照片 年 月 日 陕西省 翠 花山 全家福 旅游 照片 年 月 日 陕西省 翠 花山 全家福 旅游 照片 年 月 日 陕西
['幸福', '时刻', '全家福', '旅游', '照片', '年', '月', '日', '陕西省', '翠', '花山', '全家福', '旅游', '照片', '年', '月', '日', '陕西省', '翠', '花山', '全家福', '旅游', '照片', '年', '月', '日', '陕西省', '翠', '花山', '全家福', '旅游', '照片', '年', '月', '日', '陕西']
3.5 countCls() 计算数据集中,某个种类的数量
def countCls(self, cls):
cnt = 0
for idx, _docs in enumerate(self.docs):
if (self.classes[idx] == cls):
cnt += 1
return cnt
遍历数据集,如果种类符合计数加一
3.6 buildClassVocab(self, _cls) 计算某一类的词库
def buildClassVocab(self, _cls):
curr_word_list = []
for idx, doc in enumerate(self.docs):
if self.classes[idx] == str(_cls):
curr_word_list.extend(self.cleanDoc(doc))
if _cls not in self.class_vocab:
self.class_vocab[_cls] = curr_word_list
else:
self.class_vocab[_cls].append(curr_word_list)
先遍历整个数据集,找出所有的该类的词库,最后将词库中词语剥离出来放进class_vocab[_cls]中。
3.7 saveModel(self)与readModel(),模型保存与读取
def saveModel(self):
try:
f = open("classifier.txt", "wb")
pickle.dump([self.logprior, self.vocab, self.loglikelihood, self.classes], f)
f.close()
except:
print('Error saving the model')
@staticmethod
def readModel():
try:
f = open("classifier.txt", "rb")
model = pickle.load(f)
f.close()
return model
except:
print('Error reading the model')
3.8 predict(self, test_docs, cached=False) 对测试集进行分类
def predict(self, test_docs, cached=False):
'''
对测试集进行分类
:param test_docs: 测试数据集
:param cached: 是否读取的训练模型
:return: 测试集对应的分类结果
'''
output = []
if not cached:
logprior = self.logprior
vocab = self.vocab
loglikelihood = self.loglikelihood
classes = self.classes
else:
logprior, vocab, loglikelihood, classes = self.readModel()
for doc in test_docs:
uniq_cls = np.unique(classes)
sum = dict()
for _cls in uniq_cls:
sum[_cls] = logprior[_cls]
for word in self.cleanDoc(doc):
if word in vocab:
try:
sum[_cls] += loglikelihood[word, _cls]
except:
print(sum, _cls)
result = np.argmax(list(sum.values()))
output.append(uniq_cls[result])
return output
首先通过标志cached判断是否读取模型
接着,对测试集中每个文档,按照公式3进行计算,并得到最终分类结果,加入output中
其中 np.argmax用法
import numpy as np
a = np.array([3, 1, 2, 4, 6, 1])
b=np.argmax(a)
print(b)
4. 执行结果
C:\Users\BC_PC\AppData\Local\Programs\Python\Python39\python.exe C:/Users/BC_PC/Desktop/multinomial-nb-master/mynb.py
Train data: 17600 Testing data: 4324 Total: 21924
Training finished at 0.12 mins.
Accuracy: 0.8533765032377428
Process finished with exit code 0
|