1. 程式人生 > >Python實現邏輯迴歸(Logistic Regression in Python)

Python實現邏輯迴歸(Logistic Regression in Python)

本文基於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: 用於生成統計圖

邏輯迴歸的例項

在此使用與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中已包括了計算標準差。

我特別喜歡pandaspivot_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繪製的柱狀統計圖使用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_1prestige_3prestige_4都為0。

將新的虛擬變數加入到了原始的資料集中後,就不再需要原來的prestige列了。在此要強調一點,生成m個虛擬變數後,只要引入m-1個虛擬變數到資料集中,未引入的一個是作為基準對比的。

最後,還需加上常數intercept,statemodels實現的邏輯迴歸需要顯式指定。

執行邏輯迴歸

實際上完成邏輯迴歸是相當簡單的,首先指定要預測變數的列,接著指定模型用於做預測的列,剩下的就由演算法包去完成了。

本例中要預測的是admin列,使用到gregpa和虛擬變數prestige_2prestige_3prestige_4prestige_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()

在這裡是使用了statesmodelsLogit函式,更多的模型細節可以查閱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
==============================================================================
# 檢視每個係數的置信區間
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
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 的增加,錄取可能性如何逐漸增加,並且,不同的學校聲望對錄取可能性的增加程度相差很大。

gre與prestige的關係
gpa與prestige的關係

結束語

邏輯迴歸是用於分類的優秀演算法,儘管有一些更加性感的,或是黑盒分類器演算法,如SVM和隨機森林(RandomForest)在一些情況下效能更好,但深入瞭解你正在使用的模型是很有價值的。很多時候你可以使用隨機森林來篩選模型的特徵,並基於篩選出的最佳的特徵,使用邏輯迴歸來重建模型。

其他資源