Lending Club——構建貸款違約預測模型

1 項目概述

阿蘭?麥席森?圖靈(Alan Mathison Turing,1912.6.23—1954.6.7),英國數學家、邏輯學家,他被視為計算機之父。 1931年圖靈進入劍橋大學國王學院,畢業後到美國普林斯頓大學攻讀博士學位,二戰爆發後回到劍橋,後曾協助軍方破解德國的著名密碼系統Enigma,幫助盟軍取得了二戰的勝利。

1936年,圖靈向倫敦權威的數學雜誌投了一篇論文,題為「論數字計算在決斷難題中的應用」。在這篇開創性的論文中,圖靈給「可計算性」下了一個嚴格的數學定義,並提出著名的「圖靈機」(Turing Machine)的設想。「圖靈機」不是一種具體的機器,而是一種思想模型,可製造一種十分簡單但運算能力極強的計算裝置,用來計算所有能想像得到的可計算函數。「圖靈機」與「馮?諾伊曼機」齊名,被永遠載入計算機的發展史中。1950年10月,圖靈又發表了另一篇題為「機器能思考嗎」的論文,成為劃時代之作。也正是這篇文章,為圖靈贏得了「人工智慧之父」的桂冠。

  • 本項目需解決的問題

本項目通過利用P2P平台Lending Club的貸款數據,進行機器學習,構建貸款違約預測模型,對新增貸款申請人進行預測是否會違約,從而決定是否放款。

  • 建模思路

以下是本次項目機器學習工作流程,實際操作中,其實每個步驟都是反覆迭代的過程。

2 項目背景

作為舊金山的一家個人對個人的借貸公司,Lending Club成立於2006年。他們是第一家註冊為按照美國證券交易委員會SEC(Securities and Exchange Commission)的安全標準向個人提供個人貸款的借貸公司。與傳統借貸機構最大的不同是,Lending Club利用網路技術打造的這個交易平台,直接連接了個人投資者和個人借貸者,通過此種方式,縮短了資金流通的環節,尤其是繞過了傳統的大銀行等金融機構,使得投資者和借貸者都能得到更多實惠、更快捷。對於投資者來說可以獲得更好的回報,而對於借貸者來說,則可以獲得相對較低的貸款利率。

關於Lending Club 更多介紹,可以參考虎嗅網的文章《來認識一下即將上市的全球最大P2P網貸公司Lending Club》。若想了解Lending Club 2017年Q2業務情況也可以參考我上一個項目的報告註冊會計師帶你探索風險分析(EDA)

3 場景解析(演算法選擇)

貸款申請人向Lending Club平台申請貸款時,Lending Club平台通過線上或線下讓客戶填寫貸款申請表,收集客戶的基本信息,這裡包括申請人的年齡、性別、婚姻狀況、學歷、貸款金額、申請人財產情況等信息,通常來說還會藉助第三方平台如徵信機構或FICO等機構的信息。通過這些信息屬性來做線性回歸 ,生成預測模型,Lending Club平台可以通過預測判斷貸款申請是否會違約,從而決定是否向申請人發放貸款。

通過以上的業務邏輯和上一個項目報告註冊會計師帶你探索風險分析(EDA)的數據探索,下面進行場景解析。

1)首先,我們的場景是通過用戶的歷史行為(如歷史數據的多維特徵和貸款狀態是否違約)來訓練模型,通過這個模型對新增的貸款人「是否具有償還能力,是否具有償債意願」進行分析,預測貸款申請人是否會發生違約貸款。這是一個監督學習的場景,因為已知了特徵以及貸款狀態是否違約(目標列),我們判定貸款申請人是否違約是一個二元分類問題,可以通過一個分類演算法來處理,這裡選用邏輯斯蒂回歸(Logistic Regression)。

2)通過上一個項目報告註冊會計師帶你探索風險分析(EDA)可以看到,部分數據是半結構化數據,需要進行特徵抽象。

現對該業務場景進行總結如下:

  • 根據歷史記錄數據學習並對貸款是否違約進行預測,監督學習場景,選擇邏輯斯蒂回歸(Logistic Regression)演算法
  • 數據為半結構化數據,需要進行特徵抽象

本項目報告,我將如何運用Python操作數據和機器學習的思考過程均記錄下來。

4 數據預處理(Pre-Processing Data)

  • 前期準備

# Importsnn# Numpy,Pandasnimport numpy as npnimport pandas as pdnn# matplotlib,seaborn,pyechartsnnimport matplotlib.pyplot as pltnplt.style.use(ggplot) #風格設置近似R這種的ggplot庫nimport seaborn as snsnsns.set_style(whitegrid)n%matplotlib inlinenimport missingno as msnonn# 忽略彈出的warningsnimport warningsnwarnings.filterwarnings(ignore) nnpd.set_option(display.float_format, lambda x: %.5f % x)n

  • 數據獲取與解析

