【ML學習筆記】17:多元正態分佈下極大似然估計最小錯誤率貝葉斯決策
阿新 • • 發佈:2019-01-11
簡述多元正態分佈下的最小錯誤率貝葉斯
如果特徵的值向量服從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曲線:
與一元情況的比較
在上篇中,僅使用身高或者僅使用體重做判別,得到的分類器效果都很不好。從常理上也能解釋,身高或者體重都不是鑑別男女的有效手段。建立好這個分類器後,可以看看這兩個特徵聯合的分類效果如何。
看似不那麼合適的兩個特徵,聯合後的分類效果好了很多。