机器学习之朴素贝叶斯
都说万事开头难,可一旦开头,就是全新的状态,就有可能收获自己未曾预料到的成果。从2018.12.28开始,决定跟随《机器学习实战》的脚步开始其征程,记录是为了更好的监督、理解和推进,学习过程中用到的数据集和代码都将上传到github
机器学习征程博客:(1)机器学习之K-近邻算法 (2)机器学习之决策树
1. 必须理解的基础理论
在认识朴素贝叶斯之前,必须查阅资料理解条件概率和贝叶斯法则,这是掌握朴素贝叶斯分类方法的理论基础
(1)条件概率
p(x=a|y=b) 表示在y=b成立的条件下x=a的概念
p(x=a|y=b) = p(x=a,y=b) / p(x = a)
(2)贝叶斯法则
朴素贝叶斯最核心的部分是贝叶斯法则,贝叶斯法则的基石是条件概率,贝叶斯法则告诉我们如何交换条件概率中的条件和结果,即如果已知p(x,y|c)计算p(c|x,y)
以后面例子来解释,这里的C表示类别,x,y表示各种表示词汇集中的各种特征词,p(c|x,y)理解为有x,y特征词的情况下为类型C的概率
若要求类型C1中各特征词的概率:p(x,y|C1) = p(x1,y1|C1) * p(x2,y2|C1) … p(xn,yn|C1)
根据上面的公式,我们可以定义贝叶斯分类准则为:
a.如果p(c1|x,y) > p(c2|x,y),那么属于c1类
b.如果p(c1|x,y) < p(c2|x,y),那么属于c2类
2. 什么是朴素贝叶斯
(1)概述
朴树贝叶斯是为数不多的基于概率论的分类算法,通过考虑特征概率来预测分类,多用于文本分类,下面举个粗略的例子:
邮件的分类为[垃圾邮件,非垃圾邮件]
现在有封邮件,里面就三词[广告,会议,新闻]
计算出在垃圾邮件中特征词共同出现的概率 p1 = p(广告)*p(会议)*p(新闻)
计算出在非垃圾邮件中特征词共同出现的概率 p2 = p(广告)*p(会议)*p(新闻)
若p1 > p2则推测该邮件为垃圾邮件,反之,推测为非垃圾邮件,如果有邮件的分类还有第三类,就继续算出p3,然后计算max(p1,p2,p3)
上面就是贝叶斯决策理论的核心思想,即选择具有最高概率的决策
(2)为何称为朴素
我们称之为“朴素”,是因为整个形式化过程中只做最原始、最简单的假设,假设每个词都是独立的特征,假设所有词相互条件独立,一个词的出现并不依赖于文档中的其他词。
3. 使用朴素贝叶斯对文档分类
以在线社区的留言板为例,为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要建立一个快速的过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言表示为内容不当。
需求:根据留言板内容,设计贝叶斯过滤器负责检测留言内容是否属于侮辱性言论
(1)朴素贝叶斯的一般过程
a. 准备数据:训练文档
b. 分析数据:有大量特征时,绘制特征作用不大,此时使用直方图效果好
c. 训练数据:计算不同的独立特征的条件概率
d. 测试算法:计算错误率
e. 使用算法
(2)准备数据
准备6条留言板内容,及其确认的类别(侮辱性言论和正常言论),下面代码中postingList为留言板列表,classVec 为每条留言对应的类别
def loadDataSet(): """ 创建数据集 """ postingList = [['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']] classVec = [0, 1, 0, 1, 0, 1] #1 is abusive, 0 not return postingList, classVec
(3)创建特征词汇数据集,遍历所有留言,就特征词添加到词汇集中
def create_vocab_list(dataset): """ 根据数据集创建词汇列表 """ vocabset = set([]) for doc in dataset: vocabset = vocabset | set(doc) return list(vocabset)
(3)根据特征词汇集分别将每条留言转换为向量,该向量可以是词集也可以是词袋
词集模式:对于给定文档,只统计某个词是否出现在本文档中,而不统计出现的次数
词袋模式:对于给定的文档,统计某个词在本文档中出现的次数,除此之外,往往还需要剔除重要性极低的高频词和停用词
下面代码是基于词袋的:
def trans_doc_to_vector(vocablist, inputset): """ 将输入词汇通过词汇表转换为向量 例:vocablist = ['hello','world','fate0729'] inputset = ['a','hello','b','c'] return [0,1,0,0] """ count = len(vocablist) return_vector = [0]*count for word in inputset: if word in vocablist: return_vector[vocablist.index(word)] += 1 return return_vector
(4)训练算法
计算出特征词汇集中的每个特征值分别在侮辱性言论留言和正常性言论中留言的概率
以计算每个特征值在侮辱性言论类别留言中的概率为例进行说明:
a. 统计每个特征值分别在侮辱性言论留言中出现的总次数num1…..numN
b. 统计所有特征值在侮辱性言论留言中出现的总次数sum
c. 计算每个特征值在侮辱性言论类别留言中的概率 num1/sum … numN/sum
注意:
a. 由于在使用贝叶斯算法时会使用到概率相乘的情况,为了避免概率值为0的情况,可以将所有词的出现次数初始化为1,sum初始化为2
b. 由于太多很小的数相乘容易造成程序下溢出或者得不到正确的答案,我们可以选择对成绩取自然对数
def bayes_train(train_matrix,train_caltegory): """ 训练集 train_matrix:转换后的文档向量 train_caltegory:文档类型 """ docs = len(train_matrix) #文章数量 wordnum_in_docs = len(train_matrix[0]) #文章的单词数 p0num = np.ones(wordnum_in_docs) p1num = np.ones(wordnum_in_docs) p0Denom = 2 p1Denom = 2 for i in range(docs): if train_caltegory[i] == 1: p1num += train_matrix[i] #单词在类型1的所有文章中出现的次数 p1Denom += sum(train_matrix[i]) #类型1的文章中在词汇表中出现的总次数 else: p0num += train_matrix[i] p0Denom += sum(train_matrix[i]) p1vect = np.log(p1num / p1Denom) #所有单词在类型1的文章中出现的概率 p0vect = np.log(p0num / p0Denom) #所有单词在类型0的文章中出现的概率 pAb = sum(train_caltegory) / len(train_caltegory)#类型1在所有类型中的概率 return p0vect,p1vect,pAb
先测试下,看看特征词汇集和训练算法返回结果:
if __name__ == '__main__': postingList, classVec = loadDataSet() vocablist = create_vocab_list(postingList) print('vocablist:',vocablist) return_vectors = [] for item in postingList: return_vector = trans_doc_to_vector(vocablist,item) return_vectors.append(return_vector) p0vect,p1vect,pAb = bayes_train(return_vectors, classVec) print("pAb:",pAb) print('p0vect:',p0vect) print('p0vect:',p1vect)
输出:
vocablist为特征词汇集,pAb为侮辱性留言占总留言的比例,p0vect为特征词在正常性言论留言中的概率,p1vect为特征词在正常性言论留言中的概率
以stupid为例,stupid在p0vect中的概率为-3.25896,而在p1vect中的概率为-1.65822808,且为p1vect中最大值,这意味着stupid最能代表侮辱性留言类别
(5)朴树贝叶斯分类函数
def classify(pAb,p0vect,p1vect,test_vect): """ 分类 """ p1 = sum(test_vect*p1vect) + math.log(pAb) p0 = sum(test_vect*p0vect) + math.log((1-pAb)) print("p0:",p0) print("p1:",p1) if p1 > p0: return 1 else: return 0
该函数比较test_vect向量在类型0类型1中的概率,可以回头看看贝叶斯函数原型
(6)测试
给条留言进行测试
test_doc = [‘love’, ‘my’, ‘dalmation’]
test_doc1 = [‘stupid’, ‘garbage’]
test_doc = ['love', 'my', 'dalmation'] test_doc_vect = np.array(trans_doc_to_vector(vocablist, test_doc)) label = classify(pAb,p0vect,p1vect,test_doc_vect) print("留言类型为:",label) test_doc1 = ['stupid', 'garbage'] test_doc_vect1 = np.array(trans_doc_to_vector(vocablist, test_doc1)) label1 = classify(pAb,p0vect,p1vect,test_doc_vect1) print("留言类型为:",label1)
输出:
即第一条留言为正常性言论,第二条为侮辱性留言
4. 总结
(1)对于分类而言,使用概率有时比使用硬规则更为有效,贝叶斯提供了一种利用已知值来估计未知概率的有效方法
(2)特征之间的条件独立性假设,显然这种假设显得“粗鲁”而不符合实际,这也是名称中“朴素”的由来。然而事实证明,朴素贝叶斯在有些领域很有用,比如垃圾邮件过滤;
(3)在具体的算法实施中,要考虑很多实际问题,比如因为“下溢”问题,需要对概率乘积取对数,再比如词集模型和词袋模型,还有停用词和无意义的高频词的剔除,以及大量的数据预处理问题,等等