data = pd.read_csv(LoanStats_2017Q2.csv , encoding=latin-1,skiprows = 1) #讀取數據ndata.head() #查看錶格默認前5行n

統計每列屬性缺失值的數量。

check_null = data.isnull().sum(axis=0).sort_values(ascending=False)/float(len(data)) #查看缺失值比例nprint(check_null[check_null > 0.2]) # 查看缺失比例大於20%的屬性。n

數據是否有缺失值或亂碼一般是判斷數據質量的主要因素。

對於缺失值的處理,一般來說先判定缺失的數據是否有意義。從上面信息可以發現,本次數據集缺失值較多的屬性對我們模型預測意義不大,例如id和member_id以及url等。因此,我們直接刪除這些沒有意義且缺失值較多的屬性。此外,如果缺失值對屬性來說是有意義的,還得細分缺失值對應的屬性是數值型變數或是分類類型變數。

thresh_count = len(data)*0.4 # 設定閥值ndata = data.dropna(thresh=thresh_count, axis=1 ) #若某一列數據缺失的數量超過閥值就會被刪除n

再次檢查缺失值情況,發現缺失值比較多的數據列已被我們刪除。

data.isnull().sum(axis=0).sort_values(ascending=False)/float(len(data)) n

data.to_csv(loans_2017q2_ml.csv, index = False) # 將初步預處理後的數據轉化為csvn

再次用pandas解析數據。

loans = pd.read_csv(loans_2017q2_ml.csv,encoding=gb2312) nloans.dtypes.value_counts() # 分類統計數據類型n

我們通過Pandas的nunique方法來篩選屬性分類為一的變數,剔除分類數量只有1的變數,Pandas方法nunique()返回的是變數的分類數量(除去非空值)。

loans = loans.loc[:,loans.apply(pd.Series.nunique) != 1]n

查看數據的行列,發現數據已比之前少了3列。

loans.shape nout:(105455, 98)n

  • 缺失值處理——分類型變數

首先,我們查看分類變數缺失值的情況。

objectColumns = loans.select_dtypes(include=["object"]).columnsnloans[objectColumns].isnull().sum().sort_values(ascending=False)n

我們注意到分類變數中,"int_rate"、"revol_util"、「annual_inc」的屬性實質意義是數值,但pandas因為它們含有「%」符號或數字間有逗號而誤識別為字元。為了方便後續處理,我們先將他們的數據類型重分類。

loans[int_rate] = loans[int_rate].str.rstrip(%).astype(float)nloans[revol_util] = loans[revol_util].str.rstrip(%).astype(float)nloans[annual_inc] = loans[annual_inc].str.replace(",","").astype(float)nobjectColumns = loans.select_dtypes(include=["object"]).columns # 對objectColumns重新賦值n

對分類型變數缺失值來個感性認知。

msno.matrix(loans[objectColumns]) #缺失值可視化n

從上圖可以直觀看出變數「emp_title」、「next_pymnt_d」缺失值較多,同時圖的右邊分別反映了缺失值最多和最少的行數,分別是第25行和第0行。

msno.heatmap(loans[objectColumns]) #查看缺失值之間的相關性n

上圖顯示了缺失值之間的相關性,當相關性為0時,說明一個變數與另一個變數之間沒有影響。相關性接近1或-1說明變數之間呈現正相關或負相關。但我們從圖中發現,並不完全如此。zip_code和其他變數之間的相關性比較強,這與我們的期望相反,zip_code一般來說和其他變數沒什麼關係,有可能表明數據某些行的記錄是不完整的。

這個熱圖對於選擇變數對之間的數據完整性關係非常有用,但是當涉及到更大的數據關係時,它的視覺能力是有限的,而且對於非常大的數據集沒有特別的支持。

我們使用pandas.fillna()處理文本變數缺失值,為分類變數缺失值創建一個分類「Unknown」。

objectColumns = loans.select_dtypes(include=["object"]).columns # 篩選數據類型為object的數據nloans[objectColumns] = loans[objectColumns].fillna("Unknown") #以分類「Unknown」填充缺失值n

再次查看分類變數缺失值的情況,發現缺失值已被清洗乾淨。

msno.bar(loans[objectColumns]) #可視化n

  • 缺失值處理——數值型變數

查看數值型變數的缺失值情況。

loans.select_dtypes(include=[np.number]).isnull().sum().sort_values(ascending=False)n

numColumns = loans.select_dtypes(include=[np.number]).columnsnmsno.matrix(loans[numColumns]) #缺失值可視化n

pd.set_option(display.max_columns, len(loans.columns))nloans[numColumns]n

