朴素贝叶斯是一种生成式模型,不同于之前讲过的k-近邻,支持向量机,决策树等判别式模型。生成式模型:需要计算x,y的联合概率分布!并且是比较各分类概率大小,来决定分类。
判别式模型:只需要计算y的概率分布!需要从训练出的模型中计算分类概率。
关于判别式模型与生成式模型,请参考这篇文章求知鸟:关于统计学的思考(3)
作为一名励志成为技师的我来说,当然不想被人嘲笑为”调包侠“,于是就有了接下来这篇文章:如何自编程实现朴素贝叶斯。
本文内容如下:
demo:
原理公式
算法实现
实战:使用Python进行文本分类
数据准备:从文本中构建词向量
伪代码:从词向量计算概率
算法实现
原理公式
贝叶斯原理解决了数学中的“逆向概率问题”,在这个基础上,人们设计出了贝叶斯分类器,朴素贝叶斯是贝叶斯分类中最简单的分类器,之所以称为朴素,是假设属性是相互独立的;而实际上,相互独立的属性在生活中是不存在的,这就会导致分类正确率降低,好在错误率并不高!朴素贝叶斯适应于离散性数据 ;
高斯朴素贝叶斯:特征变量是连续变量,符合高斯分布
多项式朴素贝叶斯:特征变量是离散变量,符合多项分布
伯努利朴素贝叶斯:特征变量是0,1分布
有了朴素贝叶斯模型,还需要对模型进行求解,一是用极大似然估计进行求解
一是用贝叶斯估计求解参数
由于极大似然估计无法应对分母为0的情形,所以一般采用贝叶斯估计:公式1.1公式1.2公式1.3
可能,你不了解上述公式代表的意义,没关系,接下来这张图片会帮你搭建起”积木中的全局视野“,这样就可以清晰的理解上述公式了:想要更清楚的了解,可以参考李航博士的《统计学习方法》
或是之前写的一篇文章求知鸟:朴素贝叶斯分类:原理公式1.4
等式左边是我们要求的后验概率,我们就依据这个后验值的比较,来进行分类;请注意:我用的关键字是:后验概率值的比较!比较就意味着有多个!
等式右边:分母在分类中,不同类别的计算是一样的,既然没有区别,我们不care它,分子可以抽出两部分:
对应着本文公式的1.2;
对应1.1!
算法实现
先构建主函数
import numpy as np
import pandas as pd
def main():
X_train=np.array([
[1,"S"],
[1,"M"],
[1,"M"],
[1,"S"],
[1,"S"],
[2,"S"],
[2,"M"],
[2,"M"],
[2,"L"],
[2,"L"],
[3,"L"],
[3,"M"],
[3,"M"],
[3,"L"],
[3,"L"]
])
y_train=np.array([-1,-1,1,1,-1,-1,-1,1,1,1,1,1,1,1,-1])
clf=NaiveBayes(lambda_=0.2)
clf.fit(X_train,y_train)
X_new=np.array([2,"S"])
y_predict=clf.predict(X_new)
print("{}被分类为:{}".format(X_new,y_predict))
if __name__=="__main__":
main()主函数中出现了训练样本集:输入特征与输出类别标签;
还定义了测试样本集:输入特征X_new;(demo就取了一个样本)
主函数还引用了类-实例中的方法进行分类预测。
#定义朴素贝叶斯中的类-方法-实例
class NaiveBayes():
def __init__(self,lambda_): #变量的初始化
self.lambda_=lambda_ #贝叶斯系数 取0时,即为极大似然估计
self.y_types_count=None #y的(类型:数量)
self.y_types_proba=None #y的(类型:概率)
self.x_types_proba=dict() #(xi 的编号,xi的取值,y的类型):概率
#对应公式1.1
def fit(self,X_train,y_train):
self.y_types=np.unique(y_train) #y的所有去除重复取值类型
X=pd.DataFrame(X_train) #转化成pandas DataFrame数据格式,下同
y=pd.DataFrame(y_train)
# DataFrame数据结构,先从列开始,y[0]就是第一列;y的(类型:数量)统计:告诉你-1出现了9次,1出现了6次
self.y_types_count=y[0].value_counts()
# y的(类型:概率)计算
self.y_types_proba=(self.y_types_count+self.lambda_)/(y.shape[0]+len(self.y_types)*self.lambda_)
#对应公式1.2
# (xi 的编号,xi的取值,y的类型):概率的计算
for idx in X.columns: # 遍历X的列名0,1
for j in self.y_types: # 选取每一个y的类型,-1,1
p_x_y=X[(y==j).values][idx].value_counts() #选择所有y==j为真的数据点的第idx个特征的值,并对这些值进行(类型:数量)统计
for i in p_x_y.index: #计算(xi 的编号,xi的取值,y的类型):概率;元组
self.x_types_proba[(idx,i,j)]=(p_x_y[i]+self.lambda_)/(self.y_types_count[j]+p_x_y.shape[0]*self.lambda_)
#对应公式1.3
def predict(self,X_new):
res=[] #空列表,可变数据
for y in self.y_types: #遍历y的可能取值
p_y=self.y_types_proba[y] #计算y的先验概率P(Y=ck),字典
p_xy=1
for idx,x in enumerate(X_new):
p_xy*=self.x_types_proba[(idx,x,y)] #计算P(X=(x1,x2...xd)/Y=ck)
res.append(p_y*p_xy)
for i in range(len(self.y_types)):
print("[{}]对应概率:{:.2%}".format(self.y_types[i],res[i]))
#返回最大后验概率对应的y值
return self.y_types[np.argmax(res)]数据结构可以参考这篇,求知鸟:Python科学计算:庖丁解牛之Pandas
计算起来还是有点饶的,p_x_y=X[(y==j).values][idx].value_counts();这一行尤其难理解!
我们先来看单个索引:
df1 = pd.DataFrame( np.array([[1, 2, 3], [4, 2, 6],[7,8,9]]) )
print(df1)
print(df1[1])
print(df1[1].value_counts)#对第二列统计出现次数
print(df2[2:2])
print(df1[2:2].value_counts)#不能对行统计出现次数
print(df2[1][1].value_counts)#不能对索引值统计出现次数以上说明,value_counts()只能用在某列上,不能用在某行,某个具体值上!
索引某个值,遵循[columns][index]的格式,可下面的功能显然不是!
p_x_y=X[(y==j).values][idx].value_counts()
idx可选项{0,1},循环遍历
j的可选项{-1,1},循环遍历!
y是[-1,-1,1,1,-1,-1,-1,1,1,1,1,1,1,1,-1]
当idx=0时
当j=-1时,依次判断j==y,匹配上,统计第1列出现次数
当j=1时,依次判断j==y;匹配上,统计第1列出现次数
内层循环结束;
外层循环idx=1时
当j=-1时,依次判断j==y,匹配上,统计第2列出现次数
当j=1时,依次判断j==y;匹配上,统计第2列出现次数
打印结果如下:
1 3
2 2
3 1
Name: 0, dtype: int64
3 4
2 3
1 2
Name: 0, dtype: int64
S 3
M 2
L 1
Name: 1, dtype: int64
M 4
L 4
S 1
实战:使用Python进行文本分类
从文本中构建词向量
#创建了一个实验样本。返回的第一个变量是词条集合,第二个变量是类别标签(人工标注)
def loadDataSet():
postingList=[['my','dog','is','ugly'],['my','girlfriend', 'will', 'be','wd'],['the', 'day', 'is', 'garbage'],['tomorrow', 'will', 'be', 'sunny']]
classVec=[1,0,1,0]
return postingList,classVec
#创建一个不重复出现词的列表
def createVocabList(dataSet):
vocabSet=set([])#创建一个无序不重复元素集
for document in dataSet:
vocabSet=vocabSet|set(document)#&交,|或,-差运算
return list(vocabSet)
def setOfWorlds2Vec(vocabList,inputSet):
returnVec=[0]*len(vocabList)#创建一个和词汇表等长的全0向量
for word in range(inputSet):#注意加range,否则报错TypeError: ‘int’ object is not iterable!
if word in vocabList:
returnVec[vocabList.index(word)]=1#如果出现了词汇表中的单词,则将输出文档向量中的值设为1
else:
print("the world :%s is not in my Vocabulary!" %word)
return returnVec
#检测下上述函数的运行效果
listOPosts,listClasses=loadDataSet()
myVocabList=createVocabList(listOPosts)
print(myVocabList)
print(setOfWorlds2Vec(myVocabList,listClasses[0]))def():要知道传入的是你的输入,要写进括号内,返回的是你想要的结果,用return!
循环中使用的应该是一个数组:for word in range(inputSet):
伪代码:从词向量计算概率
计算每个类别中的文档数目
对每篇训练文档:
对每个类别:
如果词条出现在文档中---》增加该词条的计数值
增加所有词条的计数值
对每个类别:
对每个词条:
将该词条的数目除以总词条数目得到条件概率
返回每个类别的条件概率
def trainNB0(trainMatrix,trainCategory): #传入参数:文档矩阵trainMatrix,和每篇文档类别标签所构成的向量trainCategory
numTrainDocs=len(trainMatrix)
numWords=len(trainMatrix[0])
pAbusive=sum(trainCategory)/float(numTrainDocs)
p0Deom=0.0
plDemon=0.0
for i in range(numTrainDocs):
if trainCategory[i]==1:
plNum+=trainMatrix[i]
plDenom+=sum(trainMatrix[i])
else:
p0Num+=trainMatrix[i]
p0Denom+=sum(trainMatrix[i])
p1Vect=p1Num/p1Denom
p0Vect=p0Num/p0Denom
return p0Vect,p1Vect,pAbusive
#加载数据
listOPosts,listClasses=loadDataSet()
myVocabList=createVocabList(listOPosts)
#构建一个包含所有词的列表
trainMat=[]
for postinDoc in listOPosts:
trainMat.append(setOfWorlds2Vec(myVocabList,postinDoc))
p0V,p1V,pAb=trainNB0(trainMat,listClasses)还没仔细研究,感觉没有demo中的方法巧妙!
总结:
朴素贝叶斯算法原理:朴素贝叶斯提出是为了解决“逆向概率问题”---知道结果推原因举例:患癌症的概率是万分之一(先验概率),医院判断一个人是否得病的正确率是99.9%,误判的概率是0.1%;--条件概率(先验概率)
如果一个人被查出来患有癌症,实际上患有的可能性有多大?---这就是求后验概率!
用数学语言来讲:
知道每个类别概率和各个属性下的条件概率,来求后验概率!
朴素贝叶斯分类器的实现步骤:
朴素贝叶斯分类器模型,本质上是个生成模型:需要知道x,y的联合概率分布,不像判别模型只需要知道y的概率分布就好!具体在分类上,是通过比分类概率大小来预测类别!举例:现在有一只羊,想知道它是山羊还是绵羊?我们先把这只羊的特征带入山羊的训练模型中,得出一个概率值,然后再带入绵羊的训练模型中得出一个概率,比较这两个概率大小,大的就是我们最终的分类!
用数学语言讲:1、求各类别概率P(Y=Ck)
2、求联合概率分布
3、比较选择概率最大的类(不涉及分母,因为类别之间都一样)
朴素贝叶斯的假设:各属性之间相互独立!
对数据要求:朴素贝叶斯适应于离散性数据 ;
高斯朴素贝叶斯:特征变量是连续变量,符合高斯分布(正态分布)
多项式朴素贝叶斯:特征变量是离散变量,符合多项分布
伯努利朴素贝叶斯:特征变量是0,1分布
朴素贝叶斯估计参数的方法:极大似然估计:因为不能应对极端情况--分母为0的情况,一般选择贝叶斯估计!
贝叶斯估计:在样本量无穷,且先验概率靠谱的局面下,极大似然估计与贝叶斯估计的结果是一样的!
若是数据量有限的情况下,就计算复杂度来说,选择极大似然估计(极大似然估计就涉及求导,贝叶斯不仅求导,还涉及多重积分),就计算准确性而言,选择贝叶斯估计,毕竟贝叶斯估计有很强的理论基础!
朴素贝爷斯算法优缺点:
优点:对小规模的数据表现很好,能个处理多分类任务,适合增量式训练,尤其是数据量超出内存时,我们可以一批批的去增量训练。
对缺失数据不太敏感,算法也比较简单,常用于文本分类。
缺点:朴素贝叶斯假设各属性之间独立,现实很难满足,这就导致分类有天然的误差!
需要知道先验概率,而先验概率依赖于假设!
对输入数据的表达形式很敏感。