朴素贝叶斯
朴素贝叶斯方法是一套基于贝叶斯定理的监督学习算法,在每一对特征之间采用独立的 “naive” 假设。给定一个类变量 和一个从属特征向量
到
,贝叶斯定理表示以下关系:
使用 naive ( 天真 ) 独立假设:
对于所有的 i ,这种关系被简化为:
由于 给定输入是常数,所以我们可以使用以下分类规则:
我们可以使用 Maximum A Posteriori ( 最大后验 ) ( MAP ) 估计来估计 和
; 那么前者是训练集中的类
的相对频率。
不同的朴素贝叶斯分类器主要取决于它们对 的分布的假设。
尽管他们显然过于简化的假设,但朴素贝叶斯分类器在许多现实世界的情况下都有很好的效果,有著名的文档分类和垃圾邮件过滤。他们需要少量的训练数据来估计必要的参数。( 为了了解到朴素贝叶斯运作良好,并且在哪类数据上运行良好的假想的原因,请看下文。 )
与更复杂的方法相比,朴素贝叶斯 learners ( 学习器 )和 classifiers ( 分类器 ) 可以非常快。类条件特征分布的解耦意味着每个分布可以独立地估计为一维分布。这又有助于缓解 the curse of dimensionality ( 维度惩罚 ) 所引起的问题。
另一方面,虽然朴素贝叶斯被认为是一个还不错的分类器,但它被认为是一个不好的 estimator ( 估计器 ) ,所以来自 predict_proba 的概率输出不被过于重视。
1,创建BYS模型
- # -*- coding: utf-8 -*-
- """
- Created on Fri Oct 20 11:44:48 2017
- @author: lizheng
- """
- # 项目案例1: 屏蔽社区留言板的侮辱性言论
- import numpy as np
- def loadDataSet():
- """
- 创建数据集
- :return: 单词列表postingList, 所属类别classVec
- """
- postingList = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'], #[0,0,1,1,1......]
- ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
- ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
- ['stop', 'posting', 'stupid', 'worthless', 'gar e'],
- ['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
- def createVocabList(dataSet):
- """
- 获取所有单词的集合
- :param dataSet: 数据集
- :return: 所有单词的集合(即不含重复元素的单词列表)
- """
- vocabSet = set([]) # create empty set
- for document in dataSet:
- # 操作符 | 用于求两个集合的并集
- vocabSet = vocabSet | set(document) # union of the two sets
- return list(vocabSet)
- def setOfWords2Vec(vocabList, inputSet):
- """
- 遍历查看该单词是否出现,出现该单词则将该单词置1
- :param vocabList: 所有单词集合列表
- :param inputSet: 输入数据集
- :return: 匹配列表[0,1,0,1...],其中 1与0 表示词汇表中的单词是否出现在输入的数据集中
- """
- # 创建一个和词汇表等长的向量,并将其元素都设置为0
- returnVec = [0] * len(vocabList)# [0,0......]
- # 遍历文档中的所有单词,如果出现了词汇表中的单词,则将输出的文档向量中的对应值设为1
- for word in inputSet:
- if word in vocabList:
- returnVec[vocabList.index(word)] = 1
- else:
- print ("the word: %s is not in my Vocabulary!" % word)
- return returnVec
- """
- 现在已经知道了一个词是否出现在一篇文档中,也知道该文档所属的类别。
- 接下来我们重写贝叶斯准则,将之前的 x, y 替换为 w. 粗体的 w 表示这是一个向量,即它由多个值组成。
- 在这个例子中,数值个数与词汇表中的词个数相同。
- P(ci|w)=p(w|ci)p(ci)/p(w)
- 我们使用上述公式,对每个类计算该值,然后比较这两个概率值的大小。
- #首先可以通过类别 i (侮辱性留言或者非侮辱性留言)中的文档数除以总的文档数来计算概率 p(ci) 。
- 接下来计算 p(w | ci) ,这里就要用到朴素贝叶斯假设。如果将 w 展开为一个个独立特征,
- 那么就可以将上述概率写作 p(w0, w1, w2...wn | ci) 。
- 这里假设所有词都互相独立,该假设也称作条件独立性假设(例如 A 和 B 两个人抛骰子,概率是互不影响的,
- 也就是相互独立的,A 抛 2点的同时 B 抛 3 点的概率就是 1/6 * 1/6),
- 它意味着可以使用 p(w0 | ci)p(w1 | ci)p(w2 | ci)...p(wn | ci) 来计算上述概率,这样就极大地简化了计算的过程。
- """
- def trainNB0(trainMatrix, trainCategory):
- """
- 训练数据
- :param trainMatrix: 文件单词矩阵
- :param trainCategory: 文件对应的类别
- :return:
- """
- # 总文件数
- numTrainDocs = len(trainMatrix)
- # 总单词数
- numWords = len(trainMatrix[0])
- # 侮辱性文件的出现概率
- pAbusive = sum(trainCategory) / float(numTrainDocs)
- # 构造单词出现次数列表
- # p0Num 正常的统计
- # p1Num 侮辱的统计
- # 避免单词列表中的任何一个单词为0,而导致最后的乘积为0,所以将每个单词的出现次数初始化为 1
- p0Num = np.ones(numWords)#[0,0......]->[1,1,1,1,1.....]
- p1Num = np.ones(numWords)
- # 整个数据集单词出现总数,2.0根据样本/实际调查结果调整分母的值(2主要是避免分母为0,当然值可以调整)
- # p0Denom 正常的统计
- # p1Denom 侮辱的统计
- p0Denom = 2.0
- p1Denom = 2.0
- for i in range(numTrainDocs):
- if trainCategory[i] == 1:
- # 累加辱骂词的频次
- p1Num += trainMatrix[i]
- # 对每篇文章的辱骂的频次 进行统计汇总
- p1Denom += sum(trainMatrix[i])
- else:
- p0Num += trainMatrix[i]
- p0Denom += sum(trainMatrix[i])
- # 类别1,即侮辱性文档的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表
- p1Vect = np.log(p1Num / p1Denom)
- # 类别0,即正常文档的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表
- p0Vect = np.log(p0Num / p0Denom)
- return p0Vect, p1Vect, pAbusive
- def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
- """
- 使用算法:
- # 将乘法转换为加法
- 乘法:P(C|F1F2...Fn) = P(F1F2...Fn|C)P(C)/P(F1F2...Fn)
- 加法:P(F1|C)*P(F2|C)....P(Fn|C)P(C) -> log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
- :param vec2Classify: 待测数据[0,1,1,1,1...],即要分类的向量
- :param p0Vec: 类别0,即正常文档的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表
- :param p1Vec: 类别1,即侮辱性文档的[log(P(F1|C1)),log(P(F2|C1)),log(P(F3|C1)),log(P(F4|C1)),log(P(F5|C1))....]列表
- :param pClass1: 类别1,侮辱性文件的出现概率
- :return: 类别1 or 0
- """
- # 计算公式 log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C))
- # 使用 NumPy 数组来计算两个向量相乘的结果,这里的相乘是指对应元素相乘,即先将两个向量中的第一个元素相乘,然后将第2个元素相乘,以此类推。
- # 我的理解是:这里的 vec2Classify * p1Vec 的意思就是将每个词与其对应的概率相关联起来
- # 可以理解为 1.单词在词汇表中的条件下,文件是good 类别的概率 也可以理解为 2.在整个空间下,文件既在词汇表中又是good类别的概率
- p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)
- p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
- if p1 > p0:
- return 1
- else:
- return 0
- def bagOfWords2VecMN(vocabList, inputSet):
- returnVec = [0] * len(vocabList)
- for word in inputSet:
- if word in vocabList:
- returnVec[vocabList.index(word)] += 1
- return returnVec
- import BYS
- import numpy as np
- # 1. 加载数据集
- listOPosts, listClasses = BYS.loadDataSet()
- # 2. 创建单词集合
- myVocabList = BYS.createVocabList(listOPosts)
- # 3. 计算单词是否出现并创建数据矩阵
- trainMat = []
- for postinDoc in listOPosts:
- # 返回m*len(myVocabList)的矩阵, 记录的都是0,1信息
- trainMat.append(BYS.setOfWords2Vec(myVocabList, postinDoc))
- # 4. 训练数据
- p0V, p1V, pAb = BYS.trainNB0(np.array(trainMat), np.array(listClasses))
- # 5. 测试数据
- testEntry = ['love', 'my', 'dalmation']
- thisDoc = np.array(BYS.setOfWords2Vec(myVocabList, testEntry))
- print (testEntry, 'classified as: ', BYS.classifyNB(thisDoc, p0V, p1V, pAb))
- testEntry = ['stupid', 'garbage']
- thisDoc = np.array(BYS.setOfWords2Vec(myVocabList, testEntry))
- print (testEntry, 'classified as: ', BYS.classifyNB(thisDoc, p0V, p1V, pAb))
- 朴素贝叶斯的准确率,其实是比较依赖于训练语料的,机器学习算法就和纯洁的小孩一样,取决于其成长(训练)条件,”吃的是草挤的是奶”,但”不是所有的牛奶,都叫特仑苏”。
代码来自https://github.com/apachecn