從表格發現,第105,451行至105,454行的屬性值全為NaN,這些空行對我們預測模型的構建沒有任何意義,在此先單獨刪除這些行。

loans.drop([105451,105452,105453,105454], inplace = True)nloans[numColumns].tail() # 默認查看錶格倒數5行n

對數值型變數的缺失值,我們採用均值插補的方法來填充缺失值,這裡使用可sklearn的Preprocessing模塊,參數strategy可選項有median或most_frequent以及median,具體詳見官方文檔sklearn.preprocessing.Imputer

from sklearn.preprocessing import Imputernimr = Imputer(missing_values=NaN, strategy=mean, axis=0) # 針對axis=0 列來處理nimr = imr.fit(loans[numColumns])nloans[numColumns] = imr.transform(loans[numColumns])n

msno.matrix(loans) # 再次檢查缺失值情況n

  • 數據過濾

對於同一份數據基於不同的數據挖掘目的,很多時候不需要把所有數據進行訓練。冗餘特徵重複了包含在一個或多個其他屬性中的許多或所有信息。例如,zip_code對於我們借款人的償債能力並沒有任何意義。grade和sub_grade是重複的屬性信息。下一步,我們對數據進行過濾。

objectColumns = loans.select_dtypes(include=["object"]).columnsnvar = loans[objectColumns].columnsnfor v in var:n print(nFrequency count for variable {0}.format(v))n print(loans[v].value_counts())nloans[objectColumns].shapen

  • sub_grade:與Grade的信息重複
  • emp_title :缺失值較多,同時不能反映借款人收入或資產的真實情況
  • zip_code:地址郵編,郵編顯示不全,沒有意義
  • addr_state:申請地址所屬州,不能反映借款人的償債能力
  • last_credit_pull_d :LendingClub平台最近一個提供貸款的時間,沒有意義
  • policy_code : 變數信息全為1
  • pymnt_plan 基本是n
  • title: title與purpose的信息重複,同時title的分類信息更加離散
  • next_pymnt_d : 下一個付款時間,沒有意義
  • policy_code : 沒有意義
  • collection_recovery_fee: 全為0,沒有意義
  • earliest_cr_line : 記錄的是借款人發生第一筆借款的時間
  • issue_d : 貸款發行時間,這裡提前向模型泄露了信息
  • last_pymnt_d、collection_recovery_fee、last_pymnt_amnt: 預測貸款違約模型是貸款前的風險控制手段,這些貸後信息都會影響我們訓練模型的效果,在此將這些信息刪除

將以上重複或對構建預測模型沒有意義的屬性進行刪除。

drop_list = [sub_grade, emp_title, title, zip_code, addr_state, n mths_since_last_delinq ,initial_list_status,title,issue_d,last_pymnt_d,last_pymnt_amnt,n next_pymnt_d,last_credit_pull_d,policy_code,collection_recovery_fee, earliest_cr_line]nloans.drop(drop_list, axis=1, inplace = True)n

分類型變數從28列被精減至11列

loans.select_dtypes(include = [object]).shapenout:(105448, 11)nloans.select_dtypes(include = [object]).head() # 再次概覽數據n

不同演算法模型需要不同的數據類型來建立。例如邏輯回歸只支持數值型的數據,而隨機森林通常對字元型和數值型都支持。由於在場景分析中,我們判定本項目預測貸款違約是一個二元分類問題,我們選擇的演算法是邏輯回歸演算法模型,從數據預處理的過程中也發現數據的結構是半結構化,因此需要對特徵數據作進一步轉換。

5 特徵工程(Feature Engineering)

"Coming up with features is difficult, time-consuming, requires expert knowledge. 「Applied machine learning」 is basically feature engineering."————Andrew Ng

"feature engineering is manually designing what the input xs should be"

————Tomasz Malisiewicz, answer to 「What is feature engineering?」

特徵工程是機器學習中最重要的步驟。實際工作中,特徵工程是個反覆迭代的過程,大部分時間也是在分析業務、分析case,不斷地找特徵。更好的特徵意味著只需要用簡單的模型,更好的特徵也意味著能夠獲得更好的依據去預測結果。2014年天池比賽,第一名團隊的模型並不複雜,但他們特徵更貼近業務,對業務場景理解比較好,出來的結果比淘寶負責做推薦的準確率提升16%。

本次項目特徵工程主要分4大部分:1、特徵衍生 2、特徵抽象 3、特徵縮放 4、特徵選擇

  • 5.1 特徵衍生

