《统计学习方法》李航 第4章 朴素贝叶斯
《机器学习》周志华 第7章 贝叶斯分类器
《机器学习实战》第4章 基于概率论的分类方法:朴素贝叶斯
朴素贝叶斯中“朴素“的含义:只做了最原始、最简单的假设——特征条件独立
朴素贝叶斯法:基于贝叶斯定理和特征条件独立假设的分类方法
实际上是学到生成数据的机制,属于生成模型
特征条件独立假设:用于分类的特征在类确定的条件下都是条件独立的。
朴素贝叶斯主要用于多分类(特征大多是类别型的,当然对于连续型的数据也有相应的处理方法)
常见应用:文档分类
先验概率vs后验概率
先验概率:根据以往经验分析得到的概率(eg.所有人中患肝癌的概率P(B))
后验概率:基于新的信息,利用贝叶斯公式,对先验概率进行修正得到的(eg.被血清…法测出患肝癌的人,的确患肝癌的概率P(B|A))
判别式模型vs生成式模型
判别式模型(discriminative probability):给定x,通过直接建模 P ( c ∣ x ) P(c|x) P(c∣x) 来预测c
(eg.决策树、BP神经网络、支持向量机等)
生成式模型(generative probability):先对联合概率分布 P ( x , c ) P(x,c) P(x,c) 建模,然后由此获得 P ( c ∣ x ) P(c|x) P(c∣x)
贝叶斯公式(条件概率公式):
P ( c ∣ x ) = p ( x ∣ c ) p ( c ) p ( x ) P(c|x)=\frac{p(x|c)p(c)}{p(x)} P(c∣x)=p(x)p(x∣c)p(c)
其中:
P ( c ) P(c) P(c)是类的先验概率(表达了样本空间中各类样本所占的比例),
P ( x ∣ c ) P(x|c) P(x∣c)是样本 x x x对于类 c c c 的条件概率,或称“似然”,
P ( x ) P(x) P(x)是“证据”因子
估计 P ( c ∣ x ) P(c|x) P(c∣x) --> 基于训练集D,估计先验概率 P ( c ) P(c) P(c)和似然 P ( x ∣ c ) P(x|c) P(x∣c)
实际背景:“执果溯因”
已知某个实验结果,探讨每个原因导致这一结果的可能性大小。
<----待补充证明---->
0-1损失函数时期望风险最小化 等价于 后验概率最大化
假设样本类别数为 K K K,特征 x ( j ) x^{(j)} x(j)可取 S j S_j Sj个值
需要估计的参数有:先验概率 P ( Y = c k ) P(Y=c_k) P(Y=ck)和条件概率 P ( X ( j ) = x ( j ) ∣ Y = c k ) P(X^{(j)}=x^{(j)}|Y=c_k) P(X(j)=x(j)∣Y=ck)
因此朴素贝叶斯法估计的参数量为: K ( ∑ j = 1 n S j + 1 ) K (\sum_{j=1}^{n}S_j+1) K(∑j=1nSj+1)
根据特征条件独立假设,可得出:
P ( X = x ∣ Y = c k ) = P ( X ( 1 ) = x ( 1 ) , X ( 2 ) = x ( 2 ) , . . . , X ( n ) = x ( n ) ∣ Y = c k ) = ∏ i ∈ n P ( X ( i ) = x ( i ) ∣ Y = c k ) k = 1 , 2 , . . . , K \begin{aligned} P(X=x|Y=c_k)&=P(X^{(1)}=x^{(1)},X^{(2)}=x^{(2)},...,X^{(n)}=x^{(n)}|Y=c_k)\\ &=\prod_{i\in{n}}{P(X^{(i)}=x^{(i)}|Y=c_k)}\\ &k=1,2,...,K \end{aligned} P(X=x∣Y=ck)=P(X(1)=x(1),X(2)=x(2),...,X(n)=x(n)∣Y=ck)=i∈n∏P(X(i)=x(i)∣Y=ck)k=1,2,...,K
上式中连乘操作易造成下溢(过多较小的数相称造成的影响),常使用对数似然
L L ( c k ) = l o g P ( X = x ∣ Y = c k ) = ∑ i ∈ n l o g P ( X ( i ) = x ( i ) ∣ Y = c k ) LL(c_k)=logP(X=x|Y=c_k)=\sum_{i\in{n}}{logP(X^{(i)}=x^{(i)}|Y=c_k)} LL(ck)=logP(X=x∣Y=ck)=i∈n∑logP(X(i)=x(i)∣Y=ck)
最大似然估计可能会出现概率值为0的情况
贝叶斯估计:在极大似然估计的基础上进行了平滑处理,防止因训练集样本不充分而出现概率为0的情况
令 S j S_j Sj 表示第j个属性可能的取值数目,则条件概率的贝叶斯估计为:
P ( X ( j ) = a j l ∣ Y = c k ) = ∑ i = 1 N I ( x i ( j ) = a j l , y i = c k ) + λ ∑ i = 1 N I ( y i = c k ) + S j λ P(X^{(j)}=a_{jl}|Y=c_k)=\frac{\sum_{i=1}^NI(x_i^{(j)}=a_{jl},y_i=c_k)+\lambda}{\sum_{i=1}^NI(y_i=c_k)+S_j\lambda} P(X(j)=ajl∣Y=ck)=∑i=1NI(yi=ck)+Sjλ∑i=1NI(xi(j)=ajl,yi=ck)+λ
其中, λ \lambda λ为平滑因子,可以是整数或小数, λ = 1 \lambda=1 λ=1为拉普拉斯平滑; λ = 0 \lambda=0 λ=0时为最大似然估计
对于连续属性,以下代码通过计算类条件下的概率密度计算条件概率
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
# 载入数据集
iris = load_iris()
X = iris['data']
Y = iris['target']
X = pd.DataFrame(X, columns= ['sepL','sepW','petL','petW'])
Y = pd.DataFrame(Y,columns = ['c'])
# 划分数据集
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.2, random_state = 1)
'''
连续属性的处理:(此处采用第一种方法)
1.假设连续属性是正态分布的,通过样本计算出均值和方差,即得到正态分布的密度函数,通过密度函数
将值代入,算出某一点的密度函数值
2.将连续属性离散化
'''
# 计算均值和方差
def getMeanStd(X):
X_mean = np.mean(X)
X_std = np.std(X)
return X_mean, X_std
# 计算连续属性的概率密度
def calGaussProb(x,x_mean,x_std):
one = np.ones(x.shape)
ex = np.exp(-np.power(x-x_mean,2)/(2*np.power(x_std,2)))
gaussProb = one/(np.sqrt(2*np.pi)*x_std) * ex
return gaussProb
# 计算得到先验概率、类条件下概率密度的均值和方差
def trainNB(X_train, Y_train):
# 类别数目
K = len(set(Y_train['c']))
# 统计训练集中不同类别的样本数
X_y = []
for i in range(K):
X_y.append(X_train.loc[Y_train[Y_train['c']==i].index])
# 计算先验概率
N_train = Y_train.shape[0]
prior = []
for i in range(K):
prior.append(X_y[i].shape[0]/N_train)
X_y_mean = []
X_y_std = []
for i in range(K):
mean, std = getMeanStd(X_y[i])
X_y_mean.append(mean)
X_y_std.append(std)
return prior, X_y_mean, X_y_std
def testNB(prior, X_y_mean, X_y_std, X):
K = 3
Y_pred = []
for i in range(X.shape[0]):
post = []
for k in range(K):
gaussProb = calGaussProb(X.iloc[i], X_y_mean[k], X_y_std[k])
res = sum(np.log(gaussProb)) + np.log(prior[k])
post.append(res)
Y_pred.append(post.index(max(post)))
return Y_pred
prior, X_y_mean, X_y_std = trainNB(X_train, Y_train)
Y_pred1 = testNB(prior, X_y_mean, X_y_std, X_test)
print(Y_pred1)
# 将预测结果与sklearn中的GaussianNB比较,基本一致
from sklearn.naive_bayes import GaussianNB
clf = GaussianNB()
clf.fit(X_train,Y_train)
Y_pred = clf.predict(X_test)
print(Y_pred)
print(Y_pred1 == Y_pred)
'''
《机器学习实战》
'''
from numpy import *
'''
postingList: 进行词条切分后的文档集合
classVec:类别标签
'''
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([])#使用set创建不重复词表库
#包含所有文档的单词
for document in dataSet:
vocabSet = vocabSet | set(document) #创建两个集合的并集
#将单词集合转化成列表并返回
return list(vocabSet)
'''根据词汇表中的单词是否在文档中出现(出现1,未出现0),将文档转化为单词向量'''
def setOfWords2Vec(vocabList, inputSet):
#创建一个所包含元素都为0的向量
returnVec = [0]*len(vocabList)
#遍历文档中的所有单词,如果出现了词汇表中的单词,则对应值设为1
for word in inputSet:
if word in vocabList:
#通过index获得当前单词的下标
returnVec[vocabList.index(word)] = 1
else: print("the word: %s is not in my Vocabulary!" % word)
return returnVec
'''
我们将每个词的出现与否作为一个特征,这可以被描述为词集模型(set-of-words model)。
如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,
这种方法被称为词袋模型(bag-of-words model)。
在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。
为适应词袋模型,需要对函数setOfWords2Vec稍加修改,修改后的函数称为bagOfWords2VecMN
'''
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
'''
朴素贝叶斯训练函数
trainMat:由每篇文档的单词向量构成的文档矩阵
trainCate:每篇文档的类别标签构成的向量
'''
def trainNB0(trainMat,trainCate):
#计算文档数目
numTrainDocs=len(trainMat)
#获得每篇文档的单词个数
numWords=len(trainMat[0])
#计算侮辱性文档出现的概率p(c=1)
pAbusive=sum(trainCate)/float(numTrainDocs)
#采用极大似然估计
#p0Num=zeros(numWords);p1Num=zeros(numWords)
#为了防止出现概率值为0的情况,采用贝叶斯估计 lambda=1,拉普拉斯平滑
#初始化两个矩阵,值为1
p0Num=ones(numWords);p1Num=ones(numWords)
p0Denom=2.0;p1Denom=2.0
#遍历每一篇文档的单词向量
for i in range(numTrainDocs):
#如果文档属于侮辱性文档
if trainCate[i]==1:
#统计所有类别为1的单词向量中各个单词出现的次数
p1Num+=trainMat[i]
#统计类别为1的所有文档中出现的单词数目
p1Denom+=sum(trainMat[i])
#print(p1Num,p1Denom)
else:
p0Num+=trainMat[i]
p0Denom+=sum(trainMat[i])
#对结果取对数,避免下溢出(过多较小的数相称造成的影响)
#计算条件概率p(wi|c=1)
p1Vect=log(p1Num/p1Denom)
#计算条件概率p(wi|c=0)
p0Vect=log(p0Num/p0Denom)
return p0Vect,p1Vect,pAbusive
#朴素贝叶斯分类函数
#vec2Classify:待测试分类的单词向量
#p0Vec:类别0所有文档中各个单词出现的频数p(wi|c=0)
#p0Vec:类别1所有文档中各个单词出现的频数p(wi|c=1)
#pClass1:类别为1的文档占文档总数比例
def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1) #元素相乘
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1>p0:
return 1
else:
return 0
if __name__=='__main__':
#产生文档矩阵和对应的标签
listOPosts,listClasses = loadDataSet()
#创建词汇表
myVocabList = createVocabList(listOPosts)
#创建一个空的列表
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList,postinDoc)) #使用词向量来填充trainMat列表
p0V,p1V,pAb = trainNB0(array(trainMat),array(listClasses)) #训练函数
testEntry = ['love','my','dalmation'] #测试文档列表
thisDoc = array(setOfWords2Vec(myVocabList,testEntry)) #声明矩阵
print(thisDoc)
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))
testEntry = ['stupid','garbage']
thisDoc = array(setOfWords2Vec(myVocabList,testEntry)) #声明矩阵
print(testEntry,'classified as:',classifyNB(thisDoc,p0V,p1V,pAb))