Naive Bayesian Model
(概率模型,不需要数据归一化)
假设一个数据集有两个类别C1和C2,对于一个新的测试点(x,y)
即选择具有最高概率的类别作为决策!
贝叶斯定理(即条件概率计算公式):
简单变形:
P(A)称为 “先验概率”(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为 “后验概率”(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为 “可能性函数”(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。
后验概率 = 先验概率 x 调整因子
我们先预估一个"先验概率",然后加入实验结果,看这个实验到底是增强还是削弱了"先验概率",由此得到更接近事实的"后验概率"。如果"可能性函数"P(B|A)/P(B)>1,意味着"先验概率"被增强,事件A的发生的可能性变大;如果"可能性函数"=1,意味着B事件无助于判断事件A的可能性;如果"可能性函数"<1,意味着"先验概率"被削弱,事件A的可能性变小。
两个一模一样的碗,一号碗(H1)有30颗水果糖和10颗巧克力糖,二号碗(H2)有水果糖(E)和巧克力糖各20颗。现在随机选择一个碗,从中摸出一颗糖,发现是水果糖。请问这颗水果糖来自一号碗的概率有多大?
由于这两个碗是一样的,所以P(H1)=P(H2),也就是说,在取出水果糖之前,这两个碗被选中的概率相同。因此,P(H1)=0.5,我们把这个概率就叫做"先验概率",即没有做实验之前,来自一号碗的概率是0.5。问题变成了已知E的情况下,来自一号碗的概率有多大,即求P(H1|E)。我们把这个概率叫做"后验概率",即在E事件发生之后,对P(H1)的修正。公式为:
在实际应用中,我们往往只需要知道所属类别,而不需要计算具体的类别概率。在上式中,因为两个碗相同,只需要比较 P(H1|E)和P(H2|E)的大小,找到那个更大的概率就可以。既然如此,两者的分母P(E)都是相同的,那我们只需要比较分子即可,即比较P(E|H1)P(H1)和P(E|H2)P(H2)的大小,所以为了减少计算量,全概率公式在实际编程中可以不使用,即分母不需要计算。问题转化成如何基于训练数据D来估计先验概率P©和似然P(X|c)
朴素贝叶斯对条件个概率分布做了条件独立性的假设。
由于每个特征都是独立的,我们可以将分子上的条件概率进行拆分。
"""
import numpy as np
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代表侮辱性文字, 0代表正常言论
return postingList, classVec
# 创建包含在所有文档中的词汇表(不重复)
def createVocabList(dataSet):
vocabSet = set([]) # 创建一个空的不重复列表
for document in dataSet:
vocabSet = vocabSet | set(document) # 求并集
return list(vocabSet)
# 根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0] * len(vocabList) # 创建一个其中所含元素都为0的向量
for word in inputSet: # 遍历每个词条
if word in vocabList: # 如果词条存在于词汇表中,则设为1
returnVec[vocabList.index(word)] = 1
else:
print("the word: %s is not in my Vocabulary!" % word)
return returnVec # 返回文档向量
# 函数说明: 朴素贝叶斯分类器训练函数
# trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵,文档含有这个词1,文档不含这个词0
# trainCategory - 训练类别标签向量,即loadDataSet返回的classVe
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix) # 计算训练的文档数目
numWords = len(trainMatrix[0]) # 计算每篇文档的词条数
pAbusive = sum(trainCategory)/float(numTrainDocs) # 文档属于侮辱类的概率
# p0Num = np.zeros(numWords)
# p1Num = np.zeros(numWords) # 创建numpy.zeros数组,词条出现数初始化为0
# p0Denom = 0.0
# p1Denom = 0.0 # 分母初始化为0
p0Num = np.ones(numWords); p1Num = np.ones(numWords) # change to np.ones() 防止有一项概率为0,乘积为0
p0Denom = 2.0; p1Denom = 2.0 # change to 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1: # 统计属于侮辱类的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i]) # +当前文档所有的词
else: # 统计属于非侮辱类的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i]) # +当前文档所有的词
# p1Vect = p1Num/p1Denom # P(cute|侮辱类)
# p0Vect = p0Num/p0Denom # P(cute|非侮辱类)
p1Vect = np.log(p1Num/p1Denom) # change to np.log() 防止概率乘积过小,被python约为0
p0Vect = np.log(p0Num/p0Denom) # change to np.log()
return p0Vect, p1Vect, pAbusive # 返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): # p(w|ci)*p(ci)
p1 = sum(vec2Classify * p1Vec) + np.log(pClass1) # sum也是乘积转化为log的和而来
p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1) # log(a*b) = log(a) + log(b)
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
def testingNB():
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
p0V, p1V, pAb = trainNB0(np.array(trainMat), np.array(listClasses))
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testEntry = ['stupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testingNB()
在sklearn中,朴素贝叶斯有三种方法,分别是GaussianNB(先验为高斯分布即正态分布),MultinomialNB(先验为多项式分布) 和 BernoulliNB(先验为伯努利分布)。
通过假设P (Xi∣Y) 是服从高斯分布(也就是正态分布),来估计每个特征下每个类别上的条件概率。
分类效果在二分数据和月亮型数据上表现优秀,但是环形数据不太擅长(但远远胜过其他线性模型)
import numpy as np
from sklearn.datasets import make_blobs # 导入数据集
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
x, y = make_blobs(n_samples=500, centers=5, random_state=8) # 这里构建了一个500个样本,5类的数据
x_train, x_test, y_train, y_test = train_test_split(x, y, random_state=8)
gnb = GaussianNB()
gnb.fit(x_train, y_train)
print(gnb.score(x_test, y_test)
二项分布,适合0—1变量的分类任务
from sklearn.naive_bayes import BernoulliNB
bnb = BernoulliNB()
bnb.fit(x_train, y_train)
bnb.score(x_test, y_test)
多项式分布擅长的是分类型变量(离散),不接受负值的输入
from sklearn.naive_bayes import MultinomialNB
mnb=MultinomialNB()
mnb.fit(x_train,y_train)
mnb.score(x_test,y_test)
1. 优点
2. 缺点
sklearn中,有三种不同的朴素贝叶斯模型。朴素贝叶斯将问题转化成如何基于训练数据D来估计先验概率P(A)和似然P(B|A)。P(A)由大数定律,是不是可以由训练集的某分类除以训练集总数得到?
那么P(A)的算法统一了,问题转化成了如何求似然估计P(B|A),资料说根据对“似然度 P(Bi|A)”计算方法的不同,我们将朴素贝叶斯大致分为三种:多项式朴素贝叶斯、伯努利分布朴素贝叶斯、高斯分布朴素贝叶斯。例如高斯朴素贝叶斯就是用正态分布模型来估算P(B|A)。如何理解这里的计算方法不同呢?
是否这样理解:真实的数据集,并不像我们的例子,例如“碗中取糖果等”有着固定的分布规律,这三个算法是否就是在假设数据集符合某种分布,例如用伯努利朴素贝叶斯,就是在假设训练集的每个属性都满足二项分布?然后去计算条件概率?那如果有的特征符合正态分布,有的特征符合多项式分布,有的特征符合伯努利怎么办呢?
Reference:
机器学习实战教程(四):朴素贝叶斯基础篇之言论过滤器