朴素贝叶斯(Naive Bayes)是基于贝叶斯定理和概率论预测样本类别的概率算法,而朴素一词的来源就是假设各特征之间相互独立。朴素贝叶斯属于监督学习的生成模型,实现简单,没有迭代,并有坚实的数学理论(即贝叶斯定理)作为支撑。在大量样本下会有较好的表现,不适用于输入向量的特征条件有关联的场景。
关于贝叶斯定理的详细介绍
拿西瓜是否成熟举例,假设判断西瓜成熟的特征有:瓜蒂是否脱落,敲打的声音-浊响|清脆
数据集是使用了分层抽样的十折交叉验证的,所以分为了十个名字相似的各类样本相近的数据集桶文件,如下:
该数据集为国会投票的样本:
分析数据集:
democrat为民主党,republican为共和党,即标签,表示类别
此后y和n表示特征,共16个特征,每个特征两个类别
存储从数据集中提取的数据,存储对数据统计分析的结果
数据分为三类:标签类别(democrat/republican),字符型特征(y/n),数值型(即连续型,该数据集没有此类型)
'''
params:
bucketPrefix: 桶文件名的前缀
testBucketNumber:测试数据所在的桶的编号
dataFormat: 数据文件格式列表
'''
total=0 #记录总数据量
classes={} #存类别出现的次数
counts={} #每个类别对应的属性值出现的次数
self.format = dataFormat.strip().split('\t') # 切分每一列的数据类型
'''
存储数值型数据信息
'''
totals={} # 每种类别对应的值的总和
numericValues={} #存每种类别对应的列的取值
'''
存储计算出的概率
'''
self.prior={} #先验概率
self.condition={} #条件概率
十个桶文件中根据传入的参数指定一个为测试数据集,其他九个都为训练数据集
for i in range(1,11): #循环10次
if i!=testBucketNumber: #判断是否为测试集
filename='%s-%02i'%(bucketPrefix,i)
f=open(filename,'r')
lines = f.readlines()
f.close()
for line in lines:
total=total+1 #统计总数
fields = line.strip().split('\t') #分割文件的每列
vector =[] #存储字符型特征
'''
保存每一条数据的数值型数据
'''
nums=[]
#分门别类存储标签和不同类型的特征
for i in range(len(fields)):
if self.format[i]=='attr':
vector.append(fields[i])
elif self.format[i]=='class':
category=fields[i]
elif self.format[i]=='num':
nums.append(float(fields[i]))
#记录每个列别出现的次数
classes.setdefault(category,0)
counts.setdefault(category,{})
classes[category]+=1
'''
记录每个数值型数据
'''
totals.setdefault(category,{})
numericValues.setdefault(category,{})
#循环处理么每条记录出现的属性值
#循环vector 取出每列值
col=0
for columnValue in vector:
col+=1
counts[category].setdefault(col,{})
counts[category][col].setdefault(columnValue,0)
counts[category][col][columnValue]+=1
'''
counts形成的结构为:
{
'democrat':
{
1: {'y': 64, 'n': 48},
2: {'n': 64, 'y': 48},
...
16: {'y': 105, 'n': 7}
},
'republican':
{
1: {'y': 22, 'n': 76},
2: {'y': 46, 'n': 52},
...
16: {'n': 30, 'y': 68}
}
}
'''
'''
保存每个数值型数据与类别的对应字典
'''
col=0
for columnValue in nums:
col+=1
totals[category].setdefault(col,0)
totals[category][col]+=columnValue
numericValues[category].setdefault(col,[])
numericValues[category][col].append(columnValue)
多项式类型数据的概率,利用贝叶斯公式(引入拉普拉斯平滑)计算先验概率,再计算条件概率
#开始计算概率
# classes: {'democrat': 112, 'republican': 98}
for (category, count) in classes.items():
self.prior[category] = count/total
#条件概率: 注意:columns是一个字典
for (category, columns) in counts.items():
self.condition.setdefault(category,{})
for (col,valueCount) in columns.items():
self.condition[category].setdefault(col, {})
# 此处引入拉普拉斯平滑处理已存在值
for (attrValue, count) in valueCount.items():
# 此处先计算所有在样本中已存在值的条件概率 ( nc+(mp))/(N+m)
m=len(counts[category][col].items())
self.condition[category][col][attrValue] = (count+1)/(classes[category]+m)
# 设置为类成员,方便在创建类对象后查看
self.counts=counts
self.classes=classes
处理连续型数值型数据的概率,因为连续型数据的分布不如多项式型数据直观,引入概率密度函数使得我们能直观的从数据上比较观察,这里先计算概率密度函数所需的均值和样本标准差
self.means={}
self.totals=totals
#先计算均值
for (category, columns) in totals.items():
self.means.setdefault(category,{})
for (col,colTotal) in columns.items():
self.means[category][col]=colTotal/classes[category]
#在计算样本标准差
self.ssd={}
for (category,columns) in numericValues.items():
self.ssd.setdefault(category,{})
for (col,values) in columns.items():
sumOfSquareDifferences=0
theMean = self.means[category][col]
for value in values:
sumOfSquareDifferences+= (value-theMean)**2
columns[col] =0
self.ssd[category][col] = math.sqrt(sumOfSquareDifferences/(classes[category]-1))
在处理完所有样本的信息后,我们已经得到样本类别的统计,各类别(标签)中每个特征的数量统计,以及各类别每个特征的概率,接下来就可以利用这些数据和贝叶斯公式预测待测样本在各类别的概率
def classify(self, itemVector, numVector):
self.results=[] # [()]
for (category, prior) in self.prior.items():
prob = prior
col=1
for attrValue in itemVector:
if not attrValue in self.condition[category][col]:
# prob=0 即nc=0 ( nc+(mp))/(N+m) = 1/(n+m)
#此处计算缺失值的概率/处理样本中0概率事件 ,因为实际情况事件概率可能不为0,只是概率低未出现在样本中
#如果想进一步优化,可以对拉普拉斯平滑值调参
m=len(self.counts[category][col].items())
temp=1/(self.classes[category]+m)
prob=prob*temp
else:
prob = prob*self.condition[category][col][attrValue]
col+=1
'''
数值型的联合概率运算
'''
sqrt2pi = math.sqrt(2*math.pi)
col=1
for x in numVector:
mean = self.means[category][col]
ssd = self.ssd[category][col]
# 使用概率密度公式
ePart = math.pow(math.e, -(x-mean)**2/(2*ssd**2))
prob = prob*((1.0/(sqrt2pi*ssd))*ePart)
col+=1
self.results.append((prob,category))
#这里返回值为预测概率最大的类别
return max(self.results)[1]
因为十个桶文件中有一个是测试集,之前的操作都是处理训练集数据训练模型,我们需要用测试来评断模型的好坏
def testBucket(self,bucketPrefix, bucketNumber):
'''
用 bucketNumber 这个桶做测试集,来完成十折交叉测试
'''
#提取测试集数据
filename = '%s-%02i'%(bucketPrefix,bucketNumber)
print('待测试的桶为:',filename)
f = open(filename)
lines = f.readlines()
f.close()
#循环lines中的每一行数据,调用classify进行预测,保留预测结果
totals={}
for line in lines:
data = line.strip().split('\t')
vector=[] # 存储真实的待预测的数据
numV=[]
classInColumn=-1 # 类别在原始数据中的列索引
for i in range(len(self.format)):
if self.format[i]=='attr':
vector.append(data[i])
elif self.format[i]=='class':
classInColumn=i
elif self.format[i]=='num':
numV.append(float(data[i]))
#取出这个一行line的实际类别名
theRealClass = data[classInColumn]
# 对vector 特征向量进行预测类别
predictClass = self.classify(vector,numV)
# 预测结果 totals结构为{'democrat': {'democrat': 12}, 'republican': {'republican': 10}}
totals.setdefault(theRealClass,{})
totals[theRealClass].setdefault(predictClass,0)
totals[theRealClass][predictClass]+=1
return totals
使用第七个文件作为测试数据集
c=Classifer('house-votes/hv', 7, 'class\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr')
c.testBucket('house-votes/hv',7)
结果:
真实标签为democrat的12个预测中,有9个预测结果为democrat,3个预测结果为republican
真实标签为republican的11个预测中,有10个预测结果为republican,1个预测结果为democrat
待测试的桶为: house-votes/hv-07
{'democrat': {'republican': 3, 'democrat': 9},
'republican': {'republican': 10, 'democrat': 1}}
从一个测试桶文件中看不出什么,我们将十个桶的每个桶文件轮流做测试数据集,而其他九个桶文件做训练集,这样得到的十个结果的准确率的平均值,就是比较理想的预测正确率,
当然这里做了混淆矩阵的正确率,准确率,召回率三指标的输出,方便观察模型的好坏
#自动化进行10折交叉验证
def tenfold(bucketPrefix, dataFormat):
results = {}
for i in range(1,11):
c = Classifer(bucketPrefix,i,dataFormat)
t = c.testBucket(bucketPrefix,i)
for (key,value) in t.items():
results.setdefault(key, {})
for (ckey ,cvalue) in value.items():
results[key].setdefault(ckey, 0)
results[key][ckey]+= cvalue
print(results)
categories =list(results.keys()) #['democrat', 'republican']
categories.sort()
print("\n 混淆矩阵")
header = " "
subheader = " +"
for category in categories:
header+= " %10s " %category
subheader += "-------+"
print(header)
print(subheader)
total = 0.0
correct = 0.0
confusion_matrix=[]
for category in categories: #['democrat', 'republican']
row = " %10s |" %category
for c2 in categories:##['democrat', 'republican'] tp0 fn 1 tn 3 fp2
if c2 in results[category]: #{'democrat': {'democrat': 111, 'republican': 13}, 'republican': {'republican': 99, 'democrat': 9}}
count = results[category][c2]
else:
count = 0
row+= " %5i |"%count
total +=count
if c2 == category:
correct +=count
confusion_matrix.append(count)
else:
confusion_matrix.append(count)
print(row)
print(subheader)
print("\n正确率:%5.3f "%((correct * 100 )/total))
print("实例总数: %s" %total)
print("准确率:\ndemocrat:%5.3f\nrepublican:%5.3f"%(confusion_matrix[0]/(confusion_matrix[0]+confusion_matrix[2]),confusion_matrix[3]/(confusion_matrix[3]+confusion_matrix[1])))
print("召回率:\ndemocrat:%5.3f\nrepublican:%5.3f"%(confusion_matrix[0]/(confusion_matrix[0]+confusion_matrix[1]),confusion_matrix[3]/(confusion_matrix[3]+confusion_matrix[2])))
return (results,total)
tenfold("house-votes/hv", 'class\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr\tattr')
待测试的桶为: house-votes/hv-01
待测试的桶为: house-votes/hv-02
待测试的桶为: house-votes/hv-03
待测试的桶为: house-votes/hv-04
待测试的桶为: house-votes/hv-05
待测试的桶为: house-votes/hv-06
待测试的桶为: house-votes/hv-07
待测试的桶为: house-votes/hv-08
待测试的桶为: house-votes/hv-09
待测试的桶为: house-votes/hv-10
{'democrat': {'democrat': 111, 'republican': 13}, 'republican': {'republican': 101, 'democrat': 7}}
混淆矩阵
democrat republican
+-------+-------+
democrat | 111 | 13 |
republican | 7 | 101 |
+-------+-------+
正确率:91.379
实例总数: 232.0
准确率:
democrat:0.941
republican:0.886
召回率:
democrat:0.895
republican:0.935
总预测正误情况
({'democrat': {'democrat': 111, 'republican': 13},
'republican': {'republican': 101, 'democrat': 7}},
232.0)
import math
class Classifer:
def __init__(self, bucketPrefix, testBucketNumber, dataFormat):
'''
params:
bucketPrefix: 桶文件名的前缀
testBucketNumber:测试数据所在的桶的编号
dataFormat: 数据文件格式列表
'''
total=0 #记录总数据量
classes={} #存类别出现的次数
counts={} #每个类别对应的属性值出现的次数
self.format = dataFormat.strip().split('\t') # 切分每一列的数据类型
'''
存储数值型数据信息
'''
totals={} # 每种类别对应的值的总和
numericValues={} #存每种类别对应的列的取值
'''
存储计算出的概率
'''
self.prior={} #先验概率
self.condition={} #条件概率
for i in range(1,11): #循环10次
if i!=testBucketNumber: #判断是否为测试集
filename='%s-%02i'%(bucketPrefix,i)
f=open(filename,'r')
lines = f.readlines()
f.close()
for line in lines:
total=total+1 #统计总数
fields = line.strip().split('\t') #分割文件的每列
vector =[] #存储字符型特征
'''
保存每一条数据的数值型数据
'''
nums=[]
#分门别类存储标签和不同类型的特征
for i in range(len(fields)):
if self.format[i]=='attr':
vector.append(fields[i])
elif self.format[i]=='class':
category=fields[i]
elif self.format[i]=='num':
nums.append(float(fields[i]))
#记录每个列别出现的次数
classes.setdefault(category,0)
counts.setdefault(category,{})
classes[category]+=1
'''
记录每个数值型数据
'''
totals.setdefault(category,{})
numericValues.setdefault(category,{})
#循环处理么每条记录出现的属性值
#循环vector 取出每列值
col=0
for columnValue in vector:
col+=1
counts[category].setdefault(col,{})
counts[category][col].setdefault(columnValue,0)
counts[category][col][columnValue]+=1
'''
counts形成的结构为:
{
'democrat':
{
1: {'y': 64, 'n': 48},
2: {'n': 64, 'y': 48},
...
16: {'y': 105, 'n': 7}
},
'republican':
{
1: {'y': 22, 'n': 76},
2: {'y': 46, 'n': 52},
...
16: {'n': 30, 'y': 68}
}
}
'''
'''
保存每个数值型数据与类别的对应字典
'''
col=0
for columnValue in nums:
col+=1
totals[category].setdefault(col,0)
totals[category][col]+=columnValue
numericValues[category].setdefault(col,[])
numericValues[category][col].append(columnValue)
print(counts)
#开始计算概率
# classes: {'democrat': 112, 'republican': 98}
for (category, count) in classes.items():
self.prior[category] = count/total
#条件概率: 注意:columns是一个字典
for (category, columns) in counts.items():
self.condition.setdefault(category,{})
for (col,valueCount) in columns.items():
self.condition[category].setdefault(col, {})
# 此处引入拉普拉斯平滑处理已存在值
for (attrValue, count) in valueCount.items():
# 此处先计算所有在样本中已存在值的条件概率 ( nc+(mp))/(N+m)
m=len(counts[category][col].items())
self.condition[category][col][attrValue] = (count+1)/(classes[category]+m)
# 设置为类成员,方便在创建类对象后查看
self.counts=counts
self.classes=classes
'''
开始计算数值型数据的概率密度函数
'''
self.means={}
self.totals=totals
#先计算均值
for (category, columns) in totals.items():
self.means.setdefault(category,{})
for (col,colTotal) in columns.items():
self.means[category][col]=colTotal/classes[category]
#在计算样本标准差
self.ssd={}
for (category,columns) in numericValues.items():
self.ssd.setdefault(category,{})
for (col,values) in columns.items():
sumOfSquareDifferences=0
theMean = self.means[category][col]
for value in values:
sumOfSquareDifferences+= (value-theMean)**2
columns[col] =0
self.ssd[category][col] = math.sqrt(sumOfSquareDifferences/(classes[category]-1))
def classify(self, itemVector, numVector):
self.results=[] # [()]
for (category, prior) in self.prior.items():
prob = prior
col=1
for attrValue in itemVector:
if not attrValue in self.condition[category][col]:
# prob=0 即nc=0 ( nc+(mp))/(N+m) = 1/(n+m)
#此处计算缺失值的概率/处理样本中0概率事件 ,因为实际情况事件概率可能不为0,只是概率低未出现在样本中
#如果想进一步优化,可以对拉普拉斯平滑值调参
m=len(self.counts[category][col].items())
temp=1/(self.classes[category]+m)
prob=prob*temp
else:
prob = prob*self.condition[category][col][attrValue]
col+=1
'''
数值型的联合概率运算
'''
sqrt2pi = math.sqrt(2*math.pi)
col=1
for x in numVector:
mean = self.means[category][col]
ssd = self.ssd[category][col]
# 使用概率密度公式
ePart = math.pow(math.e, -(x-mean)**2/(2*ssd**2))
prob = prob*((1.0/(sqrt2pi*ssd))*ePart)
col+=1
self.results.append((prob,category))
#这里返回值为预测概率最大的类别
return max(self.results)[1]
def testBucket(self,bucketPrefix, bucketNumber):
'''
用 bucketNumber 这个桶做测试集,来完成十折交叉测试
'''
#提取测试集数据
filename = '%s-%02i'%(bucketPrefix,bucketNumber)
print('待测试的桶为:',filename)
f = open(filename)
lines = f.readlines()
f.close()
#循环lines中的每一行数据,调用classify进行预测,保留预测结果
totals={}
for line in lines:
data = line.strip().split('\t')
vector=[] # 存储真实的待预测的数据
numV=[]
classInColumn=-1 # 类别在原始数据中的列索引
for i in range(len(self.format)):
if self.format[i]=='attr':
vector.append(data[i])
elif self.format[i]=='class':
classInColumn=i
elif self.format[i]=='num':
numV.append(float(data[i]))
#取出这个一行line的实际类别名
theRealClass = data[classInColumn]
# 对vector 特征向量进行预测类别
predictClass = self.classify(vector,numV)
# 预测结果 totals结构为{'democrat': {'democrat': 12}, 'republican': {'republican': 10}}
totals.setdefault(theRealClass,{})
totals[theRealClass].setdefault(predictClass,0)
totals[theRealClass][predictClass]+=1
return totals
#自动化进行10折交叉验证
def tenfold(bucketPrefix, dataFormat):
results = {}
for i in range(1,11):
c = Classifer(bucketPrefix,i,dataFormat)
t = c.testBucket(bucketPrefix,i)
for (key,value) in t.items():
results.setdefault(key, {})
for (ckey ,cvalue) in value.items():
results[key].setdefault(ckey, 0)
results[key][ckey]+= cvalue
print(results)
categories =list(results.keys()) #['democrat', 'republican']
categories.sort()
print("\n 混淆矩阵")
header = " "
subheader = " +"
for category in categories:
header+= " %10s " %category
subheader += "-------+"
print(header)
print(subheader)
total = 0.0
correct = 0.0
confusion_matrix=[]
for category in categories: #['democrat', 'republican']
row = " %10s |" %category
for c2 in categories:##['democrat', 'republican'] tp0 fn 1 tn 3 fp2
if c2 in results[category]: #{'democrat': {'democrat': 111, 'republican': 13}, 'republican': {'republican': 99, 'democrat': 9}}
count = results[category][c2]
else:
count = 0
row+= " %5i |"%count
total +=count
if c2 == category:
correct +=count
confusion_matrix.append(count)
else:
confusion_matrix.append(count)
print(row)
print(subheader)
print("\n正确率:%5.3f "%((correct * 100 )/total))
print("实例总数: %s" %total)
print("准确率:\ndemocrat:%5.3f\nrepublican:%5.3f"%(confusion_matrix[0]/(confusion_matrix[0]+confusion_matrix[2]),confusion_matrix[3]/(confusion_matrix[3]+confusion_matrix[1])))
print("召回率:\ndemocrat:%5.3f\nrepublican:%5.3f"%(confusion_matrix[0]/(confusion_matrix[0]+confusion_matrix[1]),confusion_matrix[3]/(confusion_matrix[3]+confusion_matrix[2])))
return (results,total)
朴素贝叶斯代码的实现其实分为下面几部分:
其中处理不同数据类型(伯努利,多项式,连续型)和0概率情况,还用到了:
但是在官方sklearn的朴素贝叶斯函数中还有许多地方可以调参优化模型准确率,这里只是简单的实现,熟悉基本原理。
若有不足之处,请提醒我改正,感激不尽