【機器學習演算法實現】系列文章將記錄個人閱讀機器學習論文、書籍過程中所碰到的演算法,每篇文章描述一個具體的演算法、演算法的程式設計實現、演算法的具體應用例項。爭取每個演算法都用多種語言程式設計實現。所有程式碼共享至github:https://github.com/wepe/MachineLearning-Demo 歡迎交流指正!
1、演算法簡介
迴歸的概念:假設有一些資料點,我們用一條直線對這些點進行擬合,這個擬合過程就稱作迴歸。
logistic迴歸演算法之所以稱作“logistic”,是因為它運用了logistic函式,即sigmoid函式。
logistic迴歸演算法一般用於二分類問題(當然也可以多類別,後面會講)。
logistic迴歸的演算法思想:
用上面的圖來分析,每個O或X代表一個特徵向量,這裡是二維的,可以寫成x=(x1,x2)。
用logistic迴歸進行分類的主要思想就是根據現有資料集,對分類邊界建立迴歸公式,拿上面這個圖來說,就是根據這些OOXX,找出那條直線的公式:Θ0*x0+Θ1*x1+Θ2*x2=Θ0+Θ1*x1+Θ2*x2=0 (x0=1)。
因為上圖是二維的,所以引數Θ=(Θ0,Θ1, Θ2),分類邊界就由這個(Θ0,Θ1, Θ2)確定,對於更高維的情況也是一樣的,所以無論二維三維更高維,分類邊界可以統一表示成f(x)=ΘT*x (ΘT表示Θ的轉置)。
對於分類邊界上的點,代入分類邊界函式就得到f(x)=0,同樣地,對於分類邊界之上的點,代入得到f(x)>0,對於分類邊界之下的點,代入得到f(x)f(x)大於0或者小於0來分類了。
logistic迴歸的最後一步就是將f(x)作為輸入,代入Sigmoid函式,當f(x)>0時,sigmoid函式的輸出就大0.5,且隨著f(x)趨於正無窮,sigmoid函式的輸出趨於1。當f(x)0時,sigmoid函式的輸出就小於0.5,且隨著f(x)趨於負無窮,sigmoid函式的輸出趨於0。
所以我們要尋找出最佳引數Θ,使得對於1類別的點x,f(x)趨於正無窮,對於0類別的點x,f(x)趨於負無窮(實際程式設計中不可能正/負無窮,只要足夠大/小即可)。
總結一下思緒,logistic迴歸的任務就是要找到最佳的擬合引數。下圖的g(z)即sigmoid函式,跟我上面講的一樣,將f(x)=ΘT*x作為g(z)的輸入。
以上就是logistic迴歸的思想,重點在於怎麼根據訓練資料求得最佳擬合引數Θ?這可以用最優化演算法來求解,比如常用的梯度上升演算法,關於梯度上升演算法這裡也不展開,同樣可以參考上面推薦的博文。
所謂的梯度,就是函式變化最快的方向,我們一開始先將引數Θ設為全1,然後在演算法迭代的每一步裡計算梯度,沿著梯度的方向移動,以此來改變引數Θ,直到Θ的擬合效果達到要求值或者迭代步數達到設定值。Θ的更新公式:
alpha是步長,一系列推導後:
這個公式也是下面寫程式碼所用到的。
後話:理解logistic迴歸之後可以發現,其實它的本質是線性迴歸,得到ΘT*x的過程跟線性迴歸是一樣的,只不過後面又將ΘT*x作為logistic函式的輸入,然後再判斷類別。
2、工程例項
我的前一篇文章:kNN演算法__手寫識別 講到用kNN演算法識別數字0~9,這是個十類別問題,如果要用logistic迴歸,
得做10次logistic迴歸,第一次將0作為一個類別,1~9作為另外一個類別,這樣就可以識別出0或非0。同樣地可以將1作為一個類別,0、2~9作為一個類別,這樣就可以識別出1或非1……..
本文的例項同樣是識別數字,但為了簡化,我只選出0和1的樣本,這是個二分類問題。下面開始介紹實現過程:
(1)工程檔案說明
在我的工程檔案目錄下,有訓練樣本集train和測試樣本集test,原始碼檔案命名為logistic regression.py
訓練樣本集train和測試樣本集test裡面只有0和1樣本:
(2)原始碼解釋
實現的功能就是從資料夾裡面讀取所有訓練樣本,每個樣本(比如0_175.txt)裡有32*32的資料,程式將32*32的資料整理成1*1024的向量,這樣從每個txt檔案可以得到一個1*1024的特徵向量X,而其類別可以從檔名“0_175.txt”裡擷取0出來。因此,從train資料夾我們可以獲得一個訓練矩陣m*1024和一個類別向量m*1,m是樣本個數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def loadData(direction): trainfileList=listdir(direction) m=len(trainfileList) dataArray= zeros((m,1024)) labelArray= zeros((m,1)) for i in range(m): returnArray=zeros((1,1024)) #每個txt檔案形成的特徵向量 filename=trainfileList[i] fr=open('%s/%s' %(direction,filename)) for j in range(32): lineStr=fr.readline() for k in range(32): returnArray[0,32*j+k]=int(lineStr[k]) dataArray[i,:]=returnArray #儲存特徵向量 filename0=filename.split('.')[0] label=filename0.split('_')[0] labelArray[i]=int(label) #儲存類別 return dataArray,labelArray |
- sigmoid(inX)函式
1 2 |
def sigmoid(inX): return 1.0/(1+exp(-inX)) |
- gradAscent(dataArray,labelArray,alpha,maxCycles)函式
用梯度下降法計算得到迴歸係數,alpha是步長,maxCycles是迭代步數。
1 2 3 4 5 6 7 8 9 10 |
def gradAscent(dataArray,labelArray,alpha,maxCycles): dataMat=mat(dataArray) #size:m*n labelMat=mat(labelArray) #size:m*1 m,n=shape(dataMat) weigh=ones((n,1)) for i in range(maxCycles): h=sigmoid(dataMat*weigh) error=labelMat-h #size:m*1 weigh=weigh+alpha*dataMat.transpose()*error return weigh |
用到numpy裡面的mat,矩陣型別。shape()用於獲取矩陣的大小。
這個函式返回引數向量Θ,即權重weigh
- classfy(testdir,weigh)函式
分類函式,根據引數weigh對測試樣本進行預測,同時計算錯誤率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def classfy(testdir,weigh): dataArray,labelArray=loadData(testdir) dataMat=mat(dataArray) labelMat=mat(labelArray) h=sigmoid(dataMat*weigh) #size:m*1 m=len(h) error=0.0 for i in range(m): if int(h[i])>0.5: print int(labelMat[i]),'is classfied as: 1' if int(labelMat[i])!=1: error+=1 print 'error' else: print int(labelMat[i]),'is classfied as: 0' if int(labelMat[i])!=0: error+=1 print 'error' print 'error rate is:','%.4f' %(error/m) |
- digitRecognition(trainDir,testDir,alpha=0.07,maxCycles=10)函式
整合上面的所有函式,呼叫這個函式進行數字識別,alpha和maxCycles有預設形參,這個可以根據實際情況更改。
1 2 3 4 |
def digitRecognition(trainDir,testDir,alpha=0.07,maxCycles=10): data,label=loadData(trainDir) weigh=gradAscent(data,label,alpha,maxCycles) classfy(testDir,weigh) |
3、試驗結果
工程檔案可以到這裡下載:github地址
執行logistic regression.py,採用預設形參:alpha=0.07,maxCycles=10,看下效果,錯誤率0.0118
1 |
>>> digitRecognition('train','test') |
改變形參,alpah=0.01,maxCycles=50,看下效果,錯誤率0.0471
1 |
>>> digitRecognition('train','test',0.01,50) |
這兩個引數可以根據實際情況調整