Naive Bayes (朴素贝叶斯) 属于监督学习算法, 它通过计算测试样本在训练样本各个分类中的概率来确定测试样本所属分类, 取最大概率为其所属分类.
优点 | 在数据较少的情况下仍然有效,可以处理多类别问题 |
缺点 | 对输入数据的准备方式较为敏感 |
适用数据类型 | 标称型 |
基础概念
1. 条件概率
P(A|B) 表示事件B已经发生的前提下, 事件A发生的概率, 即事件B发生下事件A的条件概率。
计算公式为:
2. 贝叶斯公式
当 P(A|B) 比较容易计算, P(B|A) 比较难以计算时, 可以利用贝叶斯公式.
计算公式为:
算法描述
1. 算法的核心就是计算 P(Ci|w), 其中 w 是测试样本, Ci 是某一个分类, 即计算 w 属于 Ci 的概率, 哪个概率大, 就属于哪个 Ci; 计算公式为:
2. P(w) 对于一个测试样本是固定值, 所以这里不进行考虑, 只需考虑分母即可. (代码72, 73行)
3. P(Ci) 表示的是测试样本中一个分类的概率, 这个也是可以直接求出来的.(即代码中的 pAbusive)
4. P(w|Ci) 中的 w 在本文中是指一个文档, 它由各个单词 W0, W1, W2...组成, W0, W1, W2...的相互之间是独立的, 所以有以下公式成立:
5. 如果每次有一篇测试文档 w 进来后, 都把它拆分成 W0, W1, W2.. 再去计算 P(W0|Ci), P(W1|Ci), P(W2|Ci)..., 则会大大降低效率, 所以应该在训练阶段把所有出现过的词语 Wj 在各个 Ci 的概率都 P(Wj|Ci) 计算好, 在测试时直接使用即可.
6. P(Wj|Ci) 的计算, 用 Wj 在 Ci 中出现的次数除以 Ci 中的总单词数即可. (代码第 62, 63 行)
算法流程图
# -*- coding: utf-8 -* from numpy import * # 加载已分好词的数据 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']] # 这里分别指明 postingList 中的各个项是否为侮辱性语言 # 0 表示不是侮辱性语言, 1 表示是 classVec = [0,1,0,1,0,1] return postingList,classVec # 把 dataSet 中的单词存到 list 中, 同时去除所有重复单词 def createVocabList(dataSet): vocabSet = set([]) #create empty set for document in dataSet: vocabSet = vocabSet | set(document) #union of the two sets return list(vocabSet) # 将 inputSet 转成向量, 即一个长度为 len(vocabList) 的向量 # 与 inputSet 中单词相同处为 1, 其余为 0 def setOfWords2Vec(vocabList, inputSet): returnVec = [0]*len(vocabList) 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 # 朴素贝叶斯核心训练函数, 训练结果为所有单词在各个分类中的概率即 P(w|Ci) # 本函数最后得到两个长度为 N 的数组, N 为 trainMatrix 的列数, 即所有文档中的中单词数 # p1Vect/p0Vect 即为任意文档属于侮辱性/非侮辱性文档时, 各个单词出现的概率,即为 P(w|Ci) # 返回的 pAbusive 为侮辱性语句占总语句的百分比, 即 P(Ci) def trainNB0(trainMatrix,trainCategory): numTrainDocs = len(trainMatrix) # 行数 numWords = len(trainMatrix[0]) # 列数 # 因为 trainCategory 中为 1 的是侮辱性, 为 0 的不是, 所以求 sum 后就是侮辱性的个数 # 再除以总的集合数, 就行到侮辱性所占百分比 pAbusive = sum(trainCategory)/float(numTrainDocs) # p1Num/p0Num 为所有侮辱性/非侮辱性语句对应向量的和 # p1Denom/p0Denom 为所有侮辱性/非侮辱性语句中包含的单词数 p0Num = ones(numWords); p1Num = ones(numWords) # 全为 1, 避免乘 0 p0Denom = 2.0; p1Denom = 2.0 # change to 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]) p1Vect = log(p1Num/p1Denom) # 求对数, 避免极小数相乘, 最后得0 p0Vect = log(p0Num/p0Denom) # change to log() return p0Vect,p1Vect,pAbusive # 对 vec2Classify 进行分类, 看它是属于 p1Vec 还是 p0Vec def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): # log 求加, 实际上就是贝叶斯公式分子中的求乘 # 因为 vec2Classify 对于出现的单词为 1, 未出现的单词为 0 # 所以两者相乘实际上就是 P(w|Ci) p1 = sum(vec2Classify * p1Vec) + log(pClass1) #element-wise mult p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) if p1 > p0: return 1 else: return 0 # 另一种 inputSet 生成向量方法(与 setOfWords2Vec) # 这里是每个单词出现一次, 就在相应位置 +1 # setOfWords2Vec 是对于出现过的单词, 设置相应位置为 1 def bagOfWords2VecMN(vocabList, inputSet): returnVec = [0]*len(vocabList) for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)] += 1 return returnVec # 测试朴素贝叶斯 def testingNB(): # 加载数据并进行向量化 # 每个 list0Posts 中的项都生成一个向量, 最后 trainMat 是一个矩阵 listOPosts,listClasses = loadDataSet() myVocabList = createVocabList(listOPosts) trainMat=[] for postinDoc in listOPosts: trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) # 训练 p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses)) # 测试 1, 结果应为 非侮辱 testEntry = ['love', 'my', 'dalmation'] thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb) # 测试 2, 结果应为 侮辱 testEntry = ['stupid', 'garbage'] thisDoc = array(setOfWords2Vec(myVocabList, testEntry)) print testEntry,'classified as: ',classifyNB(thisDoc,p0V,p1V,pAb) # 按空格拆分字符串, 只取长度大于 2 的单词 def textParse(bigString): #input is big string, #output is word list import re listOfTokens = re.split(r'\W*', bigString) return [tok.lower() for tok in listOfTokens if len(tok) > 2] if __name__ == "__main__": testingNB()
说明
本文为《Machine Leaning in Action》第四章(Classifying with probability theory: naïve Bayes)读书笔记, 代码稍作修改及注释.
好文参考
1.《算法杂货铺——分类算法之朴素贝叶斯分类(Naive Bayesian classification)》
转载 http://my.oschina.net/zenglingfan/blog/177517