本文基於yhat上Logistic Regression in Python,作了中文翻譯,並相應補充了一些內容。本文並不研究邏輯回歸具體算法實現,而是使用了一些算法庫,旨在幫助需要用Python來做邏輯回歸的訓練和預測的讀者快速上手。
邏輯回歸是一項可用於預測二分類結果(binary outcome)的統計技術,廣泛應用於金融、醫學、犯罪學和其他社會科學中。邏輯回歸使用簡單且非常有效,你可以在許多機器學習、應用統計的書中的前幾章中找到個關於邏輯回歸的介紹。邏輯回歸在許多統計課程中都會用到。
我們不難找到使用R語言的高質量的邏輯回歸實例,如UCLA的教程R Data Analysis Examples: Logit Regression就是一個很好的資源。Python是機器學習領域最流行的語言之一,並且已有許多Python的資源涵蓋了支持向量積和文本分類等話題,但少有關於邏輯回歸的資料。
本文介紹了如何使用Python來完成邏輯回歸。
簡介
示例代碼中使用了一些算法包,請確保在運行這些代碼前,你的電腦已經安裝了如下包:
- numpy: Python的語言擴展,定義了數字的數組和矩陣
- pandas: 直接處理和操作數據的主要package
- statsmodels: 統計和計量經濟學的package,包含了用於參數評估和統計測試的實用工具
- pylab: 用於生成統計圖
可參考 Windows安裝Python機器學習包 或 Ubuntu/CentOS安裝Python機器學習包 來搭建所需要的環境。
邏輯回歸的實例
在此使用與Logit Regression in R相同的數據集來研究Python中的邏輯回歸,目的是要辨別不同的因素對研究生錄取的影響。
數據集中的前三列可作為預測變量(predictor variables):
gpa
gre
分數rank
表示本科生母校的聲望
第四列admit
則是二分類目標變量(binary target variable),它表明考生最終是否被錄用。
加載數據
使用 pandas.read_csv
加載數據,這樣我們就有了可用於探索數據的DataFrame
。
- import pandas as pd
- import statsmodels.api as sm
- import pylab as pl
- import numpy as np
- # 加載數據
- # 備用地址: http://cdn.powerxing.com/files/lr-binary.csv
- df = pd.read_csv("http://www.ats.ucla.edu/stat/data/binary.csv")
- # 瀏覽數據集
- print df.head()
- # admit gre gpa rank
- # 0 0 380 3.61 3
- # 1 1 660 3.67 3
- # 2 1 800 4.00 1
- # 3 1 640 3.19 4
- # 4 0 520 2.93 4
- # 重命名'rank'列,因為dataframe中有個方法名也為'rank'
- df.columns = ["admit", "gre", "gpa", "prestige"]
- print df.columns
- # array([admit, gre, gpa, prestige], dtype=object)
注意到有一列屬性名為rank
,但因為rank
也是pandas dataframe中一個方法的名字,因此需要將該列重命名為”prestige”.
統計摘要(Summary Statistics) 以及 查看數據
現在我們就將需要的數據正確載入到Python中了,現在來看下數據。我們可以使用pandas
的函數describe
來給出數據的摘要–describe
與R語言中的summay
類似。這里也有一個用於計算標准差的函數std
,但在describe
中已包括了計算標准差。
我特別喜歡pandas
的pivot_table/crosstab
聚合功能。crosstab
可方便的實現多維頻率表(frequency tables)(有點像R語言中的table
)。你可以用它來查看不同數據所占的比例。
- # summarize the data
- print df.describe()
- # admit gre gpa prestige
- # count 400.000000 400.000000 400.000000 400.00000
- # mean 0.317500 587.700000 3.389900 2.48500
- # std 0.466087 115.516536 0.380567 0.94446
- # min 0.000000 220.000000 2.260000 1.00000
- # 25% 0.000000 520.000000 3.130000 2.00000
- # 50% 0.000000 580.000000 3.395000 2.00000
- # 75% 1.000000 660.000000 3.670000 3.00000
- # max 1.000000 800.000000 4.000000 4.00000
- # 查看每一列的標准差
- print df.std()
- # admit 0.466087
- # gre 115.516536
- # gpa 0.380567
- # prestige 0.944460
- # 頻率表,表示prestige與admin的值相應的數量關系
- print pd.crosstab(df['admit'], df['prestige'], rownames=['admit'])
- # prestige 1 2 3 4
- # admit
- # 0 28 97 93 55
- # 1 33 54 28 12
- # plot all of the columns
- df.hist()
- pl.show()
運行代碼后,繪制的柱狀統計圖如下所示:
使用pylab繪制的柱狀統計圖
虛擬變量(dummy variables)
虛擬變量,也叫啞變量,可用來表示分類變量、非數量因素可能產生的影響。在計量經濟學模型,需要經常考慮屬性因素的影響。例如,職業、文化程度、季節等屬性因素往往很難直接度量它們的大小。只能給出它們的“Yes—D=1”或”No—D=0”,或者它們的程度或等級。為了反映屬性因素和提高模型的精度,必須將屬性因素“量化”。通過構造0-1型的人工變量來量化屬性因素。
pandas
提供了一系列分類變量的控制。我們可以用get_dummies
來將”prestige”一列虛擬化。
get_dummies
為每個指定的列創建了新的帶二分類預測變量的DataFrame,在本例中,prestige
有四個級別:1,2,3以及4(1代表最有聲望),prestige
作為分類變量更加合適。當調用get_dummies
時,會產生四列的dataframe,每一列表示四個級別中的一個。
- # 將prestige設為虛擬變量
- dummy_ranks = pd.get_dummies(df['prestige'], prefix='prestige')
- print dummy_ranks.head()
- # prestige_1 prestige_2 prestige_3 prestige_4
- # 0 0 0 1 0
- # 1 0 0 1 0
- # 2 1 0 0 0
- # 3 0 0 0 1
- # 4 0 0 0 1
- # 為邏輯回歸創建所需的data frame
- # 除admit、gre、gpa外,加入了上面常見的虛擬變量(注意,引入的虛擬變量列數應為虛擬變量總列數減1,減去的1列作為基准)
- cols_to_keep = ['admit', 'gre', 'gpa']
- data = df[cols_to_keep].join(dummy_ranks.ix[:, 'prestige_2':])
- print data.head()
- # admit gre gpa prestige_2 prestige_3 prestige_4
- # 0 0 380 3.61 0 1 0
- # 1 1 660 3.67 0 1 0
- # 2 1 800 4.00 0 0 0
- # 3 1 640 3.19 0 0 1
- # 4 0 520 2.93 0 0 1
- # 需要自行添加邏輯回歸所需的intercept變量
- data['intercept'] = 1.0
這樣,數據原本的prestige
屬性就被prestige_x
代替了,例如原本的數值為2,則prestige_2
為1,prestige_1
、prestige_3
、prestige_4
都為0。
將新的虛擬變量加入到了原始的數據集中后,就不再需要原來的prestige
列了。在此要強調一點,生成m個虛擬變量后,只要引入m-1個虛擬變量到數據集中,未引入的一個是作為基准對比的。
最后,還需加上常數intercept,statemodels
實現的邏輯回歸需要顯式指定。
執行邏輯回歸
實際上完成邏輯回歸是相當簡單的,首先指定要預測變量的列,接着指定模型用於做預測的列,剩下的就由算法包去完成了。
本例中要預測的是admin
列,使用到gre
、gpa
和虛擬變量prestige_2
、prestige_3
、prestige_4
。prestige_1
作為基准,所以排除掉,以防止多元共線性(multicollinearity)和引入分類變量的所有虛擬變量值所導致的陷阱(dummy variable trap)。
- # 指定作為訓練變量的列,不含目標列`admit`
- train_cols = data.columns[1:]
- # Index([gre, gpa, prestige_2, prestige_3, prestige_4], dtype=object)
- logit = sm.Logit(data['admit'], data[train_cols])
- # 擬合模型
- result = logit.fit()
在這里是使用了statesmodels
的Logit函數,更多的模型細節可以查閱statesmodels
的文檔
使用訓練模型預測數據
(本小節是博主補充的)通過上述步驟,我們就得到了訓練后的模型。基於這個模型,我們就可以用來預測數據,代碼如下:
- # 構建預測集
- # 與訓練集相似,一般也是通過 pd.read_csv() 讀入
- # 在這邊為方便,我們將訓練集拷貝一份作為預測集(不包括 admin 列)
- import copy
- combos = copy.deepcopy(data)
- # 數據中的列要跟預測時用到的列一致
- predict_cols = combos.columns[1:]
- # 預測集也要添加intercept變量
- combos['intercept'] = 1.0
- # 進行預測,並將預測評分存入 predict 列中
- combos['predict'] = result.predict(combos[predict_cols])
- # 預測完成后,predict 的值是介於 [0, 1] 間的概率值
- # 我們可以根據需要,提取預測結果
- # 例如,假定 predict > 0.5,則表示會被錄取
- # 在這邊我們檢驗一下上述選取結果的精確度
- total = 0
- hit = 0
- for value in combos.values:
- # 預測分數 predict, 是數據中的最后一列
- predict = value[-1]
- # 實際錄取結果
- admit = int(value[0])
- # 假定預測概率大於0.5則表示預測被錄取
- if predict > 0.5:
- total += 1
- # 表示預測命中
- if admit == 1:
- hit += 1
- # 輸出結果
- print 'Total: %d, Hit: %d, Precision: %.2f' % (total, hit, 100.0*hit/total)
- # Total: 49, Hit: 30, Precision: 61.22
在這里,我是簡單的將原始數據再作為待預測的數據進行檢驗。通過上述步驟得到的是一個概率值,而不是一個直接的二分類結果(被錄取/不被錄取)。通常,我們可以設定一個閾值,若 predict 大於該閾值,則認為是被錄取了,反之,則表示不被錄取。
在上面的例子中,假定預測概率大於 0.5 則表示預測被錄取,一共預測有 49 個被錄取,其中有 30 個預測命中,精確度為 61.22%。
結果解釋
statesmodels
提供了結果的摘要,如果你使用過R語言,你會發現結果的輸出與之相似。
- # 查看數據的要點
- print result.summary()
- Logit Regression Results
- ==============================================================================
- Dep. Variable: admit No. Observations: 400
- Model: Logit Df Residuals: 394
- Method: MLE Df Model: 5
- Date: Sun, 03 Mar 2013 Pseudo R-squ.: 0.08292
- Time: 12:34:59 Log-Likelihood: -229.26
- converged: True LL-Null: -249.99
- LLR p-value: 7.578e-08
- ==============================================================================
- coef std err z P>|z| [95.0% Conf. Int.]
- ------------------------------------------------------------------------------
- gre 0.0023 0.001 2.070 0.038 0.000 0.004
- gpa 0.8040 0.332 2.423 0.015 0.154 1.454
- prestige_2 -0.6754 0.316 -2.134 0.033 -1.296 -0.055
- prestige_3 -1.3402 0.345 -3.881 0.000 -2.017 -0.663
- prestige_4 -1.5515 0.418 -3.713 0.000 -2.370 -0.733
- intercept -3.9900 1.140 -3.500 0.000 -6.224 -1.756
- ==============================================================================
你可以看到模型的系數,系數擬合的效果,以及總的擬合質量,以及一些統計度量。[待補充: 模型結果主要參數的含義]
當然你也可以只觀察結果的某部分,如置信區間(confidence interval)可以看出模型系數的健壯性。
- # 查看每個系數的置信區間
- print result.conf_int()
- # 0 1
- # gre 0.000120 0.004409
- # gpa 0.153684 1.454391
- # prestige_2 -1.295751 -0.055135
- # prestige_3 -2.016992 -0.663416
- # prestige_4 -2.370399 -0.732529
- # intercept -6.224242 -1.755716
在這個例子中,我們可以肯定被錄取的可能性與應試者畢業學校的聲望存在着逆相關的關系。
換句話說,高排名學校(prestige_1==True)的湘鄂生唄錄取的概率比低排名學校(prestige_4==True)要高。
相對危險度(odds ratio)
使用每個變量系數的指數來生成odds ratio,可知變量每單位的增加、減少對錄取幾率的影響。例如,如果學校的聲望為2,則我們可以期待被錄取的幾率減少大概50%。UCLA上有一個對odds ratio更為深入的解釋: 在邏輯回歸中如何解釋odds ratios?。
- # 輸出 odds ratio
- print np.exp(result.params)
- # gre 1.002267
- # gpa 2.234545
- # prestige_2 0.508931
- # prestige_3 0.261792
- # prestige_4 0.211938
- # intercept 0.018500
我們也可以使用置信區間來計算系數的影響,來更好地估計一個變量影響錄取率的不確定性。
- # odds ratios and 95% CI
- params = result.params
- conf = result.conf_int()
- conf['OR'] = params
- conf.columns = ['2.5%', '97.5%', 'OR']
- print np.exp(conf)
- # 2.5% 97.5% OR
- # gre 1.000120 1.004418 1.002267
- # gpa 1.166122 4.281877 2.234545
- # prestige_2 0.273692 0.946358 0.508931
- # prestige_3 0.133055 0.515089 0.261792
- # prestige_4 0.093443 0.480692 0.211938
- # intercept 0.001981 0.172783 0.018500
更深入的挖掘
為了評估我們分類器的效果,我們將使用每個輸入值的邏輯組合(logical combination)來重新創建數據集,如此可以得知在不同的變量下預測錄取可能性的增加、減少。首先我們使用名為 cartesian
的輔助函數來生成組合值(來源於: 如何使用numpy構建兩個數組的組合)
我們使用 np.linspace
創建 “gre” 和 “gpa” 值的一個范圍,即從指定的最大、最小值來創建一個線性間隔的值的范圍。在本例子中,取已知的最大、最小值。
- # 根據最大、最小值生成 GRE、GPA 均勻分布的10個值,而不是生成所有可能的值
- gres = np.linspace(data['gre'].min(), data['gre'].max(), 10)
- print gres
- # array([ 220. , 284.44444444, 348.88888889, 413.33333333,
- # 477.77777778, 542.22222222, 606.66666667, 671.11111111,
- # 735.55555556, 800. ])
- gpas = np.linspace(data['gpa'].min(), data['gpa'].max(), 10)
- print gpas
- # array([ 2.26 , 2.45333333, 2.64666667, 2.84 , 3.03333333,
- # 3.22666667, 3.42 , 3.61333333, 3.80666667, 4. ])
- # 枚舉所有的可能性
- combos = pd.DataFrame(cartesian([gres, gpas, [1, 2, 3, 4], [1.]]))
- # 重新創建啞變量
- combos.columns = ['gre', 'gpa', 'prestige', 'intercept']
- dummy_ranks = pd.get_dummies(combos['prestige'], prefix='prestige')
- dummy_ranks.columns = ['prestige_1', 'prestige_2', 'prestige_3', 'prestige_4']
- # 只保留用於預測的列
- cols_to_keep = ['gre', 'gpa', 'prestige', 'intercept']
- combos = combos[cols_to_keep].join(dummy_ranks.ix[:, 'prestige_2':])
- # 使用枚舉的數據集來做預測
- combos['admit_pred'] = result.predict(combos[train_cols])
- print combos.head()
- # gre gpa prestige intercept prestige_2 prestige_3 prestige_4 admit_pred
- # 0 220 2.260000 1 1 0 0 0 0.157801
- # 1 220 2.260000 2 1 1 0 0 0.087056
- # 2 220 2.260000 3 1 0 1 0 0.046758
- # 3 220 2.260000 4 1 0 0 1 0.038194
- # 4 220 2.453333 1 1 0 0 0 0.179574
現在我們已生成了預測結果,接着通過畫圖來呈現結果。我編寫了一個名為 isolate_and_plot
的輔助函數,可以比較給定的變量與不同的聲望等級、組合的平均可能性。為了分離聲望和其他變量,我使用了 pivot_table
來簡單地聚合數據。
- def isolate_and_plot(variable):
- # isolate gre and class rank
- grouped = pd.pivot_table(combos, values=['admit_pred'], index=[variable, 'prestige'],
- aggfunc=np.mean)
- # in case you're curious as to what this looks like
- # print grouped.head()
- # admit_pred
- # gre prestige
- # 220.000000 1 0.282462
- # 2 0.169987
- # 3 0.096544
- # 4 0.079859
- # 284.444444 1 0.311718
- # make a plot
- colors = 'rbgyrbgy'
- for col in combos.prestige.unique():
- plt_data = grouped.ix[grouped.index.get_level_values(1)==col]
- pl.plot(plt_data.index.get_level_values(0), plt_data['admit_pred'],
- color=colors[int(col)])
- pl.xlabel(variable)
- pl.ylabel("P(admit=1)")
- pl.legend(['1', '2', '3', '4'], loc='upper left', title='Prestige')
- pl.title("Prob(admit=1) isolating " + variable + " and presitge")
- pl.show()
- isolate_and_plot('gre')
- isolate_and_plot('gpa')
結果圖顯示了 gre, gpa 和 prestige 如何影響錄取。可以看出,隨着 gre 和 gpa 的增加,錄取可能性如何逐漸增加,並且,不同的學校聲望對錄取可能性的增加程度相差很大。
結束語
邏輯回歸是用於分類的優秀算法,盡管有一些更加性感的,或是黑盒分類器算法,如SVM和隨機森林(RandomForest)在一些情況下性能更好,但深入了解你正在使用的模型是很有價值的。很多時候你可以使用隨機森林來篩選模型的特征,並基於篩選出的最佳的特征,使用邏輯回歸來重建模型。