特徵衍生是指利用現有的特徵進行某種組合生成新的特徵。在風險控制方面,傳統銀行獲得企業的基本財務報表(資產負債表、利潤表以及現金流量表),藉助於現代成熟的財務管理體系,在不同業務場景的需求下,利用企業財務報表各種項目之間的組合,就可以衍生不同新特徵反映企業不同的財務狀況,例如資產與負債項目組合能夠生成反映企業債務情況的特徵,收入與應收賬款組合能生成反映應收賬款周轉率(資金效率)特徵等,同時還能利用企業財務報表之間的勾稽關係生成新特徵來佐證企業報表的質量。在金融風險控制中,要做好以上工作的前提是,你必須熟悉各種業務場景同時精通財務知識。

而Lending Club平台中,"installment"代表貸款每月分期的金額,我們將annual_inc除以12個月獲得貸款申請人的月收入金額,然後再把"installment"(月負債)與(annual_inc/12)(月收入)相除生成新的特徵installment_feat,新特徵installment_feat代表客戶每月還款支出占月收入的比,installment_feat的值越大,意味著貸款人的償債壓力越大,違約的可能性越大。

loans[installment_feat] = loans[installment] / (loans[annual_inc] / 12)n

  • 5.2 特徵抽象(feature abstraction)

特徵抽象是指將數據轉換成演算法可以理解的數據。

#使用Pandas replace函數定義新函數:nndef coding(col, codeDict):nn colCoded = pd.Series(col, copy=True)n for key, value in codeDict.items():n colCoded.replace(key, value, inplace=True)nn return colCodednn#把貸款狀態LoanStatus編碼為違約=1, 正常=0:nnpd.value_counts(loans["loan_status"])nnloans["loan_status"] = coding(loans["loan_status"], {Current:0,Fully Paid:0,In Grace Period:1,Late (31-120 days):1,Late (16-30 days):1,Charged Off:1})nnprint( nAfter Coding:)nnpd.value_counts(loans["loan_status"])n

# 貸款狀態分布可視化nfig, axs = plt.subplots(1,2,figsize=(14,7))nsns.countplot(x=loan_status,data=loans,ax=axs[0])naxs[0].set_title("Frequency of each Loan Status")nloans[loan_status].value_counts().plot(x=None,y=None, kind=pie, ax=axs[1],autopct=%1.2f%%)naxs[1].set_title("Percentage of each Loan status")nplt.show()n

從上一篇報告註冊會計師帶你探索風險分析(EDA)也提到,平台貸款發生違約的數量佔少數。貸款狀態為正常的有103,743個,貸款正常狀態佔比為98.38%。貸款狀態將作為我們建模的標籤,貸款狀態正常和貸款狀態違約兩者數量不平衡,絕大多數常見的機器學習演算法對於不平衡數據集都不能很好地工作,稍後我們將會解決樣本不平衡的問題。

object_columns_df =loans.select_dtypes(include=["object"]) #篩選數據類型為object的變數nprint(object_columns_df.iloc[0])n

對變數「delinq_2yrs」、「total_acc」、「last_pymnt_amnt」、「revol_bal」的數據類型重分類。

loans[delinq_2yrs] = loans[delinq_2yrs].apply(lambda x: float(x))nloans[total_acc] = loans[total_acc].apply(lambda x: float(x))nloans[revol_bal] = loans [revol_bal].apply(lambda x: float(x))nloans.select_dtypes(include=["object"]).describe().T # 再次檢查數據n

將變數類型為"object"的數量從30個縮減至7個。

多值有序變數(Ordinal Values)

多值有序變數也稱順序數據(rank data),有序多值變數是某一有序類別的非數字型數據。有序多值變數雖然是類別,但這些類別是有序的。比如將產品分為一等品、二等品、三等品、次品等;在上一篇報告中註冊會計師帶你探索風險分析(EDA),我們得知Lending Club對貸款申請者信用等級分類——A至G,相應地按照不同信用等級匹配貸款利率——等級為A的客戶信用評分比等級為B的客戶好。

  • A <B <C < D < E < F < G ; 信用風險從低到高排序

多值無序變數(Nominal Values)

多值無序變數又稱分類數據(categorical data),多值無序變數是某一類別的非數字型數據。它是對事物進行分類的結果,數據表現為類別,是用文字表述的。例如,借款人按照性別分為男、女兩類;分類數據中的分類是無序的,意味著我們並不能像多值有序變數那樣將多值無序變數(「purpose」)進行排序。

  • car < wedding < education < moving < house;這種排序不符合常識,也沒有任何意義

在此,我們對分類變數進一步細分。

  • 多值有序變數
    • grade
    • emp_length
  • 多值無序變數
    • term
    • home_ownership
    • verification_status
    • pupose
    • application_type

對不同分類變數的轉換需要使用不同的操作方法進行處理。

  • 有序特徵的映射

首先,我們對變數「emp_length」、"grade"進行特徵抽象化,使用的方法是先構建一個mapping,再用pandas的replace( )進行映射轉換,pandas的DataFrame.replace的具體用法,詳見官方文檔

