【ML学习笔记】17:多元正态分布下极大似然估计最小错误率贝叶斯决策

简述多元正态分布下的最小错误率贝叶斯

如果特征的值向量服从d元正态分布,即其概率密度函数为:
这里写图片描述
即其分布可以由均值向量和对称的协方差矩阵
这里写图片描述
唯一确定。

如果认为样本的特征向量在类内服从多元正态分布:
这里写图片描述
即对于每个类i,具有各自的类内的均值向量和协方差矩阵。

如之前所学,最小错误率贝叶斯的判别函数的原始形式是:
这里写图片描述

类条件概率密度服从多元正态分布,带入,得:
这里写图片描述

因为是比较大小用的,去掉与类号i无关的项:
这里写图片描述

而用于分类的决策面是:
这里写图片描述

在实际问题下的措施

①反向判别

这次要做的还是用[身高,体重,鞋码]->[性别]的数据,认为类别男女出现的次数一样,即先验概率都是0.5,则可以在判别函数中去除这一项:
这里写图片描述

判别函数仅仅是比较大小用的,为了减少计算机计算量,改用反向判别的函数:
这里写图片描述
反向判别函数则要取函数值小的那一方作为预测的类别。

②分类面采样以对ROC上采样点做估计

对于多元正态分布,用理论公式的方式(多重积分)去计算错误率是很困难的,所以绘制ROC曲线时,我采用改变分类面,获得多个采样分类面,在采样分类面上用频率估计错误率。

改变采样分类面的方式可以改变分类面的常数阈值,即把0改成一定区间上的正负值b:
这里写图片描述

代码实现

#-*-coding:utf-8-*-
from numpy import *
import operator
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D #绘制3D坐标的函数

#求二元正态分布概率密度函数值
#传入的向量x=[特征i值,特征j值]
def gauss2(x,miu,sgm):
    r=mat([x[0]-miu[0],x[1]-miu[1]])
    multi=r*sgm.I*r.T
    multi=float(multi) #1乘1矩阵取内容
    k=exp(-multi/2) #.I求逆,.T转置
    k/=2*math.pi*linalg.det(sgm)**(1/2) #linalg.det求行列式的值
    return k

#从文件中读取数据
def getData():
    fr1=open(r'boynew.txt')
    fr0=open(r'girlnew.txt')
    arrayOLines1=fr1.readlines() #读取文件
    arrayOLines0=fr0.readlines()
    #特征矩阵
    dataMat1=[]
    dataMat0=[]
    #男同学
    for line in arrayOLines1:
        line=line.strip() #strip()去掉首尾空格
        listFromLine=line.split() #按空白字符分割成列表
        #string变float
        for i in range(len(listFromLine)):
            listFromLine[i]=float(listFromLine[i])
        #加入特征矩阵
        dataMat1.append(listFromLine)
    #女同学
    for line in arrayOLines0:
        line=line.strip() #strip()去掉首尾空格
        listFromLine=line.split() #按空白字符分割成列表
        #string变float
        for i in range(len(listFromLine)):
            listFromLine[i]=float(listFromLine[i])
        #加入特征矩阵
        dataMat0.append(listFromLine)
    return dataMat1,dataMat0

#训练(不带归一化)
def TraBys(i,j):
    dataMat1,dataMat0=getData() #获取数据
    dataMat1=mat(dataMat1)
    dataMat0=mat(dataMat0)
    #求男女同学的均值和协方差矩阵
    miu1,miu0,sgm1,sgm0=getMiuSgm(i,j,dataMat1,dataMat0)
    return miu1,miu0,sgm1,sgm0


