1. 程式人生 > >【ML學習筆記】17:多元正態分佈下極大似然估計最小錯誤率貝葉斯決策

【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曲線:
這裡寫圖片描述

與一元情況的比較

在上篇中,僅使用身高或者僅使用體重做判別,得到的分類器效果都很不好。從常理上也能解釋,身高或者體重都不是鑑別男女的有效手段。建立好這個分類器後,可以看看這兩個特徵聯合的分類效果如何。
這裡寫圖片描述
看似不那麼合適的兩個特徵,聯合後的分類效果好了很多。