# 構建mapping,對有序變數"emp_length」、「grade」進行轉換nmapping_dict = {n "emp_length": {n "10+ years": 10,n "9 years": 9,n "8 years": 8,n "7 years": 7,n "6 years": 6,n "5 years": 5,n "4 years": 4,n "3 years": 3,n "2 years": 2,n "1 year": 1,n "< 1 year": 0,n "n/a": 0n },n "grade":{n "A": 1,n "B": 2,n "C": 3,n "D": 4,n "E": 5,n "F": 6,n "G": 7n }n}nnloans = loans.replace(mapping_dict) #變數映射nloans[[emp_length,grade]].head() #查看效果n

從上面表格可以看出多值有序變數經過處理後的效果,已經將「emp_length」,「grade」轉化為演算法可以理解的數據類型。

  • 獨熱編碼(one-hot encoding)

接下來,對多值無序變數進行獨熱編碼(one-hot encoding)。

我們使用pandas的get_dummies( )方法創建虛擬特徵,虛擬特徵的每一列各代表變數屬性的一個分類。然後再使用pandas的concat()方法將新建虛擬特徵和原數據進行拼接。

get_dummies返回的一組數據是一個稀疏矩陣,但這組數據已經可以帶到演算法中進行計算。

n_columns = ["home_ownership", "verification_status", "application_type","purpose", "term"] ndummy_df = pd.get_dummies(loans[n_columns])# 用get_dummies進行one hot編碼nloans = pd.concat([loans, dummy_df], axis=1) #當axis = 1的時候,concat就是行對齊,然後將不同列名稱的兩張表合併n

查看變數「home_ownership」經過獨熱編碼處理的前後對比,我們發現「home_ownership」每一個分類均創建了一個新的虛擬特徵,虛擬特徵的每一列代表變數「home_ownership」屬性的一個值。從下表可以看出 ,我們已成功將n1_columns的變數轉化成演算法可理解的數據類型,這裡就不逐個展示。

loans.loc[:,loans.columns.str.contains("home_ownership")].head() #篩選包含home_ownership的所有變數n

loans = loans.drop(n_columns, axis=1) #清除原來的分類變數n

用pandas的info( )的方法作最後檢查,發現已經將所有類型為object的變數作轉化,所有數據類型均滿足下一步演算法的要求。

loans.info() #查看數據信息n

  • 5.3 特徵縮放(peature scaling)

特徵縮放(peature scaling)是指將變數數據經過處理之後限定到一定的範圍之內。特徵縮放本質是一個去量綱的過程,同時可以加快演算法收斂的速度。目前,將不同變數縮放到相同的區間有兩個常用的方法:歸一化(normalization)和標準化(standardization)。

我們採用的是標準化的方法,與歸一化相比,標準化的方法保持了異常值所包含的有用信息,並且使得演算法受到異常值的影響較小。想了解歸一化和標準化的比較詳見Standardization VS Normalization

變數標準化的具體操作可以採用scikit-learn模塊preprocessing的子模塊StandardScaler,具體用法詳見scikit-learn官網

col = loans.select_dtypes(include=[int64,float64]).columnsnlen(col)nout:78 #78個特徵ncol = col.drop(loan_status) #剔除目標變數nloans_ml_df = loans # 複製數據至變數loans_ml_dfn###################################################################################nfrom sklearn.preprocessing import StandardScaler # 導入模塊nsc =StandardScaler() # 初始化縮放器nloans_ml_df[col] =sc.fit_transform(loans_ml_df[col]) #對數據進行標準化nloans_ml_df.head() #查看經標準化後的數據n

目前為止,我們已經對特徵進行進行抽象化處理,使得變數的數據類型讓演算法可以理解,同時也將不同變數的規格縮放至同一規格。接下來,我們需要了解不同特徵對目標變數的影響程度並對特徵進行選擇。

  • 5.4 特徵選擇(feature selection)

特徵和數據決定了機器學習問題的上限,而模型和演算法只是盡最大可能地逼近這個上限。」——坊間常說

維基百科對Feature selection的定義:「特徵選擇是從給定的集合中選擇出相關特徵子集的過程。」

通常來說,對特徵集合做選擇主要有2個原因:首先,優先選擇與目標相關性較高的特徵,不相關特徵包含對於數據挖掘任務完全沒用的信息,不相關特徵可能會降低分類的準確率,因此為了增強模型的泛化能力,我們需要從原有特徵集合中挑選出最佳的部分特徵。其次,去除不相關特徵可以降低學習的難度(less is more),能夠簡化分類器的計算,同時幫助了解分類問題的因果關係。

在上一個報告註冊會計師帶你探索風險分析(EDA)中,我們對Lending Club平台2017年Q2的業務數據進行了EDA,通過不同維度的變數分析,探索數據的過程可以指導我們選擇特徵的方向,而我們這次選用另一種方法來選擇特徵。

