1.最大熵原理
最大熵原理認爲,學習概率模型時,在所有可能的概率分佈中,熵最大模型爲最好的模型。因此在滿足約束條件的情況下,最大熵原理選擇的使熵最大的模型。
熵的公式爲:
2.最大熵模型
將上述最大熵原理運用到模型的選擇上即爲最大熵模型。
考慮現在有樣本集,如何來建立模型預測新的P(y|x)。
首先,原有的樣本集即爲我們的約束條件。
即對於上述的每一個樣本,以爲例,特徵函數f(x,y)=1當前僅當,這爲一個約束條件。
對於此f(x,y),我們可以求出其的關於經驗分佈的函數期望,即爲:
即求在所有的樣本中,滿足的樣本所佔的比例(因時,f(x,y)=0)。同樣地,我們要求的也應該上述約束條件,我們可以利用經驗分佈與P(y|x)的聯合分佈來對f(x,y)的期望值進行求解,.然後令。
即關於特徵函數f(x,y)的P(y|x)應滿足的約束條件如下所示:
而現在有n個樣本,因此類似的有n個約束條件,因此會有n個上述類型的特徵函數。
在要求的P(y|x)滿足上述N個約束條件的前提下,然後利用最大熵原理,選擇令
P(y|x)熵最大的模型,即要求的P爲:
其中條件熵的計算公式可爲
。
3.最大熵模型的學習
在上文介紹了最大熵原理以及對最大熵模型的定義之後,接下來涉及如何對上述最大熵模型進行求解。
在第2節對最大熵模型的定義中,我們可以將其看做一個帶約束的最優化問題。因此可以利用經典的拉格朗日乘子法將其轉換爲無約束優化問題:
首先,我們列出拉格朗日函數:
然後利用拉格朗日的對偶性(具體可參考鏈接:link link link link進行拉格朗日相關知識的學習)
此處和下文中公式推導求解不再贅述,主要是參考李航書中的內容,具體也可參考這兩篇博客link,link。
我們可通過來求出P(y|x)。
最終求出來的結果如上圖,即我們先求出來了對偶問題的結果,然後將求出來的代入到中,現在轉換爲了求w的問題,即求無約束優化問題——對對偶問題的極大化。
之後的參數w的求解可利用常用的無約束優化問題求解方法——牛頓法或者《統計學習方法》中介紹的改進的迭代尺度法,此處不再贅述。
4.最大熵模型與邏輯迴歸的關係
同時,在統計學習方法的6.2.4節,證明了對對對偶問題的極大化等價於對條件概率分佈P(y|x)的極大對數似然估計。
而其實邏輯迴歸中P(w|x)參數的求解也是對條件概率分佈P(w|x)的極大對數似然估計,二者唯一的區別是P(w|x)的形式不同,但我們可以通過對最大熵模型加入一些條件推出邏輯迴歸模型。見:link.
5.總結
本次首先介紹最大熵原理,然後由最大熵原理+n個樣本帶來的約束條件推出最大熵模型,即爲一個有約束的最優化問題,然後利用拉格朗日乘子法求解上述的有約束的最優化問題,並利用其對偶性將其轉換爲了先求出P(y|x)在求w的無約束的最優化問題,同時我們也證明了求出P(y|x)之後求w的最優化過程等價於直接對條件概率分佈P(y|x)的極大對數似然估計,然後引出了與邏輯迴歸的關係,且在對最大熵模型進行一些初始條件限制之後,即與邏輯迴歸等價。
本次並未詳細介紹公式的推導,主要介紹了最大熵模型的大體流程,具體推導可參考《統計學習方法》和上述引用的一些鏈接,最後也可能會貼上一個利用改進的迭代尺度法實現的最大熵模型代碼。
6 代碼實現
import numpy as np
import time
from collections import defaultdict
'''
minist 數據集
50000 訓練集(實際使用1000)
10000 測試集
訓練結果:
the acc is 0.962400 :
time span 2157.652851 s:
'''
def load_data(fileName):#加載數據
'''
:param fileName:minist訓練集/測試集文件
:return: 對應的特徵值X和標籤值Y
'''
fr = open(fileName, 'r')
dataX = [];dataY = []
for line in fr.readlines():
curline = line.strip().split(',')
if(int(curline[0])==0):# 二分類問題 轉換爲區分0和非0數字
dataY.append(0) # 標籤
else:
dataY.append(1)
dataX.append([int(int(num)>128) for num in curline[1:]]) # 特徵
# print(dataX)
return dataX, dataY
class maxEnt:
'''
最大熵類
'''
def __init__(self,trainX,trainY,testX,testY):
self.trainX =trainX
self.trainY =trainY
self.testX =testX
self.testY =testY
self.featureNum = len(trainX[0])
self.N = len(trainX)#總的訓練集數目
self.n =0 #不同特徵下的(xi,y)的總數目 784*2*2 二分類
self.M =10000
self.fixy = self.calc_fixy()# #計算不同特徵下出現的(x,y)對的次數
self.w = [0]*self.n #每個(xi,y)對的出現 對應一個f 總共有n個(xi,y)對 對應n個f 也對應n個w
self.xy2idDict,self.id2xyDict = self.createSearchDict() #創建xy與id之間的對應關係
self.Ep_xy = self.calcEp_xy()# 特徵函數f(x,y)關於經驗分佈P-(x,y)的期望
def maxEntropyTrain(self,iter=500):
'''
進行最大熵模型的訓練
:param iter: 迭代次數
:return:
'''
for i in range(iter):
iterStart = time.time()
Epxy = self.calcEpxy()#計算P83頁的期望
sig =[0]*self.n
for j in range(self.n):
sig[j] = (1/self.M)*np.log(self.Ep_xy[j]/Epxy[j])
self.w =[self.w[q] + sig[q] for q in range(self.n)]# 沒有使用array 不能直接相加減
iterEnd = time.time()
print('iter:%d:%d,time:%d'%(i,iter,iterEnd-iterStart))
return self.w
def calcEpxy(self):
'''
計算特徵函數f(x,y)關於模型P(y|x)與經驗分佈P(x)的期望值
:return:
'''
Epxy = [0.0]*self.n
for i in range(self.N):
Pwxy = [0]*2
##計算P(y = 0 } X)
#注:程序中X表示是一個樣本的全部特徵,x表示單個特徵,這裏是全部特徵的一個樣本
Pwxy[0] = self.calcPwy_x(self.trainX[i],0)#計算P(y|x)的概率
##計算P(y = 1 } X)
Pwxy[1] = self.calcPwy_x(self.trainX[i],1)
for j in range(self.featureNum):
for y in range(2):#對每個xi點判斷所有情況
if (self.trainX[i][j],y) in self.fixy[j]:#如果真的存在這種情況
id = self.xy2idDict[j][(self.trainX[i][j],y)]
Epxy[id] += (1/self.N)*Pwxy[y]#然後計算對應情況下的關於模型P(y|x)與經驗分佈P(x)的期望值
return Epxy
def calcPwy_x(self,X,y):
'''
#計算條件概率 依據最大熵模型公式計算
:param X:訓練樣本X
:param y: 不同的標籤
:return: P(y|X)
'''
Z =0
numerator = 0
for i in range(self.featureNum):
if (X[i],y) in self.xy2idDict[i]:#xi y 出現過
index = self.xy2idDict[i][(X[i],y)]
numerator += self.w[index]
if (X[i],1-y) in self.xy2idDict[i]:#xi 1-y 出現過
index = self.xy2idDict[i][(X[i], 1-y)]
Z += self.w[index]
numerator = np.exp(numerator)
Z = numerator+np.exp(Z)# 所有情況的
return numerator/Z
def calcEp_xy(self):# 特徵函數f(x,y)關於經驗分佈P-(x,y)的期望
Ep_xy = [0]*self.n
for i in range(self.featureNum):
for (x,y) in self.fixy[i]:
id = self.xy2idDict[i][(x,y)]
Ep_xy[id]=self.fixy[i][(x,y)]/self.N#利用(xi,y)出現次數除以總的訓練個數即爲期望 fixy 已是對所有x,y的遍歷
return Ep_xy
def createSearchDict(self):
# 對 self.fixy中的(x,y)建立一個標號 實現feature、(x,y)對到n的映射
xy2idDict = [{} for i in range(self.featureNum)]
id2xyDict = {}
index = 0
for i in range(self.featureNum):
for (x,y) in self.fixy[i]:
xy2idDict[i][(x,y)]=index
id2xyDict[index] = (x,y)
index+=1
return xy2idDict,id2xyDict
def calc_fixy(self):
'''
#計算不同特徵下出現的(x,y)對的次數
:return:字典列表
'''
fxyDict = [defaultdict(int) for i in range(self.featureNum)]#建立featureNum個字典
for i in range(len(self.trainX)):
for j in range(self.featureNum):
fxyDict[j][(self.trainX[i][j],self.trainY[i])]+=1#計算j特徵下的(xi,y)出現的頻率
for i in fxyDict:#對整個大字典進行去重、計算每個特徵下共有多少個特徵對
self.n+=len(i)
return fxyDict
def predict(self,X):
# Pwy_x = self.calcPwy_x(X,y)
# if(Pwy_x>=0.5):
# return 1
# else:
# return 0
result =[0]*2
for i in range(2):
result[i] = self.calcPwy_x(X,i)
print(result[0],result[1],self.w[0],self.w[1])
return 0 if result[0]>result[1] else 1
def test(self):
errorCnt = 0
for i in range(len(self.testX)):
if self.predict(self.testX[i])!=self.testY[i]:
errorCnt+=1
return 1-errorCnt/len(self.testX)
if __name__=="__main__":
start =time.time()
print("start read trainData")
trainX, trainY = load_data("./mnist_train/mnist_train.csv")
print("start read TestData")
testX, testY = load_data("./mnist_test/mnist_test.csv")
maxEnt = maxEnt(trainX[:1000],trainY[:1000],testX,testY)
print("start to train")
maxEnt.maxEntropyTrain(500)
print("start to test")
acc = maxEnt.test()
print("the acc is %f :"%acc)
print("time span %f s:"%(time.time()-start))
參考鏈接:
https://www.pkudodo.com/2018/12/05/1-7/
https://www.jianshu.com/p/e7c13002440d