sklearn中的逻辑回归
sklearn中的邏輯回歸
目錄sklearn中的邏輯回歸1 概述1.1 名為“回歸”的分類器1.2 為什么需要邏輯回歸1.3 sklearn中的邏輯回歸2 linear_model.LogisticRegression2.1 二元邏輯回歸的損失函數2.1.1 損失函數的概念與解惑2.2 重要參數penalty & C2.2.1 正則化2.2.2 邏輯回歸中的特征工程R vs Python,統計學 vs 機器學習2.3 梯度下降:重要參數max_iter2.3.1 梯度下降求解邏輯回歸2.3.2 梯度下降的概念與解惑2.3.3 步長的概念與解惑2.4 二元回歸與多元回歸:重要參數solver & multi_class2.5 樣本不平衡與參數class_weight3 案例:用邏輯回歸制作評分卡3.1 導庫,獲取數據3.2 探索數據與數據預處理3.2.1 去除重復值3.2.2 填補缺失值3.2.3 描述性統計處理異常值3.2.4 為什么不統一量綱,也不標準化數據分布?3.2.5 樣本不均衡問題3.3 分箱3.3.1 等頻分箱
1 概述
1.1 名為“回歸”的分類器
在過去的四周中,我們接觸了不少帶“回歸”二字的算法,回歸樹,隨機森林的回歸,無一例外他們都是區別于分類算法們,用來處理和預測連續型標簽的算法。然而邏輯回歸,是一種名為“回歸”的線性分類器,其本質是由線性回歸變化而來的,一種廣泛使用于分類問題中的廣義回歸算法。要理解邏輯回歸從何而來,得要先理解線性回歸。線性回歸是機器學習中最簡單的的回歸算法,它寫作一個幾乎人人熟悉的方程:
[z= heta_0+ heta_2x_2+...+ heta _nx_n
]
( heta)被統稱為模型的參數,其中( heta_0)被稱為截距(intercept), ( heta_1)~( heta_n)被稱為系數(coe?cient),這個表達式,其實就和我 們小學時就無比熟悉的(y=ax+b)是同樣的性質。我們可以使用矩陣來表示這個方程,其中x和 都可以被看做是一 個列矩陣,則有:
[z=[ heta_0, heta_1, heta_2... heta_n]*egin{bmatrix}
x_0\
x_1\
x_2\
...\
x_n
end{bmatrix}
= heta^Tx(x_0=1)
]
線性回歸的任務,就是構造一個預測函數 來映射輸入的特征矩陣x和標簽值y的線性關系,而構造預測函數的核心 就是找出模型的參數:( heta_0)和( heta^T) ,著名的最小二乘法就是用來求解線性回歸中參數的數學方法。
通過函數 ,線性回歸使用輸入的特征矩陣X來輸出一組連續型的標簽值y_pred,以完成各種預測連續型變量的任務 (比如預測產品銷量,預測股價等等)。那如果我們的標簽是離散型變量,尤其是,如果是滿足0-1分布的離散型變量,我們要怎么辦呢?我們可以通過引入聯系函數(link function),將線性回歸方程z變換為g(z),并且令g(z)的值 分布在(0,1)之間,且當g(z)接近0時樣本的標簽為類別0,當g(z)接近1時樣本的標簽為類別1,這樣就得到了一個分 類模型。而這個聯系函數對于邏輯回歸來說,就是Sigmoid函數:
[g(z)=frac{1}{1 + e^{-z}}
]
| 面試高危問題:Sigmoid函數的公式和性質 |
|---|
| Sigmoid函數是一個S型的函數,當自變量z趨近正無窮時,因變量g(z)趨近于1,而當z趨近負無窮時,g(z)趨近 于0,它能夠將任何實數映射到(0,1)區間,使其可用于將任意值函數轉換為更適合二分類的函數。 因為這個性質,Sigmoid函數也被當作是歸一化的一種方法,與我們之前學過的MinMaxSclaer同理,是屬于 數據預處理中的“縮放”功能,可以將數據壓縮到[0,1]之內。區別在于,MinMaxScaler歸一化之后,是可以取 到0和1的(最大值歸一化后就是1,最小值歸一化后就是0),但Sigmoid函數只是無限趨近于0和1。 |
線性回歸中(z= heta^Tx) ,于是我們將 帶入,就得到了二元邏輯回歸模型的一般形式:
[g(z)=frac{1}{1 + e^{- heta^Tx}}
]
而(y(x))就是我們邏輯回歸返回的標簽值。此時, (y(x))的取值都在[0,1]之間,因此(y(x))和(1-y(x))相加必然為1。如果我們令 除以 可以得到形似幾率(odds)的(frac{y(x)}{1-y(x)}),在此基礎上取對數,可以很容易就得到:
[lnfrac{y(x)}{1-y(x)}=ln()
]
不難發現,y(x)的形似幾率取對數的本質其實就是我們的線性回歸z,我們實際上是在對線性回歸模型的預測結果取 對數幾率來讓其的結果無限逼近0和1。因此,其對應的模型被稱為”對數幾率回歸“(logistic Regression),也就 是我們的邏輯回歸,這個名為“回歸”卻是用來做分類工作的分類器。
之前我們提到過,線性回歸的核心任務是通過求解 構建 這個預測函數,并希望預測函數 能夠盡量擬合數據,因此邏輯回歸的核心任務也是類似的:求解 來構建一個能夠盡量擬合數據的預測函數 ,并通過向預測函數中輸入特征矩陣來獲取相應的標簽值y。
| 思考:y(x)代表了樣本為某一類標簽的概率嗎? |
|---|
| (lnfrac{y(x)}{1-y(x)})是形似對數幾率的一種變化。而幾率odds的本質其實是 (frac{p}{1-p}),其中p是事件A發生的概率,而1-p是事 件A不會發生的概率,并且p+(1-p)=1。因此,很多人在理解邏輯回歸時,都對(y(x))做出如下的解釋: 我們讓線性回歸結果逼近0和1,此時(y(x))和(1-y(x))之和為1,因此它們可以被我們看作是一對正反例發生的概率,即(y(x))是某樣本i的標簽被預測為1的概率,而(1-y(x))是i的標簽被預測為0的概率, (frac{y(x)}{1-y(x)})就是樣本i的 標簽被預測為1的相對概率。基于這種理解,我們使用最大似然法和概率分布函數推到出邏輯回歸的損失函數,并且把返回樣本在標簽取值上的概率當成是邏輯回歸的性質來使用,每當我們訴求概率的時候,我們都會使用邏輯回歸。 然而這種理解是正確的嗎?概率是度量偶然事件發生可能性的數值,盡管邏輯回歸的取值在(0,1)之間,并且(y(x))和(1-y(x))之和的確為1,但光憑這個性質,我們就可以認為(y(x))代表了樣本x在標簽上取值為1的概率 嗎?設想我們使用MaxMinScaler對特征進行歸一化后,任意特征的取值也在[0,1]之間,并且任意特征的取值(x_0)和(1-x_0)也能夠相加為1,但我們卻不會認為0-1歸一化后的特征是某種概率。邏輯回歸返回了概率這個命題,這種說法嚴謹嗎 但無論如何,長年以來人們都是以”返回概率“的方式來理解邏輯回歸,并且這樣使用它的性質。可以說,邏輯回歸返回的數字,即便本質上不是概率,卻也有著概率的各種性質,可以被當成是概率來看待和使用。 |
1.2 為什么需要邏輯回歸
線性回歸對數據的要求很嚴格,比如標簽必須滿足正態分布,特征之間的多重共線性需要消除等等,而現實中很多真實情景的數據無法滿足這些要求,因此線性回歸在很多現實情境的應用效果有限。邏輯回歸是由線性回歸變化而來,因此它對數據也有一些要求,而我們之前已經學過了強大的分類模型決策樹和隨機森林,它們的分類效力很強,并且不需要對數據做任何預處理。
何況,邏輯回歸的原理其實并不簡單。一個人要理解邏輯回歸,必須要有一定的數學基礎,必須理解損失函數,正則化,梯度下降,海森矩陣等等這些復雜的概念,才能夠對邏輯回歸進行調優。其涉及到的數學理念,不比支持向量機少多少。況且,要計算概率,樸素貝葉斯可以計算出真正意義上的概率,要進行分類,機器學習中能夠完成二分類功能的模型簡直多如牛毛。因此,在數據挖掘,人工智能所涉及到的醫療,教育,人臉識別,語音識別這些領域,邏輯回歸沒有太多的出場機會。
甚至,在我們的各種機器學習經典書目中,周志華的《機器學習》400頁僅有一頁紙是關于邏輯回歸的(還是一頁數學公式),《數據挖掘導論》和《Python數據科學手冊》中完全沒有邏輯回歸相關的內容,sklearn中對比各種 分類器的效應也不帶邏輯回歸玩,可見業界地位。
但是,無論機器學習領域如何折騰,邏輯回歸依然是一個受工業商業熱愛,使用廣泛的模型,因為它有著不可替代的優點:
邏輯回歸對線性關系的擬合效果好到喪心病狂,特征與標簽之間的線性關系極強的數據,比如金融領域中的信用卡欺詐,評分卡制作,電商中的營銷預測等等相關的數據,都是邏輯回歸的強項。雖然現在有了梯度提升樹GDBT,比邏輯回歸效果更好,也被許多數據咨詢公司啟用,但邏輯回歸在金融領域,尤其是銀行業中的
統治地位依然不可動搖(相對的,邏輯回歸在非線性數據的效果很多時候比瞎猜還不如,所以如果你已經知道數據之間的聯系是非線性的,千萬不要迷信邏輯回歸)
邏輯回歸計算快:對于線性數據,邏輯回歸的擬合和計算都非常快,計算效率優于SVM和隨機森林,親測表示在大型數據上尤其能夠看得出區別
邏輯回歸返回的分類結果不是固定的0,1,而是以小數形式呈現的類概率數字:我們因此可以把邏輯回歸返回的結果當成連續型數據來利用。比如在評分卡制作時,我們不僅需要判斷客戶是否會違約,還需要給出確定的”信用分“,而這個信用分的計算就需要使用類概率計算出的對數幾率,而決策樹和隨機森林這樣的分類器,可以產出分類結果,卻無法幫助我們計算分數(當然,在sklearn中,決策樹也可以產生概率,使用接口 predict_proba調用就好,但一般來說,正常的決策樹沒有這個功能)。
另外,邏輯回歸還有抗噪能力強的優點。福布斯雜志在討論邏輯回歸的優點時,甚至有著“技術上來說,最佳模型的AUC面積低于0.8時,邏輯回歸非常明顯優于樹模型”的說法。并且,邏輯回歸在小數據集上表現更好,在大型的數據集上,樹模型有著更好的表現。
由此,我們已經了解了邏輯回歸的本質,它是一個返回對數幾率的,在線性數據上表現優異的分類器,它主要被應用在金融領域。其數學目的是求解能夠讓模型對數據擬合程度最高的參數 的值,以此構建預測函數 ,然后將特征矩陣輸入預測函數來計算出邏輯回歸的結果y。注意,雖然我們熟悉的邏輯回歸通常被用于處理二分類問題, 但邏輯回歸也可以做多分類。
1.3 sklearn中的邏輯回歸
| 邏輯回歸相關的類 | 說明 |
|---|---|
| linear_model.LogisticRegression | 邏輯回歸分類器(又叫logit回歸,最大熵分類器) |
| linear_model.LogisticRegressionCV | 帶交叉驗證的邏輯回歸分類器 |
| linear_model.logistic_regression_path | 計算Logistic回歸模型以獲得正則化參數的列表 |
| linear_model.SGDClassi?er | 利用梯度下降求解的線性分類器(SVM,邏輯回歸等等) |
| linear_model.SGDRegressor | 利用梯度下降最小化正則化后的損失函數的線性回歸模型 |
| metrics.log_loss | 對數損失,又稱邏輯損失或交叉熵損失 |
| 【在sklearn0.21版本中即將被移除】 | |
|---|---|
| linear_model.RandomizedLogisticRegression | 隨機的邏輯回歸 |
| 其他會涉及的類 | 說明 |
|---|---|
| metrics.confusion_matrix | 混淆矩陣,模型評估指標之一 |
| metrics.roc_auc_score | ROC曲線,模型評估指標之一 |
| metrics.accuracy_score | 精確性,模型評估指標之一 |
2 linear_model.LogisticRegression
class sklearn.linear_model.LogisticRegression (penalty=’l2’, dual=False, tol=0.0001, C=1.0, ?t_intercept=True, intercept_scaling=1, class_weight=None, random_state=None, solver=’warn’, max_iter=100, multi_class=’warn’, verbose=0, warm_start=False, n_jobs=None)
2.1 二元邏輯回歸的損失函數
2.1.1 損失函數的概念與解惑
在學習決策樹和隨機森林時,我們曾經提到過兩種模型表現:在訓練集上的表現,和在測試集上的表現。我們建模,是追求模型在測試集上的表現最優,因此模型的評估指標往往是用來衡量模型在測試集上的表現的。然而,邏輯回歸有著基于訓練數據求解參數 的需求,并且希望訓練出來的模型能夠盡可能地擬合訓練數據,即模型在訓練集上的預測準確率越靠近100%越好。
因此,我們使用”損失函數“這個評估指標,來衡量參數為 的模型擬合訓練集時產生的信息損失的大小,并以此衡量參數 的優劣。如果用一組參數建模后,模型在訓練集上表現良好,那我們就說模型擬合過程中的損失很小,損失函數的值很小,這一組參數就優秀;相反,如果模型在訓練集上表現糟糕,損失函數就會很大,模型就訓練不足,效果較差,這一組參數也就比較差。即是說,我們在求解參數 時,追求損失函數最小,讓模型在訓練數據上的擬合效果最優,即預測準確率盡量靠近100%。
| 關鍵概念:損失函數 |
|---|
| 衡量參數 的優劣的評估指標,用來求解最優參數的工具 損失函數小,模型在訓練集上表現優異,擬合充分,參數優秀 損失函數大,模型在訓練集上表現差勁,擬合不足,參數糟糕 我們追求,能夠讓損失函數最小化的參數組合 注意:沒有”求解參數“需求的模型沒有損失函數,比如KNN,決策樹 |
邏輯回歸的損失函數是由極大似然估計推導出來的,具體結果可以寫作:
[J( heta)=-sum_{i=1}^{m}(y_i*log(y_ heta(x_i))+(1-y_i)*log(1-y_{ heta}(x_i)))
]
其中, 表示求解出來的一組參數,m是樣本的個數, (y_i)是樣本i上真實的標簽, (y_ heta(x_i))是樣本i上,基于參數 計算 出來的邏輯回歸返回值, 是樣本i各個特征的取值。我們的目標,就是求解出使(J( heta))最小的$ heta (取值。注意,在邏輯 回歸的本質函數y(x)里,特征矩陣x是自變量,參數是) heta $ 。但在損失函數中,參數$ heta $是損失函數的自變量,x和y都是已 知的特征矩陣和標簽,相當于是損失函數的參數。不同的函數中,自變量和參數各有不同,因此大家需要在數學計算中,尤其是求導的時候避免混淆。
由于我們追求損失函數的最小值,讓模型在訓練集上表現最優,可能會引發另一個問題:如果模型在訓練集上表示優秀,卻在測試集上表現糟糕,模型就會過擬合。雖然邏輯回歸和線性回歸是天生欠擬合的模型,但我們還是需要控制過擬合的技術來幫助我們調整模型,對邏輯回歸中過擬合的控制,通過正則化來實現。
2.2 重要參數penalty & C
2.2.1 正則化
正則化是用來防止模型過擬合的過程,常用的有L1正則化和L2正則化兩種選項,分別通過在損失函數后加上參數向量 的L1范式和L2范式的倍數來實現。這個增加的范式,被稱為“正則項”,也被稱為"懲罰項"。損失函數改變,基于損失函數的最優化來求解的參數取值必然改變,我們以此來調節模型擬合的程度。其中L1范式表現為參數向量中的每個參數的絕對值之和,L2范數表現為參數向量中的每個參數的平方和的開方值。
[J( heta)_{L1}=C*J( heta)+sum_{j=1}^{n}| heta_j|(jgeq 1)\
J( heta)_{L2}=C*J( heta)+sqrt{sum_{j=1}^{n}( heta_j)^2}(jgeq 1)
]
其中 是我們之前提過的損失函數,C是用來控制正則化程度的超參數,n是方程中特征的總數,也是方程中參 數的總數,j代表每個參數。在這里,j要大于等于1,是因為我們的參數向量 中,第一個參數是 ,是我們的截 距,它通常是不參與正則化的。
在許多書籍和博客中,大家可能也會見到如下的寫法:
[J( heta)_{L1}=J( heta)+frac{1}{2b^2}sum_{j=1}^{n}| heta_j|\
J( heta)_{L2}=C*J( heta)+frac{ heta^T heta}{2sigma^2 }
]
其實和上面我們展示的式子的本質是一模一樣的。不過在大多數教材和博客中,常數項是乘以正則項,通過調控正則項來調節對模型的懲罰。而sklearn當中,常數項C是在損失函數的前面,通過調控損失函數本身的大小,來調節 對模型的懲罰。
| 參數 | 說明 |
|---|---|
| penalty | 可以輸入"l1"或"l2"來指定使用哪一種正則化方式,不填寫默認"l2"。 注意,若選擇"l1"正則化,參數solver僅能夠使用求解方式”liblinear"和"saga“,若使用“l2”正則化,參數solver中所有的求解方式都可以使用。 |
| C | C正則化強度的倒數,必須是一個大于0的浮點數,不填寫默認1.0,即默認正則項與損失函數的比值是1:1。C越小,損失函數會越小,模型對損失函數的懲罰越重,正則化的效力越強,參數會逐漸被壓縮得越來越小。 |
L1正則化和L2正則化雖然都可以控制過擬合,但它們的效果并不相同。當正則化強度逐漸增大(即C逐漸變小),參數 的取值會逐漸變小,但L1正則化會將參數壓縮為0,L2正則化只會讓參數盡量小,不會取到0。
在L1正則化在逐漸加強的過程中,攜帶信息量小的、對模型貢獻不大的特征的參數,會比攜帶大量信息的、對模型有巨大貢獻的特征的參數更快地變成0,所以L1正則化本質是一個特征選擇的過程,掌管了參數的“稀疏性”。L1正則化越強,參數向量中就越多的參數為0,參數就越稀疏,選出來的特征就越少,以此來防止過擬合。因此,如果特征量很大,數據維度很高,我們會傾向于使用L1正則化。由于L1正則化的這個性質,邏輯回歸的特征選擇可以由Embedded嵌入法來完成。
相對的,L2正則化在加強的過程中,會盡量讓每個特征對模型都有一些小的貢獻,但攜帶信息少,對模型貢獻不大的特征的參數會非常接近于0。通常來說,如果我們的主要目的只是為了防止過擬合,選擇L2正則化就足夠了。但是如果選擇L2正則化后還是過擬合,模型在未知數據集上的效果表現很差,就可以考慮L1正則化。
而兩種正則化下C的取值,都可以通過學習曲線來進行調整。
建立兩個邏輯回歸,L1正則化和L2正則化的差別就一目了然了:
from sklearn.linear_model import LogisticRegression as LR
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_breast_cancer
import numpy as np
import matplotlib.pyplot as plt
data = load_breast_cancer()
X = data.data
y = data.target
lrl1 = LR(penalty="l1",solver="liblinear",C=0.5,max_iter=1000)
lrl2 = LR(penalty="l2",solver="liblinear",C=0.5,max_iter=1000)
lrl1 = lrl1.fit(X,y)
# 重要屬性coef_,查看每個特征對應的參數
lrl1.coef_
# 參數中不為零的個數
(lrl1.coef_ != 0).sum(axis=1) # array([10])
lrl2 = lrl2.fit(X,y)
lrl2.coef_
可以看見,當我們選擇L1正則化的時候,許多特征的參數都被設置為了0,這些特征在真正建模的時候,就不會出現在我們的模型當中了,而L2正則化則是對所有的特征都給出了參數。
究竟哪個正則化的效果更好呢?還是都差不多?
l1 = []
l2 = []
l1_test = []
l2_test = []
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=520)
for i in np.linspace(0.05,1,19):
lrl1 = LR(penalty="l1",solver="liblinear",C=i,max_iter=1000)
lrl2 = LR(penalty="l2",solver="liblinear",C=i,max_iter=1000)
lrl1 = lrl1.fit(Xtrain,Ytrain)
l1.append(accuracy_score(lrl1.predict(Xtrain),Ytrain))
l1_test.append(accuracy_score(lrl1.predict(Xtest),Ytest))
lrl2 = lrl2.fit(Xtrain,Ytrain)
l2.append(accuracy_score(lrl2.predict(Xtrain),Ytrain))
l2_test.append(accuracy_score(lrl2.predict(Xtest),Ytest))
import matplotlib.pyplot as plt
graph = [l1,l2,l1_test,l2_test]
color = ["green","black","lightgreen","gray"]
label = ["l1","l2","l1_test","l2_test"]
plt.figure(figsize=(6,6))
for i in range(len(graph)):
plt.plot(np.linspace(0.05,1,19),graph[i],color[i],label=label[i])
plt.legend(loc=4)
plt.savefig("C_values",dpi=300)
plt.show()
可見,至少在我們的乳腺癌數據集下,兩種正則化的結果區別不大。但隨著C的逐漸變大,正則化的強度越來越小,模型在訓練集和測試集上的表現都呈上升趨勢,直到C=0.6左右,訓練集上的表現依然在走高,但模型在未知數據集上的表現開始下跌,這時候就是出現了過擬合。我們可以認為,C設定為0.6會比較好。在實際使用時,基本就默認使用l2正則化,如果感覺到模型的效果不好,那就換L1試試看。
2.2.2 邏輯回歸中的特征工程
當特征的數量很多的時候,我們出于業務考慮,也出于計算量的考慮,希望對邏輯回歸進行特征選擇來降維。比如,在判斷一個人是否會患乳腺癌的時候,醫生如果看5~8個指標來確診,會比需要看30個指標來確診容易得多。
業務選擇
說到降維和特征選擇,首先要想到的是利用自己的業務能力進行選擇,肉眼可見明顯和標簽有關的特征就是需要留下的。當然,如果我們并不了解業務,或者有成千上萬的特征,那我們也可以使用算法來幫助我們。或者,可以讓算法先幫助我們篩選過一遍特征,然后在少量的特征中,我們再根據業務常識來選擇更少量的特征。
PCA和SVD一般不用
說到降維,我們首先想到的是之前提過的高效降維算法,PCA和SVD,遺憾的是,這兩種方法大多數時候不適用于邏輯回歸。邏輯回歸是由線性回歸演變而來,線性回歸的一個核心目的是通過求解參數來探究特征X與標簽y之間的 關系,而邏輯回歸也傳承了這個性質,我們常常希望通過邏輯回歸的結果,來判斷什么樣的特征與分類結果相關,因此我們希望保留特征的原貌。PCA和SVD的降維結果是不可解釋的,因此一旦降維后,我們就無法解釋特征和標簽之間的關系了。當然,在不需要探究特征與標簽之間關系的線性數據上,降維算法PCA和SVD也是可以使用的。
統計方法可以使用,但不是非常必要
既然降維算法不能使用,我們要用的就是特征選擇方法。邏輯回歸對數據的要求低于線性回歸,由于我們不是使用最小二乘法來求解,所以邏輯回歸對數據的總體分布和方差沒有要求,也不需要排除特征之間的共線性,但如果我們確實希望使用一些統計方法,比如方差,卡方,互信息等方法來做特征選擇,也并沒有問題。過濾法中所有的方法,都可以用在邏輯回歸上。
在一些博客中有這樣的觀點:多重共線性會影響線性模型的效果。對于線性回歸來說,多重共線性會影響比較大,所以我們需要使用方差過濾和方差膨脹因子VIF(variance in?ation factor)來消除共線性。但是對于邏輯回歸,其實 不是非常必要,甚至有時候,我們還需要多一些相互關聯的特征來增強模型的表現。當然,如果我們無法通過其他方式提升模型表現,并且你感覺到模型中的共線性影響了模型效果,那懂得統計學的你可以試試看用VIF消除共線性的方法,遺憾的是現在sklearn中并沒有提供VIF的功能。
R vs Python,統計學 vs 機器學習
有許多學過R,或者和python一起學習R的小伙伴,曾向我問起各種各樣的統計學問題,因為R中有各種各樣 的統計功能,而python的統計學功能并不是那么全面。我也曾經被小伙伴們發給我的“R風格python代碼”弄得 暈頭轉向,也許R的代碼希望看起來高大上,但python之美就是簡單明了(P.S. Python開發者十分有情懷,在jupyter中輸入 import this 可以查看python中隱含的彩蛋,python制作者所寫的詩歌”python之禪“,通篇贊美了python代碼的簡單,明快,容易閱讀之美,大家感興趣的可以百度搜一搜看看翻譯)。
回歸正題,為什么python和R在統計學的功能上差異如此之大呢?也許大家聽說過,R是學統計學的人開發 的,因此整個思路都是統計學的思路,而python是學計算機的人開發的,因此整個思路都是計算機的思路, 也無怪R在處理統計問題上比python強很多了,這兩種學科不同的思路強烈反應在統計學和機器學習的各種建 模流程當中。
統計學的思路是一種“先驗”的思路,不管做什么都要先”檢驗“,先”滿足條件“,事后也要各種”檢驗“,以確保各種數學假設被滿足,不然的話,理論上就無法得出好結果。而機器學習是一種”后驗“的思路,不管三七二十一,我先讓模型跑一跑,效果不好我再想辦法,如果模型效果好,我完全不在意什么共線性,殘差不滿足正態分布,沒有啞變量之類的細節,模型效果好大過天!
作為一個非數學,非統計出身,敲python代碼的人,我完全欣賞機器學習的這種”后驗“思 路:我們追求結果,不要過于在意那些需要滿足的先決條件。對我而言,統計學是機器學習窮盡所有手段都無法解決問題后的”救星“,如果機器學習不能解決問題,我會向統計學尋求幫助,但我絕不會一開始就想著要去滿足各種統計要求。當然啦,如果大家是學統計學出身,寫R出身,大家也可以把機器學習當成是統計學手段用盡后的"救星"。統計學和機器學習是相輔相成的,大家要了解兩種思路的不同,以便在進入死胡同的時候,可以從另一個學科的思路中找到出路。只要能夠解決問題的,都是好思路!
高效的嵌入法embedded
但是更有效的方法,毫無疑問會是我們的embedded嵌入法。我們已經說明了,由于L1正則化會使得部分特征對應 的參數為0,因此L1正則化可以用來做特征選擇,結合嵌入法的模塊SelectFromModel,我們可以很容易就篩選出 讓模型十分高效的特征。注意,此時我們的目的是,盡量保留原數據上的信息,讓模型在降維后的數據上的擬合效果保持優秀,因此我們不考慮訓練集測試集的問題,把所有的數據都放入模型進行降維。
from sklearn.linear_model import LogisticRegression as LR
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.datasets import load_breast_cancer
import numpy as np
import matplotlib.pyplot as plt
from sklearn.feature_selection import SelectFromModel
from sklearn.model_selection import cross_val_score
data = load_breast_cancer()
LR_ = LR(solver="liblinear",C=0.9,random_state=520)
cross_val_score(LR_,data.data,data.target,cv=10).mean() # 0.9490601503759398
X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
X_embedded.shape # (569, 9)
cross_val_score(LR_,X_embedded,data.target,cv=10).mean() # 0.9368107769423559
看看結果,特征數量被減小到個位數,并且模型的效果卻沒有下降太多,如果我們要求不高,在這里其實就可以停下了。但是,能否讓模型的擬合效果更好呢?在這里,我們有兩種調整方式:
1)調節SelectFromModel這個類中的參數threshold,這是嵌入法的閾值,表示刪除所有參數的絕對值低于這個閾 值的特征。現在threshold默認為None,所以SelectFromModel只根據L1正則化的結果來選擇了特征,即選擇了所 有L1正則化后參數不為0的特征。我們此時,只要調整threshold的值(畫出threshold的學習曲線),就可以觀察 不同的threshold下模型的效果如何變化。一旦調整threshold,就不是在使用L1正則化選擇特征,而是使用模型的 屬性.coef_中生成的各個特征的系數來選擇。coef_雖然返回的是特征的系數,但是系數的大小和決策樹中的feature_ importances_以及降維算法中的可解釋性方差explained_vairance_概念相似,其實都是衡量特征的重要程度和貢獻度的,因此SelectFromModel中的參數threshold可以設置為coef_的閾值,即可以剔除系數小于 threshold中輸入的數字的所有特征。
fullx = []
fsx = []
threshold = np.linspace(0,(abs(LR_.fit(data.data,data.target).coef_)).max(),20)
k=0
for i in threshold:
X_embedded = SelectFromModel(LR_,threshold=i).fit_transform(data.data,data.target)
fullx.append(cross_val_score(LR_,data.data,data.target,cv=10).mean())
fsx.append(cross_val_score(LR_,X_embedded,data.target,cv=10).mean())
print((threshold[k],X_embedded.shape[1]))
plt.figure(figsize=(20,5))
plt.plot(threshold,fullx,label="fullx")
plt.plot(threshold,fsx,label="feature selection")
plt.xticks(threshold)
plt.legend()
plt.savefig("fsx",dpi=300)
plt.show()
(0.0, 30)
(0.0, 17)
(0.0, 12)
(0.0, 11)
(0.0, 8)
(0.0, 8)
(0.0, 6)
(0.0, 5)
(0.0, 5)
(0.0, 5)
(0.0, 5)
(0.0, 4)
(0.0, 2)
(0.0, 2)
(0.0, 2)
(0.0, 1)
(0.0, 1)
(0.0, 1)
(0.0, 1)
(0.0, 1)
然而,這種方法其實是比較無效的,大家可以用學習曲線來跑一跑:當threshold越來越大,被刪除的特征越來越 多,模型的效果也越來越差,模型效果最好的情況下需要保證有17個以上的特征。實際上我畫了細化的學習曲線,如果要保證模型的效果比降維前更好,我們需要保留25個特征,這對于現實情況來說,是一種無效的降維:需要30個指標來判斷病情,和需要25個指標來判斷病情,對醫生來說區別不大。
2)第二種調整方法,是調邏輯回歸的類LR_,通過畫C的學習曲線來實現:
fullx = []
fsx = []
C = np.arange(0.01,10.01,0.5)
for i in C:
LR_ = LR(solver="liblinear",C=i,random_state=520)
X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
fullx.append(cross_val_score(LR_,data.data,data.target,cv=10).mean())
fsx.append(cross_val_score(LR_,X_embedded,data.target,cv=10).mean())
print(max(fsx),C[fsx.index(max(fsx))]) # 0.9561090225563911 7.51
plt.figure(figsize=(20,5))
plt.plot(C,fullx,label="fullx")
plt.plot(C,fsx,label="feature selection")
plt.xticks(C)
plt.legend()
plt.savefig("fsx_C",dpi=300)
plt.show()
我們可以看到,在C取7.51時得到最好的結果,我們可以在這個基礎上繼續細化:
fullx = []
fsx = []
C = np.arange(6.51,7.51,0.005)
for i in C:
LR_ = LR(solver="liblinear",C=i,random_state=520)
X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
fullx.append(cross_val_score(LR_,data.data,data.target,cv=10).mean())
fsx.append(cross_val_score(LR_,X_embedded,data.target,cv=10).mean())
print(max(fsx),C[fsx.index(max(fsx))]) # 0.9561090225563911 6.704999999999996
plt.figure(figsize=(20,5))
plt.plot(C,fullx,label="fullx")
plt.plot(C,fsx,label="feature selection")
plt.xticks(C)
plt.legend()
plt.savefig("fsx_C2",dpi=300)
plt.show()
#驗證模型效果:降維之前
LR_ = LR(solver="liblinear",C=6.704999999999996,random_state=520)
cross_val_score(LR_,data.data,data.target,cv=10).mean() # 0.9508145363408522
#驗證模型效果:降維之后
LR_ = LR(solver="liblinear",C=6.704999999999996,random_state=520)
X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
cross_val_score(LR_,X_embedded,data.target,cv=10).mean() # 0.9561090225563911
X_embedded.shape # (569, 9)
可以看出,在降維之后,效果更好了。
這樣我們就實現了在特征選擇的前提下,保持模型擬合的高效,現在,如果有一位醫生可以來為我們指點迷津,看看剩下的這些特征中,有哪些是對針對病情來說特別重要的,也許我們還可以繼續降維。當然,除了嵌入法,系數累加法或者包裝法也是可以使用的。
比較麻煩的系數累加法
系數累加法的原理非常簡單。在PCA中,我們通過繪制累積可解釋方差貢獻率曲線來選擇超參數,在邏輯回歸中我們可以使用系數coef_來這樣做,并且我們選擇特征個數的邏輯也是類似的:找出曲線由銳利變平滑的轉折點,轉 折點之前被累加的特征都是我們需要的,轉折點之后的我們都不需要。不過這種方法相對比較麻煩,因為我們要先對特征系數進行從大到小的排序,還要確保我們知道排序后的每個系數對應的原始特征的位置,才能夠正確找出那些重要的特征。如果要使用這樣的方法,不如直接使用嵌入法來得方便。
簡單快速的包裝法
相對的,包裝法可以直接設定我們需要的特征個數,邏輯回歸在現實中運用時,可能會有”需要5~8個變量”這種需求,包裝法此時就非常方便了。不過邏輯回歸的包裝法的使用和其他算法一樣,并不具有特別之處,因此在這里就不在贅述,具體大家可以參考03期:數據預處理和特征工程中的代碼。
2.3 梯度下降:重要參數max_iter
邏輯回歸的數學目的是求解能夠讓模型最優化,擬合程度最好的參數 的值,即求解能夠讓損失函數 最小化的( heta)值。對于二元邏輯回歸來說,有多種方法可以用來求解參數 ,最常見的有梯度下降法(Gradient Descent),坐標下降法(Coordinate Descent),牛頓法(Newton-Raphson method)等,其中又以梯度下降法最為著名。每種方法都涉及復雜的數學原理,但這些計算在執行的任務其實是類似的。
2.3.1 梯度下降求解邏輯回歸
我們以最著名也最常用的梯度下降法為例,來看看邏輯回歸的參數求解過程究竟實在做什么。現在有一個帶兩個特征并且沒有截距的邏輯回歸(y(x_1,x_2)),兩個特征所對應的參數分別為([ heta_1, heta_2])。下面這個華麗的平面就是我們的損失函數(J( heta_1, heta_2))在以( heta _1),( heta _2) 和(J)為坐標軸的三維立體坐標系上的圖像。現在,我們尋求的是損失函數的最小值,也就是圖像的最低點。
那我們怎么做呢?我在這個圖像上隨機放一個小球,當我松手,這個小球就會順著這個華麗的平面滾落,直到滾到深藍色的區域——損失函數的最低點。為了嚴格監控這個小球的行為,我讓小球每次滾動的距離有限,不讓他一次性滾到最低點,并且最多只允許它滾動100步,還要記下它每次滾動的方向,直到它滾到圖像上的最低點。
可以看見,小球從高處滑落,在深藍色的區域中來回震蕩,最終停留在了圖像凹陷處的某個點上。非常明顯,我們可以觀察到幾個現象:
首先,小球并不是一開始就直向著最低點去的,它先一口氣沖到了藍色區域邊緣,后來又折回來,我們已經規定了小球是多次滾動,所以可見,小球每次滾動的方向都是不同的。
另外,小球在進入深藍色區域后,并沒有直接找到某個點,而是在深藍色區域中來回震蕩了數次才停下。這有兩種可能:1) 小球已經滾到了圖像的最低點,所以停下了,2) 由于我設定的步數限制,小球還沒有找到最低點,但也只好在100步的時候停下了。也就是說,小球不一定滾到了圖像的最低處。
但無論如何,小球停下的就是我們在現有狀況下可以獲得的唯一點了。如果我們夠幸運,這個點就是圖像的最低點,那我們只要找到這個點的對應坐標(( heta_1^*, heta_2^*,J_{min})),就可以獲取能夠讓損失函數最小的參數取值(( heta_1^*, heta_2^*))了。如此,梯度下降的過程就已經完成。
在這個過程中,小球其實就是一組組的坐標點(( heta_1, heta_2,J));小球每次滾動的方向就是那一個坐標點的梯度向量的方向,因為每滾動一步,小球所在的位置都發生變化,坐標點和坐標點對應的梯度向量都發生了變化,所以每次滾動的方向也都不一樣;人為設置的100次滾動限制,就是sklearn中邏輯回歸的參數max_iter,代表著能走的最大步 數,即最大迭代次數。
所以梯度下降,其實就是在眾多([ heta_1, heta _2])可能的值中遍歷,一次次求解坐標點的梯度向量,不斷讓損失函數的取值逐漸逼近最小值,再返回這個最小值對應的參數取值([ heta_1^*, heta _2^*])的過程。
2.3.2 梯度下降的概念與解惑
那梯度究竟如何定義呢?在多元函數上對各個自變量求?偏導數,把求得的各個自變量的偏導數以向量的形式寫出來,就是梯度。比如損失函數(J( heta_1, heta _2)),其自變量是邏輯回歸預測函數(y_ heta(x))的參數 ( heta_1),( heta_2),在損失函數上( heta_1),( heta_2)對求偏導數,求得的梯度向量(d)就是 ([frac{partial J}{partial heta_1},frac{partial J}{partial heta_2}]^T),簡稱(grad J( heta_1, heta_2))或者(igtriangledown J( heta_1, heta_2)) 。在( heta_1),( heta_2)和(J)的取值構成的坐標系上,點(( heta_1^*, heta _2^*,J))具體的梯度向量就是([frac{partial J}{partial heta_1^*},frac{partial J}{partial heta_2^*}]^T),或者(igtriangledown J( heta_1^*, heta_2^*)) 。如果是3個參數的梯度向量,就是([frac{partial J}{partial heta_1},frac{partial J}{partial heta_2},frac{partial J}{partial heta_3}]^T),以此類推。
| 核心誤區:到底在哪個函數上,求什么的偏導數? |
|---|
| 注意,在一些博客或教材中,講解梯度向量的定義時會寫一些讓人容易誤解的句子,比如“對多元函數的參數求(partial)偏導數,把求得的各個參數的偏導數以向量的形式寫出來,就是梯度”。注意,這種解釋一眼看上去沒錯,卻是不太嚴謹的。
一個多元函數的梯度,是對其自變量求偏導的結果,不是對其參數求偏導的結果。但是在邏輯回歸的數學過程中,損失函數的自變量剛好是邏輯回歸的預測函數y(x)的參數,所以才造成了這種讓人誤解的,“對多元函數的 參數求偏導”的寫法。務必記住,正確的做法是:在多元函數(損失函數)上對自變量(邏輯回歸的預測函數y(x) 的參數)求偏導,求解梯度的方式,和邏輯回歸本身的預測函數y(x)沒有一絲聯系。 以及,有一些博客會以(f(x,y))作為例子,解釋說梯度向量是((frac{partial f}{partial x},frac{partial f}{partial y})^T)。這種舉例方式又會造成誤解:很多人看到這個式子,會特別自然地理解成:“ 是邏輯回歸中的特征呀,所以梯度向量是對模型的特征,即 求偏導數”。這個例子,其實是在表明,我們是對多元函數的自變量求偏導數,并不是代表我們在邏輯回歸中要對特征求偏導數。 在這里我會不厭其煩地給大家強調:求解梯度,是在損失函數(J( heta_1, heta_2))上對損失函數自身的自變量( heta_ 1)和( heta _2)求偏導,而這兩個自變量,剛好是邏輯回歸的預測函數(y(x)=frac{1}{1+e^{- heta^{T_x}}})的參數。 |
那梯度有什么含義呢?梯度是一個向量,因此它有大小也有方向。它的大小,就是偏導數組成的向量的大小,又叫做向量的模,記作(d) 。它的方向,幾何上來說,就是損失函數(J( heta ))的值增加最快的方向,就是小球每次滾動的方向的反方向。只要沿著梯度向量的反方向移動坐標,損失函數(J( heta ))的取值就會減少得最快,也就最容易找到損失函數的最小值。
在邏輯回歸中,我們的損失函數如下所示:
[J( heta)=-sum_{i=1}^{m}(y_i*log(y_ heta(x_i))+(1-y_i)*log(1-y_{ heta}(x_i)))
]
我們對這個函數上的自變量$ heta $求偏導,就可以得到梯度向量在第 組 的坐標點上的表示形式:
[frac{partial}{partial heta_j}J( heta)=d_j=sum_{i=1}^{m}(y_ heta(x_i)-y_i)x_{ij}
]
在這個公式下,只要給定一組( heta) 的取值( heta _j) ,再帶入特征矩陣(x),就可以求得這一組$ heta (取值下的預測結果 ,結合真實標簽向量)y(,就可以獲得這一組) heta _j (取值下的梯度向量,其大小表示為)d(。之前說過,我們的目的是在可能的) heta (取值上進行遍歷,一次次計算梯度向量,并在梯度向量的反方向上讓損失函數)J(下降至最小值。在這個過程中,我們的) heta (和梯度向量)d(的大小 都會不斷改變,而我們遍歷) heta$的過程可以描述為:
[ heta_{j+1}= heta_j-alpha *d_j\;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;= heta_j-alpha *sum_{i=1}^{m}(y_ heta(x_i)-y_i)x_{ij}
]
其中( heta_{j+1})是第j次迭代后的參數向量, ( heta_j)是第j次迭代是的參數向量,(alpha)被稱為步長,控制著每走一步(每迭代一 次)后( heta)的變化,并以此來影響每次迭代后的梯度向量的大小和方向。
2.3.3 步長的概念與解惑
| 核心誤區:步長到底是什么? |
|---|
| 許多博客和教材在描述步長的時候,聲稱它是”梯度下降中每一步沿梯度的反方向前進的長度“,”沿著最陡峭最易下山的位置走的那一步的長度“或者”梯度下降中每一步損失函數減小的量“,甚至有說,步長是二維平面著名的求導三角形中的”斜邊“或者“對邊”的。
這些說法都是錯誤的! |
來看下面這一張二維平面的求導三角型圖。類比到我們的損失函數和梯度概念上,圖中的拋物線就是我們的損失函數(J( heta)), (A( heta_a,J( heta_a)))就是小球最初在的位置, (B( heta_b,J( heta_b)))就是一次滾動后小球移動到的位置。從A到B的方向就是梯度向量的反方向,指向損失函數在A點下降最快的方向。而梯度向量的大小是點A在圖像上對 求導后的結果,也是點A切線方向的斜率,橙色角的tan結果,記作(d)。
梯度下降每走一步,損失函數減小的量,是損失函數在( heta)變化之后的取值的變化,寫作(J( heta_b)-J( heta_a)) ,這是二維平面的求導三角型中的“對邊”。
梯度下降每走一步,參數向量的變化,寫作( heta_a - heta_b),根據我們參數向量的迭代公式,就是我們的 步長 * 梯度向量的大小,記作(alpha * d),這是二維平面的求倒三角形中的“鄰邊”。
梯度下降中每走一步,下降的距離,是(sqrt{(alpha*d)^2+(J( heta_b)-J( heta_a))^2 }) ,是對邊和鄰邊的根號下平方和,是二維平面的求導三角型中的”斜邊“。
所以,步長不是任何物理距離,它甚至不是梯度下降過程中任何距離的直接變化,它是梯度向量的大小 上的一個比例,影響著參數向量 每次迭代后改變的部分。
不難發現,既然參數迭代是靠梯度向量的大小(d) * 步長(alpha)來實現的,而(J( heta))的降低又是靠調節( heta)來實現的,所以步長可以調節損失函數下降的速率。在損失函數降低的方向上,步長越長, ( heta)的變動就越大。相對的,步長如果很短,( heta)的每次變動就很小。具體地說,如果步長太大,損失函數下降得就非常快,需要的迭代次數就很少,但梯度下降過程可能跳過損失函數的最低點,無法獲取最優值。而步長太小,雖然函數會逐漸逼近我們需要的最低點,但迭代的速度卻很緩慢,迭代次數就需要很多。
記得我們在看小球運動時注意到,小球在進入深藍色區域后,并沒有直接找到某個點,而是在深藍色區域中來回震蕩了數次才停下,這種”震蕩“其實就是因為我們設置的步長太大的緣故。但是在我們開始梯度下降之前,我們并不知道什么樣的步長才合適,但梯度下降一定要在某個時候停止才可以,否則模型可能會無限地迭代下去。因此,在sklearn當中,我們設置參數max_iter最大迭代次數來代替步長,幫助我們控制模型的迭代速度并適時地讓模型停 下。max_iter越大,代表步長越小,模型迭代時間越長,反之,則代表步長設置很大,模型迭代時間很短。
迭代結束,獲取到 的最小值后,我們就可以找出這個最小值對應的參數向量 ,邏輯回歸的預測函數也就可以根據這個參數向量 來建立了。
來看看乳腺癌數據集下,max_iter的學習曲線:
l2 = []
l2_test = []
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3,random_state=520)
for i in np.arange(1,201,10):
lrl2 = LR(penalty="l2",solver="liblinear",C=0.9,max_iter=i)
lrl2 = lrl2.fit(Xtrain,Ytrain)
l2.append(accuracy_score(lrl2.predict(Xtrain),Ytrain))
l2_test.append(accuracy_score(lrl2.predict(Xtest),Ytest))
graph = [l2,l2_test]
color = ["black", "gray"]
label = ["L2","L2test"]
plt.figure(figsize=(20,5))
for i in range(len(graph)):
plt.plot(np.arange(1,201,10),graph[i],color[i],label=label[i])
plt.legend(loc=4)
plt.xticks(np.arange(1,201,10))
plt.savefig("max_iter",dpi=300)
plt.show()
#我們可以使用屬性.n_iter_來調用本次求解中真正實現的迭代次數
lr = LR(penalty="l2",solver="liblinear",C=0.9,max_iter=300).fit(Xtrain,Ytrain)
lr.n_iter_ # array([19], dtype=int32)
當max_iter中限制的步數已經走完了,邏輯回歸卻還沒有找到損失函數的最小值,參數 的值還沒有被收斂, sklearn就會彈出這樣的紅色警告:
當參數solver="liblinear":
當參數solver="sag":
雖然寫法看起來略有不同,但其實都是一個含義,這是在提醒我們:參數沒有收斂,請增大max_iter中輸入的數 字。但我們不一定要聽sklearn的。max_iter很大,意味著步長小,模型運行得會更加緩慢。雖然我們在梯度下降 中追求的是損失函數的最小值,但這也可能意味著我們的模型會過擬合(在訓練集上表現得太好,在測試集上卻不一定),因此,如果在max_iter報紅條的情況下,模型的訓練和預測效果都已經不錯了,那我們就不需要再增大 max_iter中的數目了,畢竟一切都以模型的預測效果為基準——只要最終的預測效果好,運行又快,那就一切都 好,無所謂是否報紅色警告了。
2.4 二元回歸與多元回歸:重要參數solver & multi_class
之前我們對邏輯回歸的討論,都是針對二分類的邏輯回歸展開,其實sklearn提供了多種可以使用邏輯回歸處理多 分類問題的選項。比如說,我們可以把某種分類類型都看作1,其余的分類類型都為0值,和”數據預處理“中的二值化的思維類似,這種方法被稱為"一對多"(One-vs-rest),簡稱OvR,在sklearn中表示為“ovr"。又或者,我們可以把 好幾個分類類型劃為1,剩下的幾個分類類型劃為0值,這是一種”多對多“(Many-vs-Many)的方法,簡稱MvM,在sklearn中表示為"Multinominal"。每種方式都配合L1或L2正則項來使用。在sklearn中,我們使用參數multi_class來告訴模型,我們的預測標簽是什么樣的類型。
multi_class
輸入"ovr", "multinomial", "auto"來告知模型,我們要處理的分類問題的類型。默認是"ovr"。
'ovr':表示分類問題是二分類,或讓模型使用"一對多"的形式來處理多分類問題。
'multinomial':表示處理多分類問題,這種輸入在參數solver是'liblinear'時不可用。
"auto":表示會根據數據的分類情況和其他參數來確定模型要處理的分類問題的類型。
比如說,如果數據是二分類,或者solver的取值為"liblinear","auto"會默認選擇"ovr"。反之,則會選擇"nultinomial"。
注意:默認值將在0.22版本中從"ovr"更改為"auto"。
我們之前提到的梯度下降法,只是求解邏輯回歸參數 的一種方法,并且我們只講解了求解二分類變量的參數時的各種原理。sklearn為我們提供了多種選擇,讓我們可以使用不同的求解器來計算邏輯回歸。求解器的選擇,由參 數"solver"控制,共有五種選擇。其中“liblinear”是二分類專用,也是現在的默認求解器。
來看看鳶尾花數據集上,multinomial和ovr的區別怎么樣:
from sklearn.datasets import load_iris
iris = load_iris()
for multi_class in ("multinomial","ovr"):
clf = LR(solver='sag',max_iter=100,random_state=42,
multi_class=multi_class).fit(iris.data,iris.target)
#打印兩種multi_class模式下的訓練分數
#%的用法,用%來代替打印的字符串中,想由變量替換的部分。%.3f表示,保留三位小數的浮點數。%s表示,字符串。 #字符串后的%后使用元祖來容納變量,字符串中有幾個%,元祖中就需要有幾個變量
print("training score : %.3f (%s)" % (clf.score(iris.data, iris.target),multi_class)) # training score : 0.960 (ovr)
2.5 樣本不平衡與參數class_weight
樣本不平衡是指在一組數據集中,標簽的一類天生占有很大的比例,或誤分類的代價很高,即我們想要捕捉出某種特定的分類的時候的狀況。
什么情況下誤分類的代價很高?例如,我們現在要對潛在犯罪者和普通人進行分類,如果沒有能夠識別出潛在犯罪者,那么這些人就可能去危害社會,造成犯罪,識別失敗的代價會非常高,但如果,我們將普通人錯誤地識別成了潛在犯罪者,代價卻相對較小。所以我們寧愿將普通人分類為潛在犯罪者后再人工甄別,但是卻不愿將潛在犯罪者分類為普通人,有種"寧愿錯殺不能放過"的感覺。
再比如說,在銀行要判斷“一個新客戶是否會違約”,通常不違約的人vs違約的人會是99:1的比例,真正違約的人 其實是非常少的。這種分類狀況下,即便模型什么也不做,全把所有人都當成不會違約的人,正確率也能有99%,這使得模型評估指標變得毫無意義,根本無法達到我們的“要識別出會違約的人”的建模目的。
因此我們要使用參數class_weight對樣本標簽進行一定的均衡,給少量的標簽更多的權重,讓模型更偏向少數類, 向捕獲少數類的方向建模。該參數默認None,此模式表示自動給與數據集中的所有標簽相同的權重,即自動1: 1。當誤分類的代價很高的時候,我們使用”balanced“模式,我們只是希望對標簽進行均衡的時候,什么都不填就 可以解決樣本不均衡問題。
但是,sklearn當中的參數class_weight變幻莫測,大家用模型跑一跑就會發現,我們很難去找出這個參數引導的模 型趨勢,或者畫出學習曲線來評估參數的效果,因此可以說是非常難用。我們有著處理樣本不均衡的各種方法,其中主流的是采樣法,是通過重復樣本的方式來平衡標簽,可以進行上采樣(增加少數類的樣本),比如SMOTE,或者下采樣(減少多數類的樣本)。對于邏輯回歸來說,上采樣是最好的辦法。在案例中,會給大家詳細來講如何在邏輯回歸中使用上采樣。
3 案例:用邏輯回歸制作評分卡
在銀行借貸場景中,評分卡是一種以分數形式來衡量一個客戶的信用風險大小的手段,它衡量向別人借錢的人(受信人,需要融資的公司)不能如期履行合同中的還本付息責任,并讓借錢給別人的人(授信人,銀行等金融機構)造成經濟損失的可能性。一般來說,評分卡打出的分數越高,客戶的信用越好,風險越小。
這些”借錢的人“,可能是個人,有可能是有需求的公司和企業。對于企業來說,我們按照融資主體的融資用途,分別使用企業融資模型,現金流融資模型,項目融資模型等模型。而對于個人來說,我們有”四張卡“來評判個人的信用程度:A卡,B卡,C卡和F卡。而眾人常說的“評分卡”其實是指A卡,又稱為申請者評級模型,主要應用于相關融資類業務中新用戶的主體評級,即判斷金融機構是否應該借錢給一個新用戶,如果這個人的風險太高,我們可以拒絕貸款。
一個完整的模型開發,需要有以下流程:
今天我們以個人消費類貸款數據,來為大家簡單介紹A卡的建模和制作流程,由于時間有限,我們的核心會在”數據清洗“和“模型開發”上。模型檢驗與評估也非常重要,但是在今天的課中,內容已經太多,我們就不再去贅述了。
3.1 導庫,獲取數據
%matplotlib inline
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression as LR
data = pd.read_csv('rankingcard.csv')
在銀行系統中,這個數據通常使來自于其他部門的同事的收集,因此千萬別忘記抓住給你數據的人,問問她/他各個項都是什么含義。通常來說,當特征非常多的時候(比如幾百個),都會有一個附帶的excel或pdf文檔給到你, 備注了各個特征都是什么含義。這種情況下其實要一個個去看還是非常困難,所以如果特征很多,建議先做降維,具體參考“2.2.2 邏輯回歸中的特征工程”。
3.2 探索數據與數據預處理
在這一步我們要樣本總體的大概情況,比如查看缺失值,量綱是否統一,是否需要做啞變量等等。其實數據的探索和數據的預處理并不是完全分開的,并不一定非要先做哪一個,因此這個順序只是供大家參考。
#觀察數據類型
data.head()
#觀察數據結構
data.shape
data.info()
| 特征/標簽 | 含義 |
|---|---|
| SeriousDlqin2yrs | 出現 90 天或更長時間的逾期行為(即定義好壞客戶) |
| RevolvingUtilizationOfUnsecuredLines | 貸款以及信用卡可用額度與總額度比例 |
| age | 借款人借款年齡 |
| NumberOfTime30-59DaysPastDueNotWorse | 過去兩年內出現35-59天逾期但是沒有發展得更壞的次數 |
| DebtRatio | 每月償還債務,贍養費,生活費用除以月總收入 |
| MonthlyIncome | 月收入 |
| NumberOfOpenCreditLinesAndLoans | 開放式貸款和信貸數量 |
| NumberOfTimes90DaysLate | 過去兩年內出現90天逾期或更壞的次數 |
| NumberRealEstateLoansOrLines | 抵押貸款和房地產貸款數量,包括房屋凈值信貸額度 |
| NumberOfTime60-89DaysPastDueNotWorse | 過去兩年內出現60-89天逾期但是沒有發展得更壞的次數 |
| NumberOfDependents | 家庭中不包括自身的家屬人數(配偶,子女等) |
3.2.1 去除重復值
現實數據,尤其是銀行業數據,可能會存在的一個問題就是樣本重復,即有超過一行的樣本所顯示的所有特征都一樣。有時候可能時人為輸入重復,有時候可能是系統錄入重復,總而言之我們必須對數據進行去重處理。可能有人會說,難道不可能出現說兩個樣本的特征就是一模一樣,但他們是兩個樣本嗎?比如,兩個人,一模一樣的名字,年齡,性別,學歷,工資……當特征量很少的時候,這的確是有可能的,但一些指標,比如說家屬人數,月收入,已借有的房地產貸款數量等等,幾乎不可能都出現一樣。尤其是銀行業數據經常是幾百個特征,所有特征都一樣的可能性是微乎其微的。即便真的出現了如此極端的情況,我們也可以當作是少量信息損失,將這條記錄當作重復值除去。
# 去除重復值
data.drop_duplicates(inplace=True)
data.info()
# 刪除之后千萬不要忘記,恢復索引
data.index =range(data.shape[0])
# 或者直接刪除并恢復索引
# data.reset_index(drop=True,inplace=True)
3.2.2 填補缺失值
#探索缺失值
data.info()
data.isnull().sum()/data.shape[0]
#data.isnull().mean()
第二個要面臨的問題,就是缺失值。在這里我們需要填補的特征是“收入”和“家屬人數”。“家屬人數”缺失很少,僅缺失了大約2.5%,可以考慮直接刪除,或者使用均值來填補。“收入”缺失了幾乎20%,并且我們知道,“收入”必然是一個對信用評分來說很重要的因素,因此這個特征必須要進行填補。在這里,我們使用均值填補“家屬人數”。
data["NumberOfDependents"].fillna(int(data["NumberOfDependents"].mean()),inplace=True)
那字段"收入"怎么辦呢?對于銀行數據來說,我們甚至可以有這樣的推斷:一個來借錢的人應該是會知道,“高收入”或者“穩定收入”于他/她自己而言會是申請貸款過程中的一個助力,因此如果收入穩定良好的人,肯定會傾向于寫上自己的收入情況,那么這些“收入”欄缺失的人,更可能是收入狀況不穩定或收入比較低的人。基于這種判斷,我們可以用比如說,四分位數來填補缺失值,把所有收入為空的客戶都當成是低收入人群。當然了,也有可能這些缺失是銀行數據收集過程中的失誤,我們并無法判斷為什么收入欄會有缺失,所以我們的推斷也有可能是不正確的。具體采用什么樣的手段填補缺失值,要和業務人員去溝通,觀察缺失值是如何產生的。在這里,我們使用隨機森林填補“收入”。
還記得我們用隨機森林填補缺失值的案例么?隨機森林利用“既然我可以使用A,B,C去預測Z,那我也可以使用A,C,Z去預測B”的思想來填補缺失值。對于一個有n個特征的數據來說,其中特征T有缺失值,我們就把特征T當 作標簽,其他的n-1個特征和原本的標簽組成新的特征矩陣。那對于T來說,它沒有缺失的部分,就是我們的 Y_train,這部分數據既有標簽也有特征,而它缺失的部分,只有特征沒有標簽,就是我們需要預測的部分。
特征T不缺失的值對應的其他n-1個特征 + 本來的標簽:X_train 特征T不缺失的值:Y_train 特征T缺失的值對應的其 他n-1個特征 + 本來的標簽:X_test 特征T缺失的值:未知,我們需要預測的Y_test。
這種做法,對于某一個特征大量缺失,其他特征卻很完整的情況,非常適用。更具體地,大家可以回到隨機森林地課中去復習。
之前我們所做的隨機森林填補缺失值的案例中,我們面臨整個數據集中多個特征都有缺失的情況,因此要先對特征排序,遍歷所有特征來進行填補。這次我們只需要填補“收入”一個特征,就無需循環那么麻煩了,可以直接對這一列進行填補。我們來寫一個能夠填補任何列的函數:
def fill_missing_rf(X,y,to_fill):
"""
"""
# 構建我們的新特征矩陣和新標簽
df = X.copy()
fill = df.loc[:,to_fill]
df = pd.concat([df.loc[:,df.columns != to_fill],pd.DataFramerame(y)],axis=1)
# 找出我們的訓練集和測試集
Ytrain = fill[fill.notnull()]
Ytest = fill[fill.isnull()]
Xtrain = df.iloc[Ytrain.index,:]
Xtest = df.iloc[Ytest.index,:]
# 用隨機森林回歸來填補缺失值
from sklearn.ensemble import RandomForestRegressor as rfr
rfr = rfr(n_estimators=100)
rfr = rfr.fit(Xtrain,Ytrain)
Ypredict = rfr.predict(Xtest)
return Ypredict
接下來,我們來創造函數需要的參數,將參數導入函數,產出結果:
X = data.iloc[:,1:]
y = data["SeriousDlqin2yrs"]
X.shape
y_pred = fill_missing_rf(X,y,"MonthlyIncome")
#確認我們的結果合理之后,我們就可以將數據覆蓋了
data.loc[data.loc[:,"MonthlyIncome"].isnull(),"MonthlyIncome"] = y_pred
3.2.3 描述性統計處理異常值
現實數據永遠都會有一些異常值,首先我們要去把他們捕捉出來,然后觀察他們的性質。注意,我們并不是要排除掉所有異常值,相反很多時候,異常值是我們的重點研究對象,比如說,雙十一中購買量超高的品牌,或課堂上讓很多學生都興奮的課題,這些是我們要重點研究觀察的。
日常處理異常值,我們使用箱線圖或者 法則來找到異常值(千萬不要說依賴于眼睛看,我們是數據挖掘工程師,除了業務理解,我們還要有方法)。但在銀行數據中,我們希望排除的“異常值”不是一些超高或超低的數字,而是一些不符合常理的數據:比如,收入不能為負數,但是一個超高水平的收入卻是合理的,可以存在的。所以在銀行業中,我們往往就使用普通的描述性統計來觀察數據的異常與否與數據的分布情況。注意,這種方法只能在特征量有限的情況下進行,如果有幾百個特征又無法成功降維或特征選擇不管用,那還是用(3sigma)比較好。
# 描述性統計
data.describe([0.01,0.1,0.25,0.5,0.75,0.9,0.99]).T
# 異常值也被我們觀察到,年齡的最小值居然有0,這不符合銀行的業務需求,即便是兒童賬戶也要至少8歲,我們可以查看一下年齡為0的人有多少
(data["age"] == 0).sum()
#發現只有一個人年齡為0,可以判斷這肯定是錄入失誤造成的,可以當成是缺失值來處理,直接刪除掉這個樣本
data = data[data["age"] != 0]
"""
另外,有三個指標看起來很奇怪:
"NumberOfTime30-59DaysPastDueNotWorse"
"NumberOfTime60-89DaysPastDueNotWorse"
"NumberOfTimes90DaysLate"
這三個指標分別是“過去兩年內出現35-59天逾期但是沒有發展的更壞的次數”,“過去兩年內出現60-89天逾期但是沒
有發展的更壞的次數”,“過去兩年內出現90天逾期的次數”。這三個指標,在99%的分布的時候依然是2,最大值卻是
98,看起來非常奇怪。一個人在過去兩年內逾期35~59天98次,一年6個60天,兩年內逾期98次這是怎么算出來的?
我們可以去咨詢業務人員,請教他們這個逾期次數是如何計算的。如果這個指標是正常的,那這些兩年內逾期了98次的
客戶,應該都是壞客戶。在我們無法詢問他們情況下,我們查看一下有多少個樣本存在這種異常:
"""
data[data.loc[:,"NumberOfTimes90DaysLate"]>90].count()
#有269個樣本存在這樣的情況,并且這些樣本,我們觀察一下,標簽并不都是1,他們并不都是壞客戶。因此,我們基本可以判斷,這些樣本是某種異常,應該把它們刪除。
data = data[data.loc[:,"NumberOfTimes90DaysLate"]<90]
#恢復索引
data.index = range(data.shape[0])
data.info()
3.2.4 為什么不統一量綱,也不標準化數據分布?
在描述性統計結果中,我們可以觀察到數據量綱明顯不統一,而且存在一部分極偏的分布,雖然邏輯回歸對于數據沒有分布要求,但是我們知道如果數據服從正態分布的話梯度下降可以收斂得更快。但在這里,我們不對數據進行標準化處理,也不進行量綱統一,為什么?
無論算法有什么樣的規定,無論統計學中有什么樣的要求,我們的最終目的都是要為業務服務。現在我們要制作評分卡,評分卡是要給業務人員們使用的基于新客戶填寫的各種信息為客戶打分的一張卡片,而為了制作這張卡片,我們需要對我們的數據進行一個“分檔”,比如說,年齡2030歲為一檔,年齡3050歲為一檔,月收入1W以上為一檔,5000~1W為一檔,每檔的分數不同。
一旦我們將數據統一量綱,或者標準化了之后,數據大小和范圍都會改變,統計結果是漂亮了,但是對于業務人員來說,他們完全無法理解,標準化后的年齡在0.003280.00467之間為一檔是什么含義。并且,新客戶填寫的信息,天生就是量綱不統一的,我們的確可以將所有的信息錄入之后,統一進行標準化,然后導入算法計算,但是最終落到業務人員手上去判斷的時候,他們會完全不理解為什么錄入的信息變成了一串統計上很美但實際上根本看不懂的數字。由于業務要求,在制作評分卡的時候,我們要盡量保持數據的原貌,年齡就是8110的數字,收入就是大于0,最大值可以無限的數字,即便量綱不統一,我們也不對數據進行標準化處理。
3.2.5 樣本不均衡問題
# 探索標簽的分布
X = data.iloc[:,2:]
y = data.iloc[:,1]
y.value_counts()
n_sample = X.shape[0]
n_1_sample = y.value_counts()[1]
n_0_sample = y.value_counts()[0]
print('樣本個數:{};1占{:.2%};0占{:.2%}'.format(n_sample,n_1_sample/n_sample,n_0_sample/n_sample)) # 樣本個數:149730;1占6.60%;0占93.40%
可以看出,樣本嚴重不均衡。雖然大家都在努力防范信用風險,但實際違約的人并不多。并且,銀行并不會真的一棒子打死所有會違約的人,很多人是會還錢的,只是忘記了還款日,很多人是不愿意欠人錢的,但是當時真的很困難,資金周轉不過來,所以發生逾期,但一旦他有了錢,他就會把錢換上。對于銀行來說,只要你最后能夠把錢還上,我都愿意借錢給你,因為我借給你就有收入(利息)。所以,對于銀行來說,真正想要被判別出來的其實是”惡意違約“的人,而這部分人數非常非常少,樣本就會不均衡。這一直是銀行業建模的一個痛點:我們永遠希望捕捉少數類。
之前提到過,邏輯回歸中使用最多的是上采樣方法來平衡樣本。
#imblearn是專門用來處理不平衡數據集的庫,在處理樣本不均衡問題中性能高過sklearn很多
#imblearn里面也是一個個的類,也需要進行實例化,fit擬合,和sklearn用法相似
import imblearn
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=17)
X,y = sm.fit_sample(X,y)
n_sample_ = X.shape[0]
n_1_sample_ = y.value_counts()[1]
n_0_sample_ = y.value_counts()[0]
print('樣本個數:{};1占{:.2%};0占{:.2%}'.format(n_sample_,n_1_sample_/n_sample_,n_0_sample_/n_sample_)) # 樣本個數:279702;1占50.00%;0占50.00%
如此,我們就實現了樣本平衡,樣本量也增加了。
from sklearn.model_selection import train_test_split
X = pd.DataFrame(X)
y = pd.DataFrame(y)
X_train,X_vali,Y_train,Y_vali = train_test_split(X,y,test_size=0.3,random_state=0)
model_data = pd.concat([Y_train,X_train],axis=1)
model_data.index = range(model_data.shape[0])
model_data.cloumns = data.columns
vali_data = pd.concat([Y_vali,X_vali],axis=1)
vali_data.index = range(vali_data.shape[0])
vali_data.columns = data.columns
model_data.to_csv("model_data.csv")
vali_data.to_csv("vali_data.csv")
3.3 分箱
前面提到過,我們要制作評分卡,是要給各個特征進行分檔,以便業務人員能夠根據新客戶填寫的信息為客戶打分。因此在評分卡制作過程中,一個重要的步驟就是分箱。可以說,分箱是評分卡最難,也是最核心的思路,分箱的本質,其實就是離散化連續變量,好讓擁有不同屬性的人被分成不同的類別(打上不同的分數),其實本質比較類似于聚類。那我們在分箱中要回答幾個問題:
首先,要分多少個箱子才合適?
最開始我們并不知道,但是既然是將連續型變量離散化,想也知道箱子個數必然不能太多,最好控制在十個以下。而用來制作評分卡,最好能在4~5個為最佳。我們知道,離散化連續變量必然伴隨著信息的損失,并且箱子越少,信息損失越大。為了衡量特征上的信息量以及特征對預測函數的貢獻,銀行業定義了概念Information value(IV):
[IV=sum_{i=1}^{N}(good\%-bad\%)*WOE_i
]
其中N是這個特征上箱子的個數,i代表每個箱子,(good\%)是這個箱內的優質客戶(標簽為0的客戶)占整個特征中 所有優質客戶的比例,(bad\%)是這個箱子里的壞客戶(就是那些會違約,標簽為1的那些客戶)占整個特征中所有壞客戶的比例,而 則寫作:
[WOE_i=ln(frac{good\%}{bad\%})
]
這是我們在銀行業中用來衡量違約概率的指標,中文叫做證據權重(weight of Evidence),本質其實就是優質客戶 比上壞客戶的比例的對數。WOE是對一個箱子來說的,WOE越大,代表了這個箱子里的優質客戶越多。而IV是對整個特征來說的,IV代表的意義是我們特征上的信息量以及這個特征對模型的貢獻,由下表來控制:
| IV | 特征對預測函數的貢獻 |
|---|---|
| < 0.03 | 特征幾乎不帶有效信息,對模型沒有貢獻,這種特征可以被刪除 |
| 0.03 ~ 0.09 | 有效信息很少,對模型的貢獻度低 |
| 0.1 ~ 0.29 | 有效信息一般,對模型的貢獻度中等 |
| 0.3 ~ 0.49 | 有效信息較多,對模型的貢獻度較高 |
| >=0.5 | 有效信息非常多,對模型的貢獻超高并且可疑 |
可見,IV并非越大越好,我們想要找到IV的大小和箱子個數的平衡點。箱子越多,IV必然越小,因為信息損失會非常多,所以,我們會對特征進行分箱,然后計算每 個特征在每個箱子數目下的WOE值,利用IV值的曲線,找出合適的分箱個數。
其次,分箱要達成什么樣的效果?
我們希望不同屬性的人有不同的分數,因此我們希望在同一個箱子內的人的屬性是盡量相似的,而不同箱子的人的屬性是盡量不同的,即業界常說的”組間差異大,組內差異小“。對于評分卡來說,就是說我們希望一個箱子內的人違約概率是類似的,而不同箱子的人的違約概率差距很大,即WOE差距要大,并且每個箱子中壞客戶所占的比重((bad\%))也要不同。那我們,可以使用卡方檢驗來對比兩個箱子之間的相似性,如果兩個箱子之間卡方檢驗的P值很大,則說明他們非常相似,那我們就可以將這兩個箱子合并為一個箱子。
基于這樣的思想,我們總結出我們對一個特征進行分箱的步驟:
1)我們首先把連續型變量分成一組數量較多的分類型變量,比如,將幾萬個樣本分成100組,或50組
2)確保每一組中都要包含兩種類別的樣本,否則IV值會無法計算
3)我們對相鄰的組進行卡方檢驗,卡方檢驗的P值很大的組進行合并,直到數據中的組數小于設定的N箱為止
4)我們讓一個特征分別分成[2,3,4.....20]箱,觀察每個分箱個數下的IV值如何變,找出最適合的分箱個數
5)分箱完畢后,我們計算每個箱的WOE值,(bad\%) ,觀察分箱效果
這些步驟都完成后,我們可以對各個特征都進行分箱,然后觀察每個特征的IV值,以此來挑選特征。
接下來,我們就以"age"為例子,來看看分箱如何完成。注意,分箱代碼的版權屬于Hsiaofei Tsien,我已獲得授 權在這門課中使用和講解他的代碼。
3.3.1 等頻分箱
總結
以上是生活随笔為你收集整理的sklearn中的逻辑回归的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: b612相机(B612咔叽)
- 下一篇: 怎么创建具有真实纹理的CG场景岩石?