#训练(带归一化)
def TraBysStd(i,j):
    dataMat1,dataMat0=getData() #获取数据
    '''
    #生成标签向量
    labelVec=[1 for k in range(len(dataMat1))]
    labelVec0=[0 for k in range(len(dataMat0))]
    labelVec.extend(labelVec0)
    '''
    #男女特征集放到一起
    dataMat=dataMat1
    dataMat.extend(dataMat0)
    #并转换为矩阵对象dataMat,保留了dataMat1和dataMat0!
    dataMat=mat(dataMat)
    dataMat1=mat(dataMat1)
    dataMat0=mat(dataMat0)
    #用合矩阵dataMat0返回归一化之后的矩阵
    #主要是要获得原范围ranges,原最小值minVals
    #这两个参数使用单一的dataMat1和dataMat0都没法表征总体!
    newDataMat,ranges,minVals=autoNorm(dataMat)
    #还要把男女同学自己的矩阵归一化
    #才能传进去求均值和协方差矩阵
    newDataMat1,_,_=autoNorm(dataMat1)
    newDataMat0,_,_=autoNorm(dataMat0)
    #维度修正,实在是无奈之举
    newDataMat1=reshape(newDataMat1,(len(dataMat1),-1))
    newDataMat0=reshape(newDataMat0,(len(dataMat0),-1))
    #求男女同学的均值和协方差矩阵
    miu1,miu0,sgm1,sgm0=getMiuSgm(i,j,newDataMat1,newDataMat0)
    #除了返回这四个参数,还要返回原范围和最小值
    #这是为了对外来特征向量归一化
    return miu1,miu0,sgm1,sgm0,ranges,minVals

#对外来特征向量x归一化
def rebuidx(x,ranges,minVals):
    return (x-minVals)/ranges

#获取男女同学的均值和协方差矩阵
def getMiuSgm(i,j,dataMat1,dataMat0):
    #计算男生i特征均值
    Imiu1=0.0
    for r in range(len(dataMat1)):
        Imiu1+=dataMat1[r,i] #注意矩阵不能像数组那样取值!
    Imiu1=Imiu1/len(dataMat1)
    #计算女生i特征均值
    Imiu0=0.0
    for r in range(len(dataMat0)):
        Imiu0+=dataMat0[r,i]
    Imiu0=Imiu0/len(dataMat0)
    #计算男生j特征均值
    Jmiu1=0.0
    for r in range(len(dataMat1)):
        Jmiu1+=dataMat1[r,j]
    Jmiu1=Jmiu1/len(dataMat1)
    #计算女生j特征均值
    Jmiu0=0.0
    for r in range(len(dataMat0)):
        Jmiu0+=dataMat0[r,j]
    Jmiu0=Jmiu0/len(dataMat0)
    #男生的均值向量
    miu1=[Imiu1,Jmiu1]
    #女生的均值向量
    miu0=[Imiu0,Jmiu0]
    #求男生的协方差矩阵
    sgm1=mat(zeros((2,2)))
    for k in range(len(dataMat1)):
        x=mat([dataMat1[k,i]-miu1[0],dataMat1[k,j]-miu1[1]])
        sgm1+=x.T*x
    sgm1/=len(dataMat1)
    #求女生的协方差矩阵
    sgm0=mat(zeros((2,2)))
    for k in range(len(dataMat0)):
        x=mat([dataMat0[k,i]-miu0[0],dataMat0[k,j]-miu0[1]])
        sgm0+=x.T*x
    sgm0/=len(dataMat0)
    return miu1,miu0,sgm1,sgm0 #返回两均值,两方差