一般來說,根據特徵選擇的思路將特徵選擇分為3種方法:嵌入方法(embedded approach)、過濾方法(filter approach)、包裝方法(wrapper approacch)。

  • 過濾方法(filter approach): 通過自變數之間或自變數與目標變數之間的關聯關係選擇特徵。
  • 嵌入方法(embedded approach): 通過學習器自身自動選擇特徵。
  • 包裝方法(wrapper approacch): 通過目標函數(AUC/MSE)來決定是否加入一個變數。

對於三種特徵選擇的方法,scikit-learn官網也有相應的工程實現方法,具體詳見Feature selection。本次項目,我採用Filter、Embedded和Wrapper三種方法組合進行特徵選擇。

#構建X特徵變數和Y目標變數nx_feature = list(loans_ml_df.columns)nx_feature.remove(loan_status)nx_val = loans_ml_df[x_feature]ny_val = loans_ml_df[loan_status]nlen(x_feature) # 查看初始特徵集合的數量n

x_val.describe().T # 初覽數據n

  • Wrapper

首先,選出與目標變數相關性較高的特徵。這裡採用的是Wrapper方法,通過暴力的遞歸特徵消除 (Recursive Feature Elimination)方法篩選30個與目標變數相關性最強的特徵,逐步剔除特徵從而達到首次降維,自變數從104個降到30個。

from sklearn.feature_selection import RFEnfrom sklearn.linear_model import LogisticRegressionn# 建立邏輯回歸分類器nmodel = LogisticRegression()n# 建立遞歸特徵消除篩選器nrfe = RFE(model, 30) #通過遞歸選擇特徵,選擇30個特徵nrfe = rfe.fit(x_val, y_val)n# 列印篩選結果nprint(rfe.support_)nprint(rfe.ranking_) #ranking 為 1代表被選中,其他則未被代表未被選中n

col_filter = x_val.columns[rfe.support_] #通過布爾值篩選首次降維後的變數ncol_filter # 查看通過遞歸特徵消除法篩選的變數n

  • Filter

正常情況下,影響目標變數的因數是多元性的;但不同因數之間會互相影響(共線性 ),或相重疊,進而影響到統計結果的真實性。下一步,我們在第一次降維的基礎上,通過皮爾森相關性圖譜找出冗餘特徵並將其剔除;同時,可以通過相關性圖譜進一步引導我們選擇特徵的方向。

colormap = plt.cm.viridisnplt.figure(figsize=(12,12))nplt.title(Pearson Correlation of Features, y=1.05, size=15)nsns.heatmap(loans_ml_df[col_filter].corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor=white, annot=True)n

drop_col = [funded_amnt, funded_amnt_inv,installment, out_prncp, out_prncp_inv,n total_pymnt_inv, total_rec_prncp, total_rec_int, home_ownership_OWN,n num_sats,application_type_JOINT, home_ownership_RENT ,n term_ 36 months, total_pymnt, verification_status_Source Verified, purpose_credit_card,int_rate]ncol_new = col_filter.drop(drop_col) #剔除冗餘特徵n

目前為止,特徵子集包含的變數從30個降維至13個。

len(col_new)n

col_new # 查看剩餘的特徵n

colormap = plt.cm.viridisnplt.figure(figsize=(12,12))nplt.title(Pearson Correlation of Features, y=1.05, size=15)nsns.heatmap(loans_ml_df[col_new].corr(),linewidths=0.1,vmax=1.0, square=True, cmap=colormap, linecolor=white, annot=True)n

  • Embedded

很多時候我們需要了解每個特徵對目標的影響程度,在特定的業務場景下,不同的特徵權重對業務的決策帶來不同的影響。例如,在Lending Club的業務數據中,能夠反映借款人資產狀況或現金流的特徵都對我們構建預測違約貸款模型十分關鍵。因此,我們需要對特徵的權重有一個正確的評判和排序,就可以通過特徵重要性排序來挖掘哪些變數是比較重要的,降低學習難度,最終達到優化模型計算的目的。這裡,我們採用的是隨機森林演算法判定特徵的重要性,工程實現方式採用scikit-learn的featureimportances 的方法,具體操介紹詳見官方文檔Feature importances with forests of trees

names = loans_ml_df[col_new].columnsnfrom sklearn.ensemble import RandomForestClassifiernclf=RandomForestClassifier(n_estimators=10,random_state=123)#構建分類隨機森林分類器nclf.fit(x_val[col_new], y_val) #對自變數和因變數進行擬合nnames, clf.feature_importances_nfor feature in zip(names, clf.feature_importances_):n print(feature)n

