先從一本書說起吧----《機器學習實戰》
作者在書中講到邏輯迴歸的時候,用簡短的語言介紹了一下理論之後,就給出了一段代碼。然而就是這段代碼把我帶進了誤區,也許不能叫誤區,而是因為我自己的水平不夠。後來在查閱資料的時候,發現有人也因為這個問題糾結了好久。也許這本書是寫給一些有經驗的人員看的,不是特別適合作為入門的書。
在查找關於邏輯迴歸相關資料的時候,發現大多數都是介紹了好多數學公式,所以我一直都在理解數學公式的基礎上同時試圖在腦海中演練該如何編程實現它,然後再對照上面提到的書中的代碼,然後悲哀的發現瞭解不了。並且,查到的大多數資料上並沒有詳細的代碼實現,如果有,也是跟書上的代碼是一樣的。
最後,從網上找到書中使用的測試數據,跟蹤打印代碼中的每個變量,才理解了書中第一段代碼的求解原理,進而理解了後面一些代碼的原理。現在回過頭想想確實比較簡單,但是這個簡單是有一個前提的:書上的代碼或者資料中推導出的的公式或者我自己的理解,這三者之間必須是有一個錯誤的。
並且在這個過程中,我一直試圖繞過那麼多數學公式和一堆概念,但是發現很難,所以我按照自己的理解剔除掉一些無用的數學公式和概念,力求用最少的理論解釋清楚什麼是邏輯迴歸。
1.邏輯迴歸的定義
1)有一種定義是這樣的:邏輯迴歸其實是一個線性分類器,只是在外面嵌套了一個邏輯函數,主要用於二分類問題。
這個定義明確的指明瞭邏輯迴歸的特點:
a) 一個線性分類器
b)外層有一個邏輯函數
2)假設有一個線性函數z,其一般公式為:
(公式一)
轉換成求和公式:
(公式二)
轉化成向量的形式:
(公式三)
3)邏輯函數(也叫Sigmoid函數)
基本上採用的都是下面這個函數:
(公式四)
這個函數的作用就是把無限大或者無限小的數據壓縮到[0,1]之間,用來估計概率。圖像大致為:
基本上是以0.5分界,0.5以上為1,0.5以下為0。但是這個分界值可以自己設定。
4)邏輯迴歸函數
綜合公式四和公式一或者公式四和公式三,即可得到邏輯迴歸函數:
(公式五)或者(公式六)
其實,如果編程求解的話,到這裡基本就可以了。但是既然都提到了最大似然估計,那我們也說下。
2.最大似然估計
最大似然估計是建立在這樣的思想上:已知某個參數能使這個樣本出現的概率最大,我們當然不會再去選擇其他小概率的樣本,所以乾脆就把這個參數作為估計的真實值。
換句話說就是:既然我們無法知道真實值,那麼就把這個當作真實值吧!
其唯一的作用就是給這個算法找一個說的過去的理論基礎,然後在這個基礎上推導出最大似然函數,接著構建損失函數。這對於非數學專業的人來說,用途並不大,有時候甚至會造成理解上的困難,進而變成學習的阻礙。其實,我們完全可以繞過這個阻礙,只去關注最後的結果。
3.求解方式
1)使用梯度下降法求解
經過一系列推導之後,得出梯度下降法求解的核心公式,即權重的更新方式:
(公式七)
需要說明的是:α表示下降的步長,可以自己指定。hθ表示損失函數或者懲罰係數。如果hθ 表示懲罰係數,那麼如何求的這組係數才是整個邏輯迴歸算法的重點。
在開始寫代碼前,再介紹另外一個求解方式:向量化
2)向量化
向量化是使用矩陣計算來代替for循環,以簡化計算過程,提高效率。(下面引用下其他文章的講解:出現的地方太多不知道哪個是原作者,見諒)
向量化過程: 約定訓練數據的矩陣形式如下,x的每一行為一條訓練樣本,而每一列為不同的特稱取值:
g(A)的參數A為一列向量,所以實現g函數時要支持列向量作為參數,並返回列向量。
θ更新過程可以改為:
綜上所述,向量化後θ更新的步驟如下:
a)求 A=x*θ
b)求 E=g(A)-y
c)求
4.實現過程(下面的代碼實現的是梯度上升法,其跟梯度下降法的唯一區別就是和之間的減號變成了加號,前者求最大值,後者求最小值)
代碼基本脫胎於《機器學習實戰》這本書,但是有改動。
1)普通的梯度上升法
下面這段代碼,也就是開頭提到的那個造成誤解的代碼,其實現依據是向量化求解,並不是根據公式七來的,所以如果對照公式七理解這段代碼會完全摸不著頭腦。如果對照向量化後θ(也就是權重)的更新步驟會很容易理解。
''' 普通的梯度上升法 ''' import numpy as np import os import pandas as pd def loadDataSet: ##運行腳本所在目錄 base_dir=os.getcwd ##記得添加header=None,否則會把第一行當作頭 data=pd.read_table(base_dir+r"\lr.txt",header=None) ##dataLen行dataWid列 :返回值是dataLen=100 dataWid=3 dataLen,dataWid = data.shape ##訓練數據集 xList = ##標籤數據集 lables = ##讀取數據 for i in range(dataLen): row = data.values[i] xList.append(row[0:dataWid-1]) lables.append(row[-1]) return xList,lables ##邏輯函數 def sigmoid(inX): return 1.0/(1+np.exp(-inX)) ##梯度上升函數 def gradAscent(datamatIn,classLables): ##把datamatIn從列表轉換成矩陣 dataMatrix = np.mat(datamatIn) ##把列表轉換成100行1列的矩陣,而np.mat(classLables)是轉換成1行100列的矩陣 labelMat = np.mat(classLables).transpose ##求矩陣的長寬 m,n = np.shape(dataMatrix) ##步長,可以自己設置 alpha = 0.001 ##最大循環次數 maxTry = 500 ##初始化向量:2行1列的矩陣 weights =np.ones((n,1)) ##循環一定次數,求權重 for k in range(maxTry): ##dataMatrix 100行2列 weights是2行1列 ##h是100行1列 h = sigmoid(dataMatrix*weights) ##向量的偏差 error = (labelMat - h) ##dataMatrix.transpose 轉換成2行100列的矩陣 ##error 是100行1列 ##weights是2行1列的值 weights = weights + alpha*dataMatrix.transpose*error return weights ''' 結果大於0.3的設置為1,正確率基本100% ''' def GetResult: dataMat,labelMat=loadDataSet weights=gradAscent(dataMat,labelMat) dataMatrix = np.mat(dataMat) ##求的最後的結果 h = sigmoid(dataMatrix*weights) ##打印結果,觀察數據 for i in range(len(h)): print(str(h[i])+":"+str(labelMat[i])) #print(h) #print(weights) ##0.08108752 -0.1233496 if __name__=='__main__': GetResult
2)隨機梯度上升發
這個算法,才是符合公式七的算法,但是代碼中並沒有求和這步,只有括號中的那部分,這也是我開頭說的三者之間必有一個錯誤的地方。
只包括核心部分,其他部分見上段代碼
''' 結果大於0.29或者0.26都可以,也只有1-2個分類錯誤 weights:[ 0.0868611 -0.13086297] ''' ##隨機梯度上升算法 def gradAscent(datamatIn,classLables): m,n = np.shape(datamatIn) ##步長,可以自己指定,決定收斂速度 alpha = 0.001 ##最大循環次數 maxTry = 200 ##初始化權重:列表而不是矩陣 weights =np.ones(n) ##循環求解:在整個數據集上循環 for k in range(maxTry): ##對每行進行處理 for i in range(m): ##每行向量化 h = sigmoid(sum(datamatIn[i]*weights)) ##每行向量偏差 error = (classLables[i] - h) ##更新權重 weights = weights +alpha*error*datamatIn[i] return weights ##打印結果 def GetResult: dataMat,labelMat=loadDataSet weights=gradAscent(dataMat,labelMat) m,n = np.shape(dataMat) for i in range(m): h = sigmoid(sum(dataMat[i]*weights)) print(str(h)+" : "+str(labelMat[i])) #print(weights)
3)改進的隨機梯度上升算法
書中還講到了一個改進的隨機梯度上升算法。
##隨機梯度上升函數 def gradAscent(datamatIn,classLables): m,n = np.shape(datamatIn) ##循環次數 maxTry = 150 ##初始化權重:列表 weights =np.ones(n) ##循環求解 for j in range(maxTry): ##在整個數據集上循環 for i in range(m): ##跟新alpha,即跟新步長值 alpha = 4/(1.0+j+i)+0.01 ##隨機抽取一個下標 randIndex = int(np.random.uniform(0,m)) ##對抽到下標的數據行進行求值 h = sigmoid(sum(datamatIn[randIndex]*weights)) ##求得誤差值 error = classLables[randIndex] - h ##更新權重 weights = weights +alpha*error*datamatIn[randIndex] return weights
該算法每次都會調整步長值,即緩解了隨著循環次數的增加造成的特徵值的波動,也保證了當j<<max(i)時,步長值的下降不是嚴格下降的。而避免參數的嚴格下降在優化退火算法中常常用到。
5.使用sklearn包中的邏輯迴歸算法(非完整代碼,缺少部分在第一個代碼段)
sklearn包中的LogisticRegression函數,默認使用L2正則化防止過度擬合。
from sklearn.linear_model import LogisticRegression def sk_lr(X_train,y_train): model = LogisticRegression model.fit(X_train, y_train) model.score(X_train,y_train) #print('權重',model.coef_) return model.predict(X_train) ##分類錯了2個 def GetResult: dataMat,labelMat=loadDataSet pred = sk_lr(dataMat,labelMat) for i in range(len(pred)): print(str(pred[i])+" : "+str(labelMat[i])) if __name__=='__main__': GetResult
最後得出的預測結果就是0,1值,跟標籤對比,有兩個分類錯了。
6.邏輯迴歸優缺點
優點:計算代價不高,易於理解和實現
缺點:容易欠擬合,分類精度可能不高
適用數據類型:數值型和標稱型數據
附錄:測試數據
-0.017612 14.053064 0 -1.395634 4.662541 1 -0.752157 6.538620 0 -1.322371 7.152853 0 0.423363 11.054677 0 0.406704 7.067335 1 0.667394 12.741452 0 -2.460150 6.866805 1 0.569411 9.548755 0 -0.026632 10.427743 0 0.850433 6.920334 1 1.347183 13.175500 0 1.176813 3.167020 1 -1.781871 9.097953 0 -0.566606 5.749003 1 0.931635 1.589505 1 -0.024205 6.151823 1 -0.036453 2.690988 1 -0.196949 0.444165 1 1.014459 5.754399 1 1.985298 3.230619 1 -1.693453 -0.557540 1 -0.576525 11.778922 0 -0.346811 -1.678730 1 -2.124484 2.672471 1 1.217916 9.597015 0 -0.733928 9.098687 0 -3.642001 -1.618087 1 0.315985 3.523953 1 1.416614 9.619232 0 -0.386323 3.989286 1 0.556921 8.294984 1 1.224863 11.587360 0 -1.347803 -2.406051 1 1.196604 4.951851 1 0.275221 9.543647 0 0.470575 9.332488 0 -1.889567 9.542662 0 -1.527893 12.150579 0 -1.185247 11.309318 0 -0.445678 3.297303 1 1.042222 6.105155 1 -0.618787 10.320986 0 1.152083 0.548467 1 0.828534 2.676045 1 -1.237728 10.549033 0 -0.683565 -2.166125 1 0.229456 5.921938 1 -0.959885 11.555336 0 0.492911 10.993324 0 0.184992 8.721488 0 -0.355715 10.325976 0 -0.397822 8.058397 0 0.824839 13.730343 0 1.507278 5.027866 1 0.099671 6.835839 1 -0.344008 10.717485 0 1.785928 7.718645 1 -0.918801 11.560217 0 -0.364009 4.747300 1 -0.841722 4.119083 1 0.490426 1.960539 1 -0.007194 9.075792 0 0.356107 12.447863 0 0.342578 12.281162 0 -0.810823 -1.466018 1 2.530777 6.476801 1 1.296683 11.607559 0 0.475487 12.040035 0 -0.783277 11.009725 0 0.074798 11.023650 0 -1.337472 0.468339 1 -0.102781 13.763651 0 -0.147324 2.874846 1 0.518389 9.887035 0 1.015399 7.571882 0 -1.658086 -0.027255 1 1.319944 2.171228 1 2.056216 5.019981 1 -0.851633 4.375691 1 -1.510047 6.061992 0 -1.076637 -3.181888 1 1.821096 10.283990 0 3.010150 8.401766 1 -1.099458 1.688274 1 -0.834872 -1.733869 1 -0.846637 3.849075 1 1.400102 12.628781 0 1.752842 5.468166 1 0.078557 0.059736 1 0.089392 -0.715300 1 1.825662 12.693808 0 0.197445 9.744638 0 0.126117 0.922311 1 -0.679797 1.220530 1 0.677983 2.556666 1 0.761349 10.693862 0 -2.168791 0.143632 1 1.388610 9.341997 0 0.317029 14.739025 0
View Code