#绘制两个类条件概率密度图
def show(i,j,miu1,miu0,sgm1,sgm0):
    '''
    #类似于标准差的东西用来确定采样点起始和终止范围
    sqrtsgm1=linalg.det(sgm1)**(1/2) #行列式的(1/2)次幂,类似于标准差
    sqrtsgm0=linalg.det(sgm0)**(1/2) #行列式的(1/2)次幂,类似于标准差
    '''
    #采样间距
    if i==0: #如果特征i是身高,那么采样区间是140~200
        Ilft=140
        Irgt=200
        xlab='Height'
    elif i==1: #如果特征i是体重,那么采样区间是30~80
        Ilft=30
        Irgt=80
        xlab='Weight'
    else: #如果特征i是鞋码,那么采样区间是32~46
        Ilft=32
        Irgt=46
        xlab='Shoe Size'
    if j==0:
        Jlft=140
        Jrgt=200
        ylab='Height'
    elif j==1:
        Jlft=30
        Jrgt=80
        ylab='Weight'
    else:
        Jlft=32
        Jrgt=46
        ylab='Shoe Size'
    #linspace创建等差数列,在I/J方向上取采样点
    I1=linspace(Ilft,Irgt,50)
    J1=linspace(Jlft,Jrgt,50)
    I0=linspace(Ilft,Irgt,50)
    J0=linspace(Jlft,Jrgt,50)
    #映射以扩充为二维采样面上的点
    X1,Y1=meshgrid(I1,J1)
    X0,Y0=meshgrid(I0,J0)
    #用正态分布概率密度函数得到采样点的Z值序列
    Z1=[]
    for k in range(len(J1)):
        for p in range(len(I1)):
            Z1.append(gauss2([X1[k][p],Y1[k][p]],miu1,sgm1))
    Z1=reshape(Z1,(len(J1),len(I1)))
    Z0=[]
    for k in range(len(J0)):
        for p in range(len(I0)):
            Z0.append(gauss2([X0[k][p],Y0[k][p]],miu0,sgm0))
    Z0=reshape(Z0,(len(J0),len(X0)))
    #用这些坐标点绘制图像
    fig1=plt.figure()#创建一个绘图对象
    ax=Axes3D(fig1)#用这个绘图对象创建一个Axes对象(有3D坐标)
    #绘制男生i,j特征的类条件概率密度函数
    ax.plot_surface(array(X1),array(Y1),Z1,rstride=1,cstride=1,cmap=plt.cm.coolwarm)
    #绘制女生i,j的类条件概率密度函数
    ax.plot_surface(array(X0),array(Y0),Z0,rstride=1,cstride=1,cmap=plt.cm.jet)
    plt.title("The probability density function of the class condition")
    #给三个坐标轴注明
    ax.set_xlabel(xlab,color='r')  
    ax.set_ylabel(ylab,color='g')  
    ax.set_zlabel('Probability Density',color='b')
    plt.show()

#不同的特征,范围可能不同,作差得到的值可能差别很大
#如果它们的重要程度一样,显然不应如此
#可以将不同取值范围的特征值数值归一化到0~1之间
def autoNorm(dataMat):
    #下面参数0表示从列中取最大最小
    minVals=dataMat.min(0) #每列的最小值存到minVals里
    maxVals=dataMat.max(0) #每列的最大值存到maxVals里
    ranges=maxVals-minVals #每列最大最小值之差,存到ranges里
    newDataMat=zeros(shape(dataMat)) #用来存归一化后的矩阵
    m=dataMat.shape[0] #取第0维即行数
    newDataMat=dataMat-tile(minVals,(m,1)) #把最小值重复m成m行,用原值减去
    newDataMat=newDataMat/tile(ranges,(m,1)) #把减完的每个偏差值除以自己列最大和最小的差值
    return newDataMat,ranges,minVals #返回归一化之后的矩阵,原范围,原最小值


#计算dxd矩阵(二次项携带矩阵)
def Wi(sgm):
    return (-1/2)*sgm.I

#计算d维列向量(一次项系数矩阵)
def wi(miu,sgm):
    return sgm.I*miu.T

#计算常数项,还要传入先验概率
def omgi(miu,sgm,Ppre):
    return float((-1/2)*miu*sgm.I*miu.T)-\
            (1/2)*log(linalg.det(sgm))+\
            log(Ppre)

#新的计算判别函数的方式
#因为先验概率都是0.5,不妨去掉这一项,然后乘以系数-2作反向判别
def newgx(miu1,miu0,sgm1,sgm0,x):
    #暂存第一项
    s1=(x-miu1)*sgm1.I*(x-miu1).T
    s0=(x-miu0)*sgm0.I*(x-miu0).T
    #取矩阵内容
    s1=float(s1)
    s0=float(s0)
    #求出两个反向判别gx
    rvs_g1x=s1+log(linalg.det(sgm1))
    rvs_g0x=s0+log(linalg.det(sgm0))
    #因为反向判别,所以反向返回
    return rvs_g0x,rvs_g1x

