还记得之前我们搭建的朴素贝叶斯的分类模型,在正面负面各9个样本的情况下,正确率如下。
{'01': 1, '10': 5, '00': 7, '11': 3} (0.875, 0.375)
我开始引入停用词,从测试来看,主要是在训练的时候效果比较明显,因为中文样本中标点符号和很多常用词占比还是比较大的,引入停用词机制前后,差不多是三分之一的量。
下面是读入停用词的函数代码。
def read_stopwords(self): self.stopwords_list = [] # 获得停用词文件的本地文件 filename = get_long_filename_with_sub_dir_module('bayes', 'stopwords.txt')[1] with open(filename, 'r') as f: for line in f: self.stopwords_list.append(line.rstrip()) print(self.stopwords_list)
目前我是将朴素贝叶斯的这个分类,作为 fish_base 包的一部分功能。对于停用词来说属于功能部分,而之前的训练文档其实不是功能部分,是由真实代码或者 demo 代码传递给分类器的。因此训练文档和测试文档属于 demo 代码,而停用词属于分类器代码。
(国内网上很多的贝叶斯代码的解释者可能都是学习数据分析、统计分析的职业人士,但是代码的写法和扩展性,略显简单,我可能习惯了尽量做得完善一些,副作用是会比较复杂一点,需要更多 python 和编程的基础)
整个类的代码最后给出。这里解释一下
get_long_filename_with_sub_dir_module
这个函数是为了获得模块运行时候的路径,否则一般的获取路径办法获得是执行文件的路径,这就是之前说的如果只是一个 demo,不会有这个问题,统统在一个路径下面,但是如果考虑做成包,做成 module,封装逻辑的话,就会复杂一点,另外还要考虑各个操作系统可能的差异。
这个函数属于 fish_base 的 commmon 部分,目前 fish_base 公开的版本1.0.7 中还不支持,在 github 上的1.0.8 支持该函数。
停用词来自于之前说到过的 snownlp 包,感谢作者。停用词数量大约1400个左右,以后考虑支持用户自定义停用词。
在训练过程引入停用词之后,再用昨天的测试样本测试一下,得到了如下的结果。
{'00': 8, '10': 4, '01': 0, '11': 4} (1.0, 0.5)
正面被分类成正面准确率100%,负面被分类成负面也从37.5% 提升到 50%,略有进步。
按照公司数据分析团队的建议,后者百分比还不够高是因为训练样本太少的缘故。
如此少的训练样本,得到了这样的结果,不得不说朴素贝叶斯的确很厉害,一点也不 naive。
之后我们还会继续改进的地方是:
1 将判断错误的样本增加到训练样本中,来重新训练
2 分词,增加用户自定义词汇
3 测试材料,增加停用词处理(目前测试下来,对提升正确率没有作用),这也和目前举例中的测试文本比较短有关,并且这个也和性能有关,训练过程中引入停用词判断对性能的损失是无所谓的,反正是在训练过程中,但是实际使用分类还是要越快越好
4 我们会将整个分类器通过 Falsk 包装成 web 接口,加入 job 概念,这样调用程序只需要传入训练文本和测试文本,之后就可以生成特定的分类器概率矩阵,然后供调用程序在真实场景下使用,作为调用程序的项目组不需要知道后面这些细节
5 所有的数据存储可以本地实例化,sqlite 或者 mysql 之类的支持
6 支持三元和四元的分类
从用 python 来实现朴素贝叶斯分类器来对中文文本进行倾向性分析来说,暂时告一段落了。所有代码都是这几天里面改写和增加的,防止错误的部分几乎没有,也没有进行完整的单元测试。
下面是当前版本完整的朴素贝叶斯分类器的类的代码,直接使用需要有 python 基础,也可以之后使用 fish_base 包来调用。
最新版本源代码在这里: github.com/chinapnr/fish_base
pypi 的安装包会稍微滞后一些。
from numpy import * import jieba from fish_base import get_long_filename_with_sub_dir_module class ClassNaiveBayes: # 训练 list, 默认内容, 原书中的内容 train_doc_list = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'], ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'], ['stop', 'posting', 'stupid', 'worthless', 'garbage'], ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'], ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']] # 倾向性向量, 0:正面 1:负面 train_doc_sent_vec = [0, 1, 0, 1, 0, 1] # 单词列表集合 word_list = [] # 正面和负面概率, 先验概率 p0_v = 0 p1_v = 0 p_ab = 0 stopwords_list = [] # 2016.5.18 # 读入停用词 def read_stopwords(self): self.stopwords_list = [] # 获得停用词文件的本地文件 filename = get_long_filename_with_sub_dir_module('bayes', 'stopwords.txt')[1] with open(filename, 'r') as f: for line in f: self.stopwords_list.append(line.rstrip()) print(self.stopwords_list) # 2016.5.19 def word_optimize(self, l): # 停用词过滤 temp_word_list = [x for x in l if x not in self.stopwords_list] return temp_word_list # 2016.5.16 5.19 # 创建单词集合 # 输入 data_list: 数据列表内容, 两维list # 输出 单维list def create_word_list(self, data_list): # create empty set word_set = set([]) for document in data_list: # union of the two sets word_set = word_set | set(document) word_list = list(word_set) # 词汇处理 word_list = self.word_optimize(word_list) # print('word list count:', len(word_list)) return word_list # 2016.5.16 # 将单词 list 转换为向量 # 输入 word_list: 单词列表 new_word_list: 需要向量化的单词列表 # 输出 vec: 生成的向量数组 @staticmethod def words_to_vec(word_list, new_word_list): vec = [0] * len(word_list) for word in new_word_list: if word in word_list: vec[word_list.index(word)] += 1 else: pass # print("the word: %s is not in my Vocabulary!" % word) return vec # 2016.5.16 # 进行 naive bayes 训练 # 输入 train_matrix: 训练举证 train_category: 正反向量列表 # 输出 p0_v, p1_v:正面反面概率, p_ab:先验概率 @staticmethod def train_nb0(train_matrix, train_category): num_train_docs = len(train_matrix) num_words = len(train_matrix[0]) p_ab = sum(train_category) / float(num_train_docs) # 创建给定长度的填满1的数组 p0_num = ones(num_words) p1_num = ones(num_words) p0_d = 2.0 p1_d = 2.0 for i in range(num_train_docs): if train_category[i] == 1: p1_num += train_matrix[i] p1_d += sum(train_matrix[i]) else: p0_num += train_matrix[i] p0_d += sum(train_matrix[i]) p1_v = log(p1_num / p1_d) p0_v = log(p0_num / p0_d) return p0_v, p1_v, p_ab # 2016.5.16 # 分类 # 输入 向量, 正面反面概率,事件概率 # 输出 正面或者反面 @staticmethod def classify_nb(vec, p0_vec, p1_vec, p_class1): # element-wise mult p0 = sum(vec * p0_vec) + log(1.0 - p_class1) p1 = sum(vec * p1_vec) + log(p_class1) print('p0:', p0, 'p1:', p1) if p1 > p0: return 1 else: return 0 # 2016.5.16 # 训练, 生成需要的向量参数等 def train(self): # 生成单词列表集合 self.word_list = self.create_word_list(self.train_doc_list) # 训练矩阵初始化 train_matrix = [] # 根据训练文档进行循环 for post_in_doc in self.train_doc_list: # 构建训练矩阵, 将单词列表转化为向量 train_matrix.append(self.words_to_vec(self.word_list, post_in_doc)) # 根据训练矩阵和情感分析向量进行训练,得到 self.p0_v, self.p1_v, self.p_ab = self.train_nb0(array(train_matrix), array(self.train_doc_sent_vec)) # 2016.5.16 # 根据输入内容测试 Naive Bayes 模型 # 输入 test_word_list: 需要测试的单词 list # 输出 0 or 1, 表示正面或者反面 def run_nb(self, word_list): # 对输入的内容转化为向量 this_post_vec = array(self.words_to_vec(self.word_list, word_list)) # 返回分类的值 return self.classify_nb(this_post_vec, self.p0_v, self.p1_v, self.p_ab) # 2016.5.18 @staticmethod def init_cut_word(): jieba.initialize() # 2016.5.18 # 打开训练文档, 将内容增加到内部变量 def open_train_doc_ch(self, filename, class_mark): train_txt0 = [] with open(filename, 'r') as f: for line in f: train_txt0.append(line.rstrip()) for item in train_txt0: s = list(jieba.cut(item)) self.train_doc_list.append(s) self.train_doc_sent_vec.append(class_mark) # 2016.5.18 # 根据测试样本来测试分类的准确率 # 输出 人工正确/机器判断正确 人工错误/机器判断错误 的两个百分比 def test_nb(self, filename): test_doc_list = [] pre_class_list = [] # 设定测试结果 dict test_result_dict = {'11': 0, '10': 0, '00': 0, '01': 0} # 打开测试文本 with open(filename, 'r') as f: for line in f: # 获得人工设定的类别 pre_class_list.append(line[0:1]) # 获得需要测试的文本 test_doc_list.append(line[2:].rstrip()) # 测试文本的长度 test_doc_count = len(test_doc_list) for i, item in enumerate(test_doc_list): s = list(jieba.cut(item)) # 对输入单词进行优化处理 s = self.word_optimize(s) # 获得程序分类结果 computer_class = self.run_nb(s) # 获得人工设定的分类结果 pre_class = pre_class_list[i] # 结果记录到测试结果 dict 中 index = '**' if pre_class == '1': index = str(10 + computer_class) if pre_class == '0': index = '0' + str(computer_class) test_result_dict[index] += 1 print(s, pre_class, computer_class) print(test_result_dict) # 返回结果, 假设测试文本中正面和负面各占一半 return test_result_dict['00'] / (test_doc_count / 2), test_result_dict['11'] / (test_doc_count / 2)
下面是测试用的程序。
from fish_base import bayes import jieba nb = bayes.ClassNaiveBayes() nb.train() test_list = ['love', 'my', 'dalmation'] print(test_list, 'classified as: ', nb.run_nb(test_list)) test_list = ['stupid', 'garbage'] print(test_list, 'classified as: ', nb.run_nb(test_list)) nb.train_doc_list = [] nb.train_doc_sent_vec = [] # for Chinese # 初始化分词 nb.init_cut_word() # 读入停用词 nb.read_stopwords() nb.open_train_doc_ch('train_bayes/good.txt', 0) nb.open_train_doc_ch('train_bayes/bad.txt', 1) nb.train() print(nb.p0_v, nb.p1_v, nb.p_ab) print(nb.test_nb('train_bayes/test.txt')) # 指定测试 print() test_s = '这个手机很好,我很喜欢' print(test_s) test_list = list(jieba.cut(test_s)) p = nb.run_nb(test_list) if p == 0: print('classified as good ') else: print('classified as bad ') # while 1: # test_s = input('input comment:') # test_list = list(jieba.cut(test_s)) # print(test_list) # p = nb.run_nb(test_list) # if p == 0: # print('classified as good ') # else: # print('classified as bad ') # # print() # 2016.5.18 # {'01': 1, '10': 5, '00': 7, '11': 3} # (0.875, 0.375) # # # 2016.5.18 增加去除停用词,一般意义 # {'11': 4, '00': 8, '10': 4, '01': 0} # (1.0, 0.5)
(感谢这几天项目开发团队和数据分析开发团队同事支持和耐心讲解。)
使用 python 基于朴素贝叶斯进行文本分类学习笔记之一:开始
使用 python 基于朴素贝叶斯进行文本分类学习笔记之二:将原书程序修改并转换为类
使用 python 基于朴素贝叶斯进行文本分类学习笔记之三:中文内容的倾向性判断初步
使用 python 基于朴素贝叶斯进行文本分类学习笔记之四:增加测试文本和计算正确率
使用 python 基于朴素贝叶斯进行文本分类学习笔记之五:增加停用词