贝叶斯分类器报告
一、报告概览
编程语言:Python3
实验环境:windows10+anaconda3.7
我的报告分为一下三部分:
- 仿真实验
- 实验题目
- 思路分析
- 思路实现(仅展示部分关键代码)
- 结果展示
- 实际应用——汽车评价分类
- 写在前面
- 实验题目
- 思路分析
- 思路实现(仅展示部分关键代码)
- 结果展示
- 总结&心得
二、仿真实验
实验题目
随机产生10000组正样本和20000负样本高斯分布的数据集合(维数设为二维),要求正样本:均值为[1;3],方差为[2 0;0 2];负样本:均值为[10;20],方差为[10 0;0 10]。先验概率按样本量设定为1/3和2/3.分别利用最小错误概率贝叶斯分类器和最小风险概率贝叶斯分类器对其分类。(假设风险程度正样本分错风险系数为0.6,负样本分错风险为0.4,该设定仅用于最小风险分析)
思路分析
样本数据集:
30000个二维样本点,有类标,各类样本服从各自二维正态分布,特征值是连续数值型
贝叶斯分类器设计:
模型评估阶段:
- 按比例 r 将30000个样本分层分割成训练集和测试集;
- 统计训练集的均值,方差,协方差;
- 由于分层抽样得到训练集,所以先验概率保持不变;
- 朴素贝叶斯和不要求朴素的贝叶斯两种方式计算条件概率密度,已知分布采用最大似然估计
(朴素:根据第2步已求出的一维正态分布公式计算各类标的条件概率密度,然后相乘p(x|wi)*p(x|wi);
不要求朴素:根据第2步已求出的二维正态分布公式(下图)计算条件概率密度) - 朴素贝叶斯和不要求朴素的贝叶斯两种方式计算全概率,然后得出后验概率
- 贝叶斯决策:选择最小错误率的那个,选择最小风险的那个,两种
- 测试:将每个测试用例进行4,5,6步,记录错误分类个数,以及loss
- 更改比例 r 重新来一次
训练模型用以实际预测工作阶段:
- 计算后验概率时,将朴素和不要求朴素两种方式所需要估计的{方差,均值}和{协方差矩阵,均值向量},改为3万个样本的。
- 预测:按题目中给定的统计量,按照最先随机生成3万样本集的方式,再生成大小为n的预测集,去模拟实际需要预测的数据。
(不妥之处:实际全集分布我们是不知道的,所有按样本的分布去模拟新的实际需要预测的数据必然是不妥的;
所以,这个预测工作仅为模拟,因为没有实际全集中的更多数据)
思路实现(仅展示关键代码)
- 随机出3万个样本
xN, yN = np.random.multivariate_normal(meanN, covN, 20000).T
xP, yP = np.random.multivariate_normal(meanP, covP, 10000).T
- 训练集测试集分割(以9:1为例)
random.shuffle(xN)
random.shuffle(yN)
xNTrain,yNTrain=xN[0:18000],yN[0:18000]
- 求训练集统计量
xNTrain_mean=np.mean(xNTrain)
xNTrain_var=np.var(xNTrain)
meanN=xNTrain_mean,yNTrain_mean
covN=np.cov([xNTrain,yNTrain])
#先验概率
priorN=2/3
priorP=1/3
- 计算正态分布某一点的条件概率密度
def getProbabilityDensity(x,mean,var):
#x是单维
return (2*math.pi*var)**-0.5*math.exp(-0.5*(x-mean)**2*var**-1)
def getProbabilityDensity2(x,mean,cov):
#x是2维,不要求朴素
x=np.mat(x)
mean=np.mat(mean)
return ((2*math.pi)**2*np.linalg.det(cov))**-0.5*math.exp(-0.5*((x-mean)*np.linalg.inv(cov)*(x-mean).T))
- #求后验概率
def getProbability(isNaive,isN,x):
#x是2维
if isNaive:
#朴素贝叶斯
xNPD=getProbabilityDensity(x[0],xNTrain_mean,xNTrain_var)
yNPD=getProbabilityDensity(x[1],yNTrain_mean,yNTrain_var)
xPPD=getProbabilityDensity(x[0],xPTrain_mean,xPTrain_var)
yPPD=getProbabilityDensity(x[1],yPTrain_mean,yPTrain_var)
XNP=xNPD*yNPD*priorN
XPP=xPPD*yPPD*priorP
else:
#协方差版,不要求朴素
XNPD=getProbabilityDensity2(x,meanN,covN)
XPPD=getProbabilityDensity2(x,meanP,covP)
XNP=XNPD*priorN
XPP=XPPD*priorP
XP=XNP+XPP
if isN:
return XNP/XP
else:
return XPP/XP
- 最小化错误率决策:return是否分为负类
def minErrorDecision(isNaive,x):
postProbabilityN=getProbability(isNaive,True,x)
postProbabilityP=getProbability(isNaive,False,x)
if postProbabilityN>postProbabilityP:
return True
else:
return False
- #最小化风险决策:return是否分为负类
def minRiskDecision(isNaive,x):
postProbablityN=getProbability(isNaive,True,x)
postProbablityP=getProbability(isNaive,False,x)
riskN=postProbablityP*0.6
riskP=postProbablityN*0.4
if riskN<riskP:
return True
else:
return False
- test&predict,俩函数类似
def test(isNaive):
countErrorN=0
countErrorP=0
countRiskErrorN=0
countRiskErrorP=0
errorloss=0
riskloss=0
for i in range(2000):
x=xNTest[i],yNTest[i]
if not minErrorDecision(isNaive,x):
countErrorN=countErrorN+1
errorloss=errorloss+0.4
if not minRiskDecision(isNaive,x):
countRiskErrorN=countRiskErrorN+1
riskloss=riskloss+0.4
for i in range(1000):
x=xPTest[i],yPTest[i]
if minErrorDecision(isNaive,x):
countErrorP=countErrorP+1
errorloss=errorloss+0.6
if minRiskDecision(isNaive,x):
countRiskErrorP+=1
riskloss=riskloss+0.6
if isNaive:
print('朴素贝叶斯分类器错误计数: ',countErrorN,countErrorP,countRiskErrorN,countRiskErrorP,' errorloss: ',errorloss,' riskloss: ',riskloss)
else:
print('不要求朴素的贝叶斯分类器错误计数: ',countErrorN,countErrorP,countRiskErrorN,countRiskErrorP,' errorloss: ',errorloss,' riskloss: ',riskloss)
结果展示
#展示测试结果
test(True)#朴素
test(False)#不要求朴素
#再生成30万个样本去预测
predict(True,300000)
predict(False,300000)
错误个数很少,效果很好(该实验数据多,且无噪声数据,数据分布理想式;)
注:loss是根据错误个数统计的风险,由于该例错误较少,最小错误率决策和最小风险决策的差异没有很大,
最后一行,可以看到最小风险的loss是小一些的。
三、实际应用——汽车评价分类
写在前面
写了模拟实验的贝叶斯分类器,我觉得不够通用,是针对那个问题来的。
所以这次,我决定写的更加通用规范,构造一个贝叶斯分类器类。
实验题目
给定汽车评价数据集,利用贝叶斯算法进行数据分类操作,并统计其预测正确率。利用10折交叉验证,对所设计的贝叶斯分类器进行性能评估。
思路分析
数据集:
共1728个数据,每个数据特征为6维,分为4类,类别标记为unacc,acc,good,V-good
四个类别标记分别表示汽车性价比等级(由低到高)
unacc:1210个
acc:384个
good:69个
V-good:65个
6个特征分别为:(6个属性)
1、buying (取值:v-high、high、med、low) 表示购买价格
2、maint (取值: v-high、high、med、low) 表示维修价格
3、door (取值:2、3、4、5-more) 车门数量
4、Persons (取值:2、4、more) 可容纳人数
5、Lug_boot (取值:small、med、big) 行李箱大小
6、Safety (取值:low、med、high) 安全系数
贝叶斯分类器思路(仅列出与模拟实验不同的思路设计):
- 数据特征值离散,条件概率计算方式更改,需要有朴素前提,依旧秉承最大似然思想,即对一个样本点,统计其各个特征维度的取值在训练集上的概率(用频率代替概率),乘积就是该样本点的类条件概率
p(x1,x2…|wi)=p(x1|wi)p(x2|wi)…=∏num(xi)/num(datasize) - 测试评估:
- 按比例 r划分出测试集(分层抽样,独立同分布),简单评估,计算错误率;
- k折交叉验证,计算错误率;
注意:分层抽样划分,保证k部分独立同分布
- 没有训练模型用以实际预测工作阶段:
经数据分析,去重验证,car.data训练集无重复样本,且大小为1728,全有类标,是全集
所以,没有训练模型用以实际预测工作阶段,因为没有更多不同数据用来预测,没有必要。
思路实现
- 贝叶斯分类器类
class NaiveBayesClassifier(object):
def __init__(self):
self.names =[]
self.data=None #dataframe的list
self.dataSize=0
self.trainSize=0
self.trainSet=[] #dataframe的list
self.testSet=[] #dataframe的list
self.label=None #四个类标的list
self.featureDim=0
self.priorProb=[] #list
self.probability=[] #df的list的list
self.errorRate=[]#一维错误率数组,分类错误率统计
self.totalErrorRate=0 #总错误率
- 数据集准备,一些变量初始化
def preDataSet(self):
self.names = ("buying,maint,door,persons,lug_boot,safety,grade").split(',')
self.data=pd.read_csv('car.data',names=self.names)
#数据去重
self.data.drop_duplicates(subset=None, keep='first', inplace=False)
print(self.data.describe())
self.featureDim=len(self.names)-1
self.dataSize=len(self.data)
self.label=self.data[self.names[-1]].unique()
- 按类别将data重排成4个dataframe的list,便于分层抽样
def resetDataByClass(self):
temp=[]
for c in self.label:
temp.append(self.data[self.data[self.names[-1]]==c])
self.data=temp
- 按比例TrainSetRate分层抽样划分数据集为训练集和测试集的函数,两种测试评估会用到
def divideDataSet(self,TrainSetRate):
#此时的data已经按类分好
self.trainSize=int(self.dataSize*TrainSetRate)
for i in range(len(self.label)):
#按类别分层采样
self.trainSet.append(self.data[i].sample(frac=TrainSetRate))
#剩下是训练集
temp=pd.DataFrame()
temp=temp.append(self.data[i])
temp=temp.append(self.trainSet[i])
temp=temp.drop_duplicates(keep=False)
self.testSet.append(temp)
- 统计各特征维度取值概率的函数,相当于贝叶斯分类器的“训练过程”
为了方便之后随取随用,用self.probability(#dataframe的list的list)组织所有结果
def statistics(self):
templabel=[]
for i in range(len(self.label)):
tempallfeat=[]#注意!空初始化与append在同一级别
for j in range(self.featureDim):
feature=self.data[i][self.names[j]].unique()
tempfeat=pd.DataFrame(columns=feature)
tempfeat.loc[0]=range(len(feature))#df初始化
for feat in feature:
tempfeat[feat]=len(self.trainSet[i][self.trainSet[i][self.names[j]]==feat])/len(self.trainSet[i])
tempallfeat.append(tempfeat)
templabel.append(tempallfeat)
self.probability=templabel
- 先验概率(由于分层抽样,该函数只需调用一次)
def getpriorProb(self):
for i in range(len(self.label)):
self.priorProb.append(len(self.trainSet[i])/self.trainSize)
- 条件概率
训练集中没有出现过的点,概率为0
def getConditionProb(self,x,label):
#lable是index
prob=1;
#label=self.label.index(label)
for i in range(self.featureDim):
#prob=prob*float(self.probability[label][i][x[i]])
temp=self.probability[label][i]
if x[i] not in temp.columns:
return 0
prob=prob*float(temp[x[i]])
return prob
- 后验概率
def getPostProb(self,x,label):
allProb=0#先计算全概率
for i in range(len(self.label)):
allProb+=self.priorProb[i]*self.getConditionProb(x,i)
return self.getConditionProb(x,label)*self.priorProb[label]/allProb
- 最小错误率的贝叶斯决策
def bayesDecision(self,x):
#return的是label index
predictProb=[]
for i in range(len(self.label)):
predictProb.append(self.getPostProb(x,i))
return predictProb.index(max(predictProb))
- 一般测试(仅划分一次训练集那种):统计错误率
def test(self):
#用测试集计算错误率
self.errorRate=[]
totalcount=0
for i in range(len(self.label)):
count=0
for j in range(len(self.testSet[i])):
a=self.bayesDecision(list(self.testSet[i].iloc[j]))
if a != i :
count=count+1
totalcount=totalcount+count
self.errorRate.append(count/len(self.testSet[i]))
self.totalErrorRate=totalcount/self.dataSize
print('测试集各类标错误率:',self.errorRate,'总错误率:',self.totalErrorRate)
- k折交叉验证:统计平均错误率
注意:分层抽样划分,保证k部分独立同分布
def crossValidation(self,k):
avg_errorRate=np.array([0,0,0,0])
avg_totalErrorRate=0
m=k
beifen=self.data
left=[]
for i in range(k):
#由于抽样是分层的,不再计算先验概率
car.divideDataSet((m-1)/m)
if i>0 :
for i in range(len(self.label)):
self.trainSet[i]=self.trainSet[i].append(left[i])
car.statistics()
car.test()
avg_errorRate=avg_errorRate+np.array(self.errorRate)
avg_totalErrorRate=avg_totalErrorRate+self.totalErrorRate
self.data=self.trainSet
left=self.testSet
m=m-1
print(k,'折交叉验证平均各类标错误率: ',avg_errorRate/k,'总错误率:',avg_totalErrorRate/k)
self.data=beifen
**注意:**仅调用上述divideDataSet()函数是做不到既分层随机抽,又使k部分加起来就是原数据集的。
所以这里写得代码多了些。
结果展示
先用了一次一般测试(比例专门选取了0.9),再来10折交叉验证
if __name__ == '__main__':
car=NaiveBayesClassifier()
car.preDataSet()
car.resetDataByClass()
car.divideDataSet(0.9)
car.statistics()
car.getpriorProb()
car.test()
print('!!!!!!!!!!分割线!!!!!!!!!')
car.crossValidation(10)
- 通过分割线前后,两种测试验证方式的比较,可以看出,交叉验证的方法更加公正,有说服力
- 列表中是四类标分别的错误率,可以看出前两种错误率比较可观,后两种尤其最后一种,错误率偏高,也是因为后两种样本数量本身少的缘故。
(unacc:1210个 acc:384个 good:69个 V-good:65个)
四、总结&心得
- 我对贝叶斯分类器的核心思想有了熟练把握
- 工程能力提高了
- 个人认为贝叶斯分类器在特征连续的情况下更有说服力,更准确,因为很多概率论方面的理论知识可以运用进来,特征离散的情况,不同特征相独立很难保证。而且对训练集中没有的点求概率时,插值比较难实现,因为像car的离散特征很多都没有大小增量关系,onehot的话,特征之间更加不独立。
对训练集中没有的点求概率时,如果不用插值的办法,可以用拉普拉斯平滑求概率,而不是置为0
car这题还可以尝试决策树和神经网络等其他方式。