plt.style.use(fivethirtyeight)nplt.rcParams[figure.figsize] = (12,6)nn## feature importances 可視化##nimportances = clf.feature_importances_nfeat_names = namesnindices = np.argsort(importances)[::-1]nfig = plt.figure(figsize=(20,6))nplt.title("Feature importances by RandomTreeClassifier")nplt.bar(range(len(indices)), importances[indices], color=lightblue, align="center")nplt.step(range(len(indices)), np.cumsum(importances[indices]), where=mid, label=Cumulative)nplt.xticks(range(len(indices)), feat_names[indices], rotation=vertical,fontsize=14)nplt.xlim([-1, len(indices)])nplt.show()n

上圖是根據特徵在特徵子集中的相對重要性繪製的排序圖,這些特徵經過特徵縮放後,其特徵重要性的和為1.0。

由上圖我們可以得出的結論:基於決策樹的計算,特徵子集上最具判別效果的特徵是「loan_amnt」。貸款金額約高,意味著借款人固定流出的現金也越多,隨著借款人償還債務壓力增大,貸款違約的風險也隨之增加。

6 模型訓練

  • 處理樣本不平衡

前面提到,目標變數「loans_status」正常和違約兩種類別的數量差別較大,會對模型學習造成困擾。舉例來說,假如有100個樣本,其中只有1個是貸款違約樣本,其餘99個全為貸款正常樣本,那麼學習器只要制定一個簡單的方法:所有樣本均判別為正常樣本,就能輕鬆達到99%的準確率。而這個分類器的決策對我們的風險控制毫無意義。因此,在將數據代入模型訓練之前,我們必須先解決樣本不平衡的問題。

非平衡樣本常用的解決方式有2種:1、過採樣(oversampling),增加正樣本使得正、負樣本數目接近,然後再進行學習。2、欠採樣(undersampling),去除一些負樣本使得正、負樣本數目接近,然後再進行學習。

本次處理樣本不平衡採用的方法是過採樣,具體操作使用SMOTE(Synthetic Minority Oversampling Technique),SMOET的基本原理是:採樣最鄰近演算法,計算出每個少數類樣本的K個近鄰,從K個近鄰中隨機挑選N個樣本進行隨機線性插值,構造新的少數樣本,同時將新樣本與原數據合成,產生新的訓練集。更詳細說明參考CMU關於SMOTE: Synthetic Minority Over-sampling Technique的介紹。

# 構建自變數和因變數nX = loans_ml_df[col_new]ny = loans_ml_df["loan_status"]nnn_sample = y.shape[0]nn_pos_sample = y[y == 0].shape[0]nn_neg_sample = y[y == 1].shape[0]nprint(樣本個數:{}; 正樣本占{:.2%}; 負樣本占{:.2%}.format(n_sample,n n_pos_sample / n_sample,n n_neg_sample / n_sample))nprint(特徵維數:, X.shape[1])n

from imblearn.over_sampling import SMOTE # 導入SMOTE演算法模塊n# 處理不平衡數據nsm = SMOTE(random_state=42) # 處理過採樣的方法nX, y = sm.fit_sample(X, y)nprint(通過SMOTE方法平衡正負樣本後)nn_sample = y.shape[0]nn_pos_sample = y[y == 0].shape[0]nn_neg_sample = y[y == 1].shape[0]nprint(樣本個數:{}; 正樣本占{:.2%}; 負樣本占{:.2%}.format(n_sample,n n_pos_sample / n_sample,n n_neg_sample / n_sample))n

  • 構建分類器進行訓練

初始化分類器。

from sklearn.linear_model import LogisticRegressionnclf1 = LogisticRegression() # 構建邏輯回歸分類器nclf1.fit(X, y)n

predicted1 = clf.predict(X) # 通過分類器產生預測結果n

查看預則結果的準確率。

from sklearn.metrics import accuracy_scorenprint("Test set accuracy score: {:.5f}".format(accuracy_score(predicted1, y,)))n

藉助混淆矩陣進一步比較。

from sklearn.metrics import confusion_matrixnm = confusion_matrix(y, predicted1) nmn

上面是混淆矩陣對分類器產生不同類型的正誤數量的統計。為了更加直觀,我們對混淆矩陣進行可視化。

plt.figure(figsize=(5,3))nsns.heatmap(m) # 混淆矩陣可視化n

熱圖顏色越淺代表數量越多,從上圖可以看出真陽性的數量最多,而假陽性的數量最少。根據混淆矩陣,我們可以分別計算precision、recall、f1-score的值,這裡我們採用sklearn.metrics子模塊classification_report快速查看混淆矩陣precision、recall、f1-score的計算值。

from sklearn.metrics import classification_reportnprint(classification_report(y, predicted1))n

from sklearn.metrics import roc_auc_scorenroc_auc1 = roc_auc_score(y, predicted1)nprint("Area under the ROC curve : %f" % roc_auc1)n

