上手機器學習系列-第3篇(下)-聊聊logistic迴歸

書接上回

在之前的上、中篇,我們分別聊了logistic迴歸的基本思想、sklearn中的實驗方法,本篇中我們一起讀一下sklearn中logistic的源代碼,一窺其究竟,進一步加深我們對該方法的理解。

代碼地址

Github中sklearn的地址爲:https://github.com/scikit-learn/scikit-learn,在下屬子錄中,依次去找sklearn -> linear_model -> _logistic.py, 就能找到我們想看的代碼了。

看的過程中,建議結合sklearn官方文檔來進行理解。

Logistic迴歸源代碼

本小節我們結合一個實際使用案例來探索源代碼的調用邏輯。

我們先梳理一下sklearn調用的固定套路:
在這裏插入圖片描述

起點

回顧上節我們建立一個logistic迴歸模型時的執行命令:

from sklearn.linear_model import LogisticRegression
clf = LogisticRegression(random_state=0)

所以這裏首先是創建了一個類LogisticRegression的實例,這也是我們正式引用該模型的起點。所以我們先去源代碼中找該類的定義。

在其源碼文件中,我們看到了兩個相似的類定義,一個叫

class LogisticRegression(BaseEstimator, LinearClassifierMixin,SparseCoefMixin)

另一個叫

class LogisticRegressionCV(LogisticRegression, BaseEstimator, LinearClassifierMixin):

從名字也可以看出LogisticRegressionCV一定是增加了交叉驗證(Cross Validation)功能的版本。我們先來搞懂基礎版本是怎麼做的。

擬合數據

創建了LogisticRegression的實例之後,我們就去擬合數據了,回憶一下具體的做法是:

clf = clf.fit(X_train, Y_train)

所以接下來就要去看fit是怎麼定義的。原代碼比較長,我們不復制了,逐段來走讀一下。

最好是帶着問題來讀,找我們重點關注的部分,否則容易陷入細節中。fit了數據,到底是怎麼fit的,根據logistic迴歸的算法原理, 我們猜想是把特徵數據X代入某個線性方程,然後定義一個損失函數,再來求解該損失函數,得到讓其最小化/或者最大化的參數。那麼就需要去fit中找這個求解損失函數的地方。

看的過程中, 我們就會發現,原來這跟使用的參數solver有關係啊!從LogisticRegression使用文檔上的介紹能更清楚地瞭解solver有哪些可選項:

solver{‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’, ‘saga’}, default=’lbfgs’
Algorithm to use in the optimization problem.

For small datasets, ‘liblinear’ is a good choice, whereas ‘sag’ and ‘saga’ are faster for large ones.

For multiclass problems, only ‘newton-cg’, ‘sag’, ‘saga’ and ‘lbfgs’ handle multinomial loss; ‘liblinear’ is limited to one-versus-rest schemes.

‘newton-cg’, ‘lbfgs’, ‘sag’ and ‘saga’ handle L2 or no penalty

‘liblinear’ and ‘saga’ also handle L1 penalty

‘saga’ also supports ‘elasticnet’ penalty

‘liblinear’ does not support setting penalty='none'

Note that ‘sag’ and ‘saga’ fast convergence is only guaranteed on features with approximately the same scale. You can preprocess the data with a scaler from sklearn.preprocessing.

New in version 0.17: Stochastic Average Gradient descent solver.

New in version 0.19: SAGA solver.

Changed in version 0.22: The default solver changed from ‘liblinear’ to ‘lbfgs’ in 0.22.

從以上內容中,我們可看出來每個solver求解方法適用於什麼樣的場景,並與哪一種懲罰項比較搭配。以後在創建LogisticRegression時,我們就可以自己指定更多自定義的參數了。而且從源代碼中,作者還貼心地給出了每種優化方法的參考文獻來源(1217行~1236行),看看人家的代碼註釋,真是信息量豐富啊。

當solver爲liblinear時,我們看到這樣一段:
(img)
這裏就直接return self了,所以如果是liblinear求解方法,這裏調用了_fit_liblinear 去求解self.coef_, self.intercept_, n_iter_。如果你繼續去挖這個方法的實現,可以發現它來自於用CPython寫的一個求解方法,這裏我們暫不展開了。

當solver爲其它時, 我們看到了這樣一段:
在這裏插入圖片描述
sag代表Stochastic Average Gradient,saga是其改進版本。不糾結細節的話,我們猜想這裏定義的max_squared_sum是打算要輸入給某一個損失函數。
往下找的時候,發現在下面這一段使用上了:
在這裏插入圖片描述
而這裏的path_func又是通過下面的命令定義的:

path_func = delayed(_logistic_regression_path)

上面出現的Parallel,delayed 都是python並行編程中的一種技巧,與本算法無關,我們有選擇性忽視這些信息,繼續找線索。delayed裏面的傳入參數_logistic_regression_path 的定義中,我們看到:

所以這個函數裏面就實現sag、saga這兩個solver的求解方法,且利用了並行編程,所以實現了更快的速度。這也呼應了我們在上面看到過的一句對不同solver的解釋時提到sag、saga對於大規模數據集更快速:
(img)
如果繼續找,會發現最終是使用了下面這樣一段來求解:
(img)
感興趣的同學可以繼續去看這個方法的實現,它是在另一個文件中實現的方法,這裏只是直接調用了。可見現實世界中,大的軟件包就是一塊一塊的磚頭壘起來的,有着一層一層的依賴關係。

回到主線上,繼續往下讀源碼。
(img)
這裏定義的classes_、n_classes 都是爲了用於下面的並行計算。這裏至於爲啥當n_classes = 2時,要人爲改爲1,筆者的理解是二分類時,其實我們只需要去預測class = 1 的那一個類,剩下的自然就是另一類了。而多分類時,是one - vs -others, 針對每一個類都要做一次求解。_

帶CV的版本

我們再來看看LogisticRegressionCV定義的有哪些不同之處。在實際使用時,該類的實例創建方法爲:

clf = LogisticRegressionCV(cv=5, random_state=0).fit(X, y) 

可見這裏核心是理解這個cv的參數怎麼使用。
我們關注的第一點不同是folds = list(cv.split(X, y)),根據我們對交叉驗證方法原理的理解,這裏是把數據集均拆了若干份,然後肯定是逐一拿出一份做爲驗證集。所以要去找它是怎麼循環使用這些拆分的數據。

往下找,果然找到一個for循環,也是用到了上文已經提到的那個並行計算方法中。
在這裏插入圖片描述
針對交叉驗證,對於參數的最終求解又是怎麼在多個fold的結果上進行綜合呢?
(img)
其它的內容,主體邏輯就與不帶KV版本的相似了。

結語

本篇囉囉嗦嗦講了這麼多,其實是希望與讀者一起通讀一篇sklearn中對於logistic迴歸的實現過程,雖然其中可能仍有一些細節我們並不清楚其底層是怎樣實現的,但相比於僅調用一個類的接口,相信我們對於該方法已經有了更深入的瞭解。也順便複習了算法本身的一些設計。

至此,我們就完成了上、中、下三篇對於logistic迴歸的分享,其中並未面面俱到,而是有選擇地對一些知識點進行了思考。希望讀到這些文章的朋友能有所裨益。

歡迎關注公衆號

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章