python|机器学习--朴素贝叶斯分类器(python手动实现)


机器学习-朴素贝叶斯分类器

  • 简介
  • 名词介绍
  • 公式
  • 概率分布
  • 数据集及代码实现
    • 代码实现
      • 定义容器对象
      • 循环提取十个桶文件中的数据,分门别类存储
      • 计算先验概率,条件概率
      • 预测分类
      • 测试集验证
      • 预测测试
      • 十折交叉验证
    • 代码汇总
  • 总结

简介 朴素贝叶斯(Naive Bayes)是基于贝叶斯定理和概率论预测样本类别的概率算法,而朴素一词的来源就是假设各特征之间相互独立。朴素贝叶斯属于监督学习的生成模型,实现简单,没有迭代,并有坚实的数学理论(即贝叶斯定理)作为支撑。在大量样本下会有较好的表现,不适用于输入向量的特征条件有关联的场景。
关于贝叶斯定理的详细介绍
名词介绍 拿西瓜是否成熟举例,假设判断西瓜成熟的特征有:瓜蒂是否脱落,敲打的声音-浊响|清脆
  • 先验概率:
    先验概率是根据以往经验和分析得到的概率,先验概率无需样本数据,不受任何条件的影响。比如有1000个西瓜,其中熟瓜600,涩瓜400,则先验概率p(熟瓜)=0.6,p(涩瓜)=0.4,并不受瓜蒂和敲打声音的影响。再比如硬币正反面概率各为0.5也是先验概率。
  • 后验(条件)概率:
    已知结果或者某一条件(结果也是一种条件),求知条件或者结果的概率。比如已知某西瓜瓜蒂脱落,判断瓜成熟的概率,即p(成熟|瓜蒂脱落)。
  • 拉普拉斯平滑(Laplace smoothing):
    它的思想非常简单,就是对先验概率的分子(划分的计数)加1,分母加上类别数;对条件概率分子加1,分母加上对应特征的可能取值数量。这样在解决零概率问题的同时,也保证了概率和依然为1。
    直接的作用就是防止模型“过拟合”,提高了模型的泛化性能。
    也是解决样本为0的时概率为0的问题,因为若p(B)为0,则P(B|A)也为0,而实际并非如此。
  • 概率密度函数:
    用于描述连续型随机变量所服从的概率分布,由此判断不可列的连续型随机变量的概率。
公式
  • 贝叶斯公式:
    python|机器学习--朴素贝叶斯分类器(python手动实现)
    文章图片

  • 朴素贝叶斯基本公式:python|机器学习--朴素贝叶斯分类器(python手动实现)
    文章图片

    即P(B|A) = P(A|B)P(B)
  • 概率密度函数:
    python|机器学习--朴素贝叶斯分类器(python手动实现)
    文章图片

  • 正态分布:
    python|机器学习--朴素贝叶斯分类器(python手动实现)
    文章图片

概率分布
  • 高斯分布型:
    正态曲线呈钟型,两头低,中间高,左右对称因其曲线呈钟形,因此人们又经常称之为钟形曲线
  • 多项式型:
    用于离散值模型里。比如文本分类问题里面我们提到过,我们不光看词语是否在文本中出现,也得看出现次数。如果总词数为n,出现词数为m的话,有点像掷骰子n次出现m次这个词的场景。
  • 伯努利型:
    亦称“零一分布”、“两点分布”:一个事情有两种可能的结果,其中结果为1的发生概率为a,结果2发生的概率为1-a
数据集及代码实现 数据集是使用了分层抽样的十折交叉验证的,所以分为了十个名字相似的各类样本相近的数据集桶文件,如下:
该数据集为国会投票的样本:
python|机器学习--朴素贝叶斯分类器(python手动实现)
文章图片

分析数据集:
democrat为民主党,republican为共和党,即标签,表示类别
此后y和n表示特征,共16个特征,每个特征两个类别
python|机器学习--朴素贝叶斯分类器(python手动实现)
文章图片

代码实现 定义容器对象
存储从数据集中提取的数据,存储对数据统计分析的结果
数据分为三类:标签类别(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 = https://www.it610.com/article/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']tp0fn 1tn3fp2 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}}混淆矩阵 democratrepublican +-------+-------+ 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 = https://www.it610.com/article/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']tp0fn 1tn3fp2 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概率情况,还用到了:
  • 概率密度函数
  • 拉普拉斯平滑
【python|机器学习--朴素贝叶斯分类器(python手动实现)】但是在官方sklearn的朴素贝叶斯函数中还有许多地方可以调参优化模型准确率,这里只是简单的实现,熟悉基本原理。
若有不足之处,请提醒我改正,感激不尽

    推荐阅读