#这个函数存在问题,但是没有发现问题所在之处
#用了上面的newgx去代替它,效果不错
#传入训练好的参数(两均值,两标准差),以及投入测试的特征向量x(mat化后)
#计算两类的判别函数值g1(x)和g0(x)
def gx(miu1,miu0,sgm1,sgm0,x):  
    #二次项携带矩阵
    W1=Wi(sgm1)
    W0=Wi(sgm0)
    #一次项系数矩阵
    w1=wi(miu1,sgm1)
    w0=wi(miu0,sgm0)
    #常数项
    omg1=omgi(miu1,sgm1,0.5) #认为男女先验概率都是0.5
    omg0=omgi(miu0,sgm0,0.5)
    #二次/一次项值1x1方阵
    s1_2=x*W1*x.T
    s1_1=w1.T*x.T
    s0_2=x*W0*x.T
    s0_1=w0.T*x.T
    #提取方阵值
    s1_2=float(s1_2)
    s1_1=float(s1_1)
    s0_2=float(s0_2)
    s0_1=float(s0_1)
    #计算判别函数值
    g1x=s1_2+s1_1+omg1
    g0x=s0_2+s0_1+omg0
    #返回求得的判别函数值
    return g1x,g0x

#对归一化后的x向量作判别,决策面方程g1(x)-g0(x)=b
#默认b=0,之所以设定b可改变是为了绘制ROC曲线
def TstBys(miu1,miu0,sgm1,sgm0,x,b=0):
    #列表变矩阵,否则不能转置
    x=mat(x)
    miu1=mat(miu1)
    miu0=mat(miu0)
    #计算判别函数值
    #g1x,g0x=gx(miu1,miu0,sgm1,sgm0,x)
    g1x,g0x=newgx(miu1,miu0,sgm1,sgm0,x)
    #返回判别类的标号
    if g1x-g0x > b:
        return 1
    else:
        return 0

#用测试集做错误率测试,传入特征号i和j
def errTst(i,j):
    fr1=open(r'boytst.txt') #测试集
    fr0=open(r'girltst.txt')
    arrayOLines1=fr1.readlines() #读取文件
    arrayOLines0=fr0.readlines()
    #特征矩阵
    dataMat=[]
    #标签向量
    labelVec=[]
    #男同学
    for line in arrayOLines1:
        line=line.strip() #strip()去掉首尾空格
        listFromLine=line.split() #按空白字符分割成列表
        #string变float
        for r in range(len(listFromLine)):
            listFromLine[r]=float(listFromLine[r])
        #加入特征矩阵
        dataMat.append(listFromLine)
        #加入标签向量
        labelVec.append(1)
    #女同学
    for line in arrayOLines0:
        line=line.strip() #strip()去掉首尾空格
        listFromLine=line.split() #按空白字符分割成列表
        #string变float
        for r in range(len(listFromLine)):
            listFromLine[r]=float(listFromLine[r])
        #加入特征矩阵
        dataMat.append(listFromLine)
        #加入标签向量
        labelVec.append(0)
    errCount=0.0 #错误分类次数计数
    #极大似然法训练出四个参数(两均值,两协方差矩阵)
    #miu1,miu0,sgm1,sgm0,minVals,ranges=TraBysStd(i,j)
    miu1,miu0,sgm1,sgm0=TraBys(i,j)
    #对于测试集的每个样本(下标)
    for k in range(len(labelVec)):
        #特征矩阵第k行的第[i,j]号特征向量做测试
        ''' 
        x=dataMat[k] #先把第k行全取过来
        x=rebuidx(x,minVals,ranges) #对x作归一化,这时不是列表了!
        x=[x[0,i],x[0,j]] #然后再变成只取i号和j号特征值的
        '''
        x=[dataMat[k][i],dataMat[k][j]]

        #如果预测的类别号和真实的标签向量中存的不一样
        if TstBys(miu1,miu0,sgm1,sgm0,x,0)!=labelVec[k]:
            errCount+=1 #记录分类错误
    print len(labelVec),"次测试错误率是:",errCount/len(labelVec)
    return dataMat,labelVec #返回测试集,用来求后面的两类错误率

