一、算法原理
贝叶斯公式描述了两个相关的随机事件或随机变量之间的概率关系。贝叶斯分类器使用贝叶斯公式计算样本属于某一类的条件概率值,并将样本判定为概率值最大的那个类。
贝叶斯公式为:
我们把P(A)称为"先验概率"(Prior probability),即在B事件发生之前,我们对A事件概率的一个判断。
P(A|B)称为"后验概率"(Posterior probability),即在B事件发生之后,我们对A事件概率的重新评估。
P(B|A)/P(B)称为"可能性函数"(Likelyhood),这是一个调整因子,使得预估概率更接近真实概率。
这一结论可以推广到随机变量。分类问题中样本的特征向量取值x与样本属性y具有因果关系。因为样本属于y,所以具有特征值x。贝叶斯分类器是在已知样本的特征向量为x的条件下反推样本所属的类别。根据贝叶斯公式有:
只要知道特征向量的概率分布p(x),每一个类出现的概率p(y),以及每一个类样本的条件概率p(y|x),就可以计算出样本属于每一个类的概率p(y|x)。分类问题只要预测类别,比较样本属于每一个类的概率的大小,找出该值最大的那一个类即可,因此可以忽略p(x),因为它对所有类都是相同的。简化后分类器的判别函数为
贝叶斯和朴素贝叶斯的概念是不同的,区别就在于“朴素”二字,朴素贝叶斯对条件个概率分布做了条件独立性的假设。朴素贝叶斯分类器假设特征向量的分量之间相互独立。
二、算法实践
1.离散型特征
以在线社区留言为例。为了不影响社区的发展,我们要屏蔽侮辱性的言论,所以要构建一个快速过滤器,如果某条留言使用了负面或者侮辱性的语言,那么就将该留言标志为内容不当。过滤这类内容是一个很常见的需求。对此问题建立两个类型:侮辱类和非侮辱类,使用1和0分别表示。(来自《机器学习实战》转载自http://blog.csdn.net/c406495762 )
# -*- coding: UTF-8 -*-
import numpy as np
from functools import reduce
"""
函数说明:创建实验样本
Parameters:
无
Returns:
postingList - 实验样本切分的词条
classVec - 类别标签向量
Author:
Jack Cui
Blog:
http://blog.csdn.net/c406495762
Modify:
2017-08-11
"""
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 #返回实验样本切分的词条和类别标签向量
"""
函数说明:将切分的实验样本词条整理成不重复的词条列表,也就是词汇表
Parameters:
dataSet - 整理的样本数据集
Returns:
vocabSet - 返回不重复的词条列表,也就是词汇表
Author:
Jack Cui
Blog:
http://blog.csdn.net/c406495762
Modify:
2017-08-11
"""
def createVocabList(dataSet):
vocabSet = set([]) #创建一个空的不重复列表
for document in dataSet:
vocabSet = vocabSet | set(document) #取并集
return list(vocabSet)
"""
函数说明:根据vocabList词汇表,将inputSet向量化,向量的每个元素为1或0
Parameters:
vocabList - createVocabList返回的列表
inputSet - 切分的词条列表
Returns:
returnVec - 文档向量,词集模型
Author:
Jack Cui
Blog:
http://blog.csdn.net/c406495762
Modify:
2017-08-11
"""
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 #返回文档向量
"""
函数说明:朴素贝叶斯分类器训练函数
Parameters:
trainMatrix - 训练文档矩阵,即setOfWords2Vec返回的returnVec构成的矩阵
trainCategory - 训练类别标签向量,即loadDataSet返回的classVec
Returns:
p0Vect - 侮辱类的条件概率数组
p1Vect - 非侮辱类的条件概率数组
pAbusive - 文档属于侮辱类的概率
Author:
Jack Cui
Blog:
http://blog.csdn.net/c406495762
Modify:
2017-08-12
"""
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数组,
p0Denom = 0.0; p1Denom = 0.0 #分母初始化为0.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 #相除
p0Vect = p0Num/p0Denom
return p0Vect,p1Vect,pAbusive #返回属于侮辱类的条件概率数组,属于非侮辱类的条件概率数组,文档属于侮辱类的概率
"""
函数说明:朴素贝叶斯分类器分类函数
Parameters:
vec2Classify - 待分类的词条数组
p0Vec - 侮辱类的条件概率数组
p1Vec -非侮辱类的条件概率数组
pClass1 - 文档属于侮辱类的概率
Returns:
0 - 属于非侮辱类
1 - 属于侮辱类
Author:
Jack Cui
Blog:
http://blog.csdn.net/c406495762
Modify:
2017-08-12
"""
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = reduce(lambda x,y:x*y, vec2Classify * p1Vec) * pClass1 #对应元素相乘
p0 = reduce(lambda x,y:x*y, vec2Classify * p0Vec) * (1.0 - pClass1)
print('p0:',p0)
print('p1:',p1)
if p1 > p0:
return 1
else:
return 0
"""
函数说明:测试朴素贝叶斯分类器
Parameters:
无
Returns:
无
Author:
Jack Cui
Blog:
http://blog.csdn.net/c406495762
Modify:
2017-08-12
"""
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'] #测试样本1
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #测试样本向量化
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类') #执行分类并打印分类结果
else:
print(testEntry,'属于非侮辱类') #执行分类并打印分类结果
testEntry = ['stupid', 'garbage'] #测试样本2
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry)) #测试样本向量化
if classifyNB(thisDoc,p0V,p1V,pAb):
print(testEntry,'属于侮辱类') #执行分类并打印分类结果
else:
print(testEntry,'属于非侮辱类') #执行分类并打印分类结果
if __name__ == '__main__':
testingNB()
2.连续型特征
如果特征向量的分量是连续型随机变量,可以假设它们服从正态分布(高斯分布)。根据训练样本集可以计算出正态分布的均值和方差,这可以通过最大似然估计得到。连续型随机变量不能计算它在某一点的概率,因为它在任何一点处的概率为0。我们可以直接用概率密度函数的值作为概率值,得到各类别的概率。
我们通过高斯贝叶斯模型对Iris数据集进行花的类别识别。该数据集包含Iris花的三个品种(Iris setosa, Iris virginica and Iris versicolor)各50个样本,每个样本还有4个特征参数(分别是萼片<sepals>的长宽和花瓣<petals>的长 宽,以厘米为单位)。
(来自《统计学习方法》的python实现)
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
import math
#data
def create_data():
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
#data = df.iloc[:100,:]
data = np.array(df)
#print (data)
return data[:,:-1],data[:,-1]
class NaiveBayes:
def __init__(self):
self.model = None
#数学期望
@staticmethod
def mean(X):
return sum(X) / float(len(X))
#标准差(方差)
def stdev(self, X):
avg = self.mean(X)
return math.sqrt(sum([pow(x-avg,2) for x in X]) / float(len(X)))
#概率密度函数
def gaussian_probability(self, x, mean, stdev):
exponent = math.exp(-(math.pow(x-mean, 2)/(2*math.pow(stdev,2))))
return (1/ (math.sqrt(2*math.pi) * stdev)) * exponent
#处理X_train
def summarize(self, train_data):
summarizes = [(self.mean(i), self.stdev(i)) for i in zip(*train_data)]
return summarizes
#分类别求出数学期望和标准差
def fit(self, X, y):
labels = list(set(y))
data = {label:[] for label in labels}
for f, label in zip(X, y):
data[label].append(f)
self.model = {label: self.summarize(value) for label, value in data.items()}
return 'guassianNB train done'
#计算概率
def calculate_probabilities(self, input_data):
probabilities = {}
for label, value in self.model.items():
probabilities[label] = 1
for i in range(len(value)):
mean, stdev = value[i]
probabilities[label] *= self.gaussian_probability(input_data[i], mean, stdev)
return probabilities
#类别
def predict(self, X_test):
label = sorted(self.calculate_probabilities(X_test).items(), key=lambda x: x[-1])[-1][0]
return label
def score(self, X_test, y_test):
right = 0
for X,y in zip(X_test, y_test):
label = self.predict(X)
if label == y:
right += 1
return right / float(len(X_test))
if __name__ == '__main__':
X,y = create_data()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
model = NaiveBayes()
model.fit(X_train, y_train)
score = model.score(X_test, y_test)
print(score)
pre_label = model.predict([4.4, 3.2, 1.3, 0.2])
print(pre_label)
3.sklearn实现
朴素贝叶斯是一类比较简单的算法,scikit-learn中朴素贝叶斯类库的使用也比较简单。在scikit-learn中,一共有3个朴素贝叶斯的分类算法类。分别是GaussianNB,MultinomialNB和BernoulliNB。其中GaussianNB就是先验为高斯分布的朴素贝叶斯,MultinomialNB就是先验为多项式分布的朴素贝叶斯,而BernoulliNB就是先验为伯努利分布的朴素贝叶斯。
官方英文文档地址:http://scikit-learn.org/dev/modules/generated/sklearn.naive_bayes.MultinomialNB.html
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB, BernoulliNB, MultinomialNB #高斯模型、伯努利模型、多项式模型
#data
def create_data():
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
#data = df.iloc[:100,:]
data = np.array(df)
#print (data)
return data[:,:-1],data[:,-1]
if __name__ == '__main__':
X,y = create_data()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
clf = GaussianNB()
clf.fit(X_train, y_train)
score = clf.score(X_test, y_test)
print(score)
pre_label = clf.predict([[4.4, 3.2, 1.3, 0.2]])
print(pre_label)
三、算法总结
朴素贝叶斯优点:
- 生成式模型,通过计算概率来进行分类,可以用来处理多分类问题。
- 对小规模的数据表现很好,适合多分类任务,适合增量式训练,算法也比较简单。
- 对数据的训练快,分类也快
朴素贝叶斯缺点:
- 对输入数据的表达形式很敏感。
- 由于朴素贝叶斯的“朴素”特点,所以会带来一些准确率上的损失。
- 需要计算先验概率,分类决策存在错误率。
应用场景:
- 文本分类/垃圾文本过滤/情感判别
- 多分类实时预测
- 推荐系统
四、面试题
1.什么是贝叶斯决策理论? 贝叶斯决策理论是主观贝叶斯派归纳理论的重要组成部分。贝叶斯决策就是在不完全情报下。对部分未知的状态用主观概率估计,然后用贝叶斯公式对发生的概率进行修正,最后利用期望值和修正概率做出最优决策(选择概率最大的类别)。其基本思想是:(1)已知类条件概率密度参数表达式和先验概率(2)利用贝叶斯公式转化为后验概率(3)根据后验概率大小进行决策分类
2.朴素贝叶斯算法的前提假设是什么? 朴素贝叶斯则是建立在两个前提假设上的:(1)特征之间相互独立(2)每个特征同等重要
3.什么是朴素贝叶斯的零概率问题? 零概率问题,就是在计算实例的概率时,如果某个量x,在训练集中未出现过,会导致整个实例的概率结果为0。可以使用拉普拉斯平滑解决,具体做法是给分子分母同时加上一个正数。如果特征分量的取值有k种情况,将分母加上k,每个类的分子加上1,这样可以保证所有类的条件概率加起来还是1。
4.当数据的属性是连续变量时,朴素贝叶斯算法如何处理? 当数据的属性是连续变量时,有两种方法可以计算属性的条件概率。(1)把一个连续的属性离散化,然后用相应的离散区间替换连续值。(2)假设连续变量服从某种概率分布,然后使用训练数据估计分布的参数,例如使用高斯分布。
5.朴素贝叶斯有哪几种常用的分类模型? 朴素贝叶斯的三个常用模型:高斯、多项式、伯努利
6.高度相关的特征对朴素贝叶斯有什么影响? 假设有两个特征高度相关,相当于该特征在模型中发挥了两次作用(计算两次条件概率),使得朴素贝叶斯获得的结果向该特征所希望的方向进行了偏移,影响了最终结果的准确性,所以朴素贝叶斯算法应先处理特征,把相关特征去掉。