以上只是一個baseline模型,接下來我們繼續優化模型。

7 模型評估與優化

在上一個步驟中,我們的模型訓練和測試都在同一個數據集上進行,這會產生2個問題:

1、很可能導致學習器把訓練樣本學得「太好」,把訓練樣本自身的特點當做所有潛在樣本都會具有的一般性質。

2、模型在同一個數據集上進行訓練和測試,使得測試集的樣本屬性提前泄露給模型。

以上2個問題都會導致模型的泛化能力下降,這種現象我們稱之為「過擬合」(overfitting)。因此,我們需要將數據集劃分為測試集和訓練集,讓模型在訓練集上學習,在測試集上測試模型的判別能力。

通常來說,將數據集劃分為訓練集和測試集有3種處理方法:1、留出法(hold-out),2、交叉驗證法(cross-validation),3、自助法(bootstrapping)

本次項目我們採用交叉驗證法劃分數據集,將數據劃分為3部分:訓練集(training set)驗證集(validation set)測試集(test set)。讓模型在訓練集進行學習,在驗證集上進行參數調優,最後使用測試集數據評估模型的性能。

模型調優我們採用網格搜索調優參數(grid search),通過構建參數候選集合,然後網格搜索會窮舉各種參數組合,根據設定評定的評分機制找到最好的那一組設置。

結合cross-validationgrid search,具體操作我們採用scikit learn模塊model_selection中的GridSearchCV方法。具體可以參看sklearn官網關於GridSearchCV的介紹。自己做了個GridSearchCV的流程圖:

  • cross-validation+grid search

from sklearn.model_selection import GridSearchCVnfrom sklearn.cross_validation import train_test_split nnX_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3, random_state = 0) # random_state = 0 每次切分的數據都一樣n# 構建參數組合nparam_grid = {C: [0.01,0.1, 1, 10, 100, 1000,],n penalty: [ l1, l2]}nngrid_search = GridSearchCV(LogisticRegression(), param_grid, cv=10) # 確定模型LogisticRegression,和參數組合param_grid ,cv指定5折ngrid_search.fit(X_train, y_train) # 使用訓練集學習演算法n

results = pd.DataFrame(cl2.cv_results_) nbest = np.argmax(results.mean_test_score.values)n

從上面能夠直觀可出,模型最優參數組合為l2和C=1000

  • 模型性能評估

print("Best parameters: {}".format(grid_search.best_params_))nprint("Best cross-validation score: {:.5f}".format(grid_search.best_score_))n

results# 查看分析報告n

scores = np.array(results.mean_test_score).reshape(2, 6)nheatmap(scores, ylabel=penalty, yticklabels=param_grid[penalty],n xlabel=C, xticklabels=param_grid[C], cmap="viridis")n

上圖模型在不同參數組合下跑出的分數熱力圖,我們可以從熱力圖明顯看出,l2比l1的表現普遍要好,當C小於1時,模型表現較差。同時,我可以利用熱力圖來尋找參數調優的方向,進一步選擇更優的參數。而實際操作中,模型調參是一個反覆迭代的過程。

print("Best estimator:n{}".format(grid_search.best_estimator_))#grid_search.best_estimator_ 返回模型以及他的所有參數(包含最優參數)n

現在,我們使用經過訓練和調優的模型在測試集上測試。

y_pred = grid_search.predict(X_test)nprint("Test set accuracy score: {:.5f}".format(accuracy_score(y_test, y_pred,)))n

print(classification_report(y_test, y_pred))n

m2 = confusion_matrix(y_test, y_pred) nplt.figure(figsize=(5,3))nsns.heatmap(m2) # 混淆矩陣可視化n

roc_auc2 = roc_auc_score(y_test, y_pred)nprint("Area under the ROC curve : %f" % roc_auc2)n

從上面數字可以看出,經過我們對模型進行訓練和參數調優後,雖然精確率平均分降低了,但模型的精確率表現更穩定,同時模型的準確率和AUC分數都有很大的提升,其中準確率從0.55762提升至0.62775,升幅達到12.58%AUC的分數也從0.557623提升至0.627906,升幅達到12.60%

PS:最後附上吳恩達最新的演講「AI是新電力!」


推薦閱讀:

盤點 2016 年那些此消彼長的科技熱點
2016波瀾壯闊的人工智慧元年從AlphaGo到國產狗絕藝系列評論之二
人從那裡來-人是什麼系列外篇之二
【Python機器學習】用遺傳演算法實現符號回歸——淺析gplearn
【專知薈萃20】圖像分割Image Segmentation知識資料全集(入門/進階/論文/綜述/視頻/專家,附查看)

TAG:机器学习 | 人工智能 | 数据分析 |