#对于传入的b值,改变超平面的位置
#从而获取假阳性率(误识率)和召回率(1-拒识率)
#假设1男性为阳性,0女性为阴性
#用测试集做错误率测试,传入特征号i和j
def getROCxy(dataMat,labelVec,i,j,b):
    FPR=0.0 #假阳性数->率
    RECALL=0.0 #召回数->率
    FPplusTN=0.0 #存阴性样本数
    TPplusFN=0.0 #存阳性样本数
    #极大似然法训练出四个参数(两均值,两协方差矩阵)
    miu1,miu0,sgm1,sgm0=TraBys(i,j)
    #对于测试集的每个样本(下标)
    for k in range(len(labelVec)):
        #特征矩阵第k行的第[i,j]号特征向量做测试
        x=[float(dataMat[k][i]),float(dataMat[k][j])]
        #本次预测出来的类号
        yc=TstBys(miu1,miu0,sgm1,sgm0,x,b)
        #如果实际是阳性样本
        if labelVec[k]==1:
            TPplusFN+=1 #阳性样本数+1
        else: #如果是阴性样本
            FPplusTN+=1 #阴性样本数+1
        #阳性样本 && 预测为阳性
        if labelVec[k]==1 and yc==1:
            RECALL+=1 #真阳性数+1
        #阴性样本 && 预测为阳性
        elif labelVec[k]==0 and yc==1:  
            FPR+=1 #假阳性数+1
    #print FPR,FPplusTN,RECALL,TPplusFN
    #假阳性率
    FPR/=FPplusTN
    #召回(真阳性)率
    RECALL/=TPplusFN
    return FPR,RECALL

#演示用的函数,传入两个特征编号i和j
def Go(i,j):
    miu1,miu0,sgm1,sgm0=TraBys(i,j) #训练获得4个参数
    show(i,j,miu1,miu0,sgm1,sgm0) #绘制类条件概率密度图
    dataMat,labelVec=errTst(i,j) #求测试错误率
    print '正在采样绘制ROC...'
    #绘制ROC曲线用的采样点
    X=[]
    Y=[]
    #分类面采样阈值从-50到60,每次移动1
    for b in range(-50,60,1):
        #获取假阳率和真阳率
        FPR,RECALL=getROCxy(dataMat,labelVec,i,j,b)
        #分别加入到坐标采样列表中
        X.append(FPR)
        Y.append(RECALL)
    #绘制ROC曲线
    plt.plot(X,Y,"g-",linewidth=2)
    #横纵坐标名称,标题名称
    plt.xlabel('FPR')
    plt.ylabel('RECALL')
    if i==0:
        xlab='Height'
    elif i==1:
        xlab='Weight'
    else:
        xlab='Shoe Size'
    if j==0:
        ylab='Height'
    elif j==1:
        ylab='Weight'
    else:
        ylab='Shoe Size'
    plt.title('ROC curve (if use '+xlab+' and '+ylab+' as feature)')
    plt.grid(True) #显示网格
    plt.show() #显示

测试

直接用封装好的演示用的函数演示功能:

import bayes as bs
bs.Go(1,2)

这里写图片描述

类条件概率密度函数图:
这里写图片描述

关闭窗口后开始采样绘制ROC:
这里写图片描述

绘制好的ROC曲线:
这里写图片描述

与一元情况的比较

在上篇中,仅使用身高或者仅使用体重做判别,得到的分类器效果都很不好。从常理上也能解释,身高或者体重都不是鉴别男女的有效手段。建立好这个分类器后,可以看看这两个特征联合的分类效果如何。
这里写图片描述
看似不那么合适的两个特征,联合后的分类效果好了很多。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值