【博客存檔】拍拍貸風控預測比賽分享
前記
前一段時間參加了拍拍貸的一個風控預測比賽,從平均400個數據維度評估用戶當前的信用狀態,給每個借款人打出當前狀態的信用分,具體比賽可以見拍拍貸魔鏡杯 本人初賽46名,複賽最終結果23名,在比賽當中,由於工作之後項目壓力比較大,時間不足,沒有詳細分析數據,只是簡單地對原始數據做了一些基本的處理,比較傾向於model的調優,和一些大神的對原始數據詳細的分析來看還有特別大的差距,以後的比賽會盡量話更多時間在數據處理方面,雖然這是我不太擅長的,但是要想出成績必須要在這部分多花時間。
所有的代碼都開源到了我的github拍拍貸代碼,可以看看我代碼裡面的基本的欄位處理、特徵的生成方式以及對xgboost模型的封裝來使用sklearn的調參方法。
比賽數據分析
拍拍貸比賽數據主要包括master、log_info、userupdate_info三部分數據,分別是: Master 每一行代表一個樣本(一筆成功成交借款),每個樣本包含200多個各類欄位。 idx:每一筆貸款的unique key,可以與另外2個文件里的idx相匹配。 UserInfo_:借款人特徵欄位 WeblogInfo_:Info網路行為欄位 Education_Info:學歷學籍欄位 ThirdParty_Info_PeriodN_:第三方數據時間段N欄位 SocialNetwork_*:社交網路欄位 LinstingInfo:借款成交時間 Target:違約標籤(1 = 貸款違約,0 = 正常還款)。測試集里不包含target欄位。
Log_info 借款人的登陸信息。 ListingInfo:借款成交時間 LogInfo1:操作代碼 LogInfo2:操作類別 LogInfo3:登陸時間 idx:每一筆貸款的unique key
Userupdate_info 借款人修改信息 ListingInfo1:借款成交時間 UserupdateInfo1:修改內容 UserupdateInfo2:修改時間 idx:每一筆貸款的unique key
特徵處理
clean_data.py – category變數除了UserInfo_2,UserInfo_4直接做 factorize,因為在tree類的model,不需要做 dummies處理; – UserInfo_2,UserInfo_4是城市信息,不用 factorize處理,取兩列城市並集,然後做映射; – 從baidu上拉出城市的經緯度信息,這樣可以找 出對應城市的經緯度信息,能夠解決一些在經緯 度上相關聯的數據問題; – 增加欄位UserInfo_2_4_01,為0表示UserInfo_2 與UserInfo_4相等,反之,為1; – 使用從city_ratio.py生產個UserInfo_2的target為0 的數量以及佔比和UserInfo_4中target為0的數量與 佔比;
nCoding Just for FunnCreated by burness on 16/3/18.nnimport pandas as pdnimport numpy as npnfrom env_variable import *ndef clean_data(train_master_file, test_master_file, debug, save_data, city_common):n if debug:n train_master_data = pd.read_csv(second_train_master_file, nrows = 500,encoding=gb18030)n train_master_data[tag] = 1n test_master_data = pd.read_csv(second_test_master_file, nrows = 500,encoding=gb18030)n test_master_data[tag] = 0n # target =2n test_master_data[target] = 2n all_master_data = train_master_data.append(test_master_data)n else:n train_master_data = pd.read_csv(second_train_master_file,encoding=gb18030)n train_master_data[tag] = 1n test_master_data = pd.read_csv(second_test_master_file,encoding=gb18030)n test_master_data[tag] = 0n test_master_data[target] = 2n all_master_data = train_master_data.append(test_master_data)n # find the category columnsn category_list = ["UserInfo_2", "UserInfo_4", "UserInfo_7", "UserInfo_8", "UserInfo_19", "UserInfo_20", "UserInfo_1", n "UserInfo_3", "UserInfo_5", "UserInfo_6", "UserInfo_9", "UserInfo_2", "UserInfo_4", n "UserInfo_7", "UserInfo_8", "UserInfo_19", "UserInfo_20", "UserInfo_11", "UserInfo_12", "UserInfo_13", n "UserInfo_14", "UserInfo_15", "UserInfo_16", "UserInfo_18", "UserInfo_21", "UserInfo_22", "UserInfo_23", n "UserInfo_24", "Education_Info1", "Education_Info2", "Education_Info3", "Education_Info4", n "Education_Info5", "Education_Info6", "Education_Info7", "Education_Info8", "WeblogInfo_19", n "WeblogInfo_20", "WeblogInfo_21", "SocialNetwork_1", "SocialNetwork_2", "SocialNetwork_7", n "ListingInfo", "SocialNetwork_12"]n # want to see the UserInfo_2 and UserInfo_4 add the feature whether UserInfo_2 and UserInfo_4 is equaln city_category_list = ["UserInfo_2", "UserInfo_4"]n user_info_2 = all_master_data[UserInfo_2].unique()n user_info_4 = all_master_data[UserInfo_4].unique()n ret_list = list(set(user_info_2).union(set(user_info_4)))n ret_list_dict = dict(zip(ret_list,range(len(ret_list))))n print ret_list_dictnnn # print all_master_data[[UserInfo_2,UserInfo_4]].head()n all_master_data[UserInfo_2] = all_master_data[UserInfo_2].map(ret_list_dict)n all_master_data[UserInfo_4] = all_master_data[UserInfo_4].map(ret_list_dict)nn for col in category_list:n if city_common:n if col in city_category_list:n continuen else:n all_master_data[col] = pd.factorize(all_master_data[col])[0]n else:n all_master_data[col] = pd.factorize(all_master_data[col])[0]nn print all_master_data.shapen city_lat_pd = pd.read_csv(city_geo_info_file,encoding=gb18030)n # print city_lat_pd.head(200)n city_lat_pd[UserInfo_2] = city_lat_pd[city].map(ret_list_dict)n city_lat_pd = city_lat_pd.drop(city,axis=1)n all_master_data = all_master_data.merge(city_lat_pd,on=UserInfo_2,how=left)n city_lat_pd[UserInfo_4] = city_lat_pd[UserInfo_2]n city_lat_pd = city_lat_pd.drop(UserInfo_2,axis=1)n all_master_data = all_master_data.merge(city_lat_pd,on=UserInfo_4,how=left)n print all_master_data.shapenn # add a feature whether the UserInfo_2 and UserInfo_4 are equaln def is_equal(x):n if x[UserInfo_2] == x[UserInfo_4]:n x[UserInfo_2_4_01] = 0n else:n x[UserInfo_2_4_01] = 1n return x[UserInfo_2_4_01]nn all_master_data[UserInfo_2_4_01] = all_master_data.apply(is_equal, axis=1)n # print all_master_data[[UserInfo_2_4_01,UserInfo_2,UserInfo_4]].head()n print all_master_data.shapen # add the ratiocountall_count of each UserInfo_2 and UserInfo_4n userinfo_2_ratio_pd = pd.read_csv(second_userinfo_2_ratio)n userinfo_4_ratio_pd = pd.read_csv(second_userinfo_4_ratio)n print userinfo_2_ratio_pd.shapen print userinfo_4_ratio_pd.shapen # merge the userinfo_2_ratio_pd and userinfo_4_ratio_pdn all_master_data = all_master_data.merge(userinfo_2_ratio_pd, on=UserInfo_2, how=left)n all_master_data = all_master_data.merge(userinfo_4_ratio_pd, on=UserInfo_4, how=left)nn print all_master_data.shapennn # save the factorizen if save_data:n all_master_data.to_csv(second_save_master_factorize_file,index=None)n # print all_master_data.shapen # clean the -1n all_master_data = all_master_data.replace(-1,np.nan)n print all_master_data.shapen if save_data:n # all_master_data.to_csv(save_master_factorize_file_nan,index=None)n all_master_data.to_csv(second_save_master_factorizeV2_file_nan,index=None)nn # # dummiesn # for col in category_list:n # temp = pd.get_dummies(all_master_data[col],prefix=col)n # all_master_data = pd.concat([all_master_data,temp], axis=1)n # print all_master_data.shapen # if save_data:n # all_master_data.to_csv(save_master_factorize_file_nan_dummies,index=None)n # all_master_data.to_csv(save_master_factorizeV2_file_nan_dummies,index=None)nnnif __name__ == __main__:n clean_data(second_train_master_file, second_test_master_file, debug = False, save_data = True, city_common = True)n # clean_data(train_master_file,test_master_file,debug = False, save_data = True, city_common = True)n
create_features.py 增加log和user update數據: – 登錄的次數、頻率、時間區間; – 用戶更改信息的次數; – 增加用戶修改信息如修改qq或者是否有車,則在 對應位置置1,增加約55維二值變數.
#-*-coding:utf-8-*-nnCoding Just for FunnCreated by burness on 16/3/19.nnimport pandas as pdnfrom env_variable import *nntrain_log_file_pd = pd.read_csv(second_test_log_info_name, encoding=gb18030)ntest_log_file_pd = pd.read_csv(second_test_log_info_name, encoding=gb18030)nall_log_info_pd = train_log_file_pd.append(test_log_file_pd)n# # all_log_info_name = ../dat a/all/log_info.csvn# print all_log_info_pd.shapen#n# all_log_info_pd = pd.read_csv(all_log_info_name, encoding=gb18030)nall_log_info_pd[diff_days] = all_log_info_pd[Listinginfo1].astype(datetime64) - all_log_info_pd[LogInfo3].astype(datetime64)nall_log_info_pd[diff_days] = all_log_info_pd[diff_days].astype(str).str.replace( days 00:00:00.000000000,).astype(int)nnnall_log_info_pd[LogInfo1] = all_log_info_pd[LogInfo1].astype(str)nall_log_info_pd[LogInfo2] = all_log_info_pd[LogInfo2].astype(str)nall_log_info_pd[LogInfo1_2] = all_log_info_pd[[LogInfo1,LogInfo2]].apply(lambda x: ,.join(x),axis=1)n# groupby Idx LogInfo1_2nall_log_info_final_pd = pd.DataFrame()nna= all_log_info_pd.groupby(Idx)[LogInfo1_2].count()ndiff_min = all_log_info_pd.groupby(Idx)[diff_days].min()ndiff_max = all_log_info_pd.groupby(Idx)[diff_days].max()nnnfreq = a/(1.0+diff_max-diff_min)nnall_log_info_final_pd[count] = anall_log_info_final_pd[freq] = freqnall_log_info_final_pd[diff_min] = diff_minnall_log_info_final_pd[diff_max] = diff_maxnall_log_info_final_pd[period] = diff_max-diff_minnn# print all_log_info_final_pd.reset_index().head()nall_log_info_final_pd = all_log_info_final_pd.reset_index()n# print all_log_info_final_pd.reset_index().head()nall_log_info_final_pd.to_csv(second_all_log_info_file,index=None, encoding=gb18030)nnnnnuser_update_info_train_pd = pd.read_csv(second_train_update_log_file_name,encoding=gb18030)nuser_update_info_test_pd = pd.read_csv(second_test_update_log_file_name, encoding=gb18030)n# user_update_info_train_pd[tag] = 1n# user_update_info_test_pd[tag] = 0nuser_update_info_all_pd = user_update_info_train_pd.append(user_update_info_test_pd)nuser_update_info_all_pd[UserupdateInfo1] = user_update_info_all_pd[UserupdateInfo1].str.lower()nuser_update_info_all_pd[UserupdateInfo1] = pd.factorize(user_update_info_all_pd[UserupdateInfo1])[0]n# print user_update_info_all_pd.head()nupdate_count = user_update_info_all_pd.groupby(Idx)[UserupdateInfo1].count()n# print update_count.head()nuser_update_info_all_pd[update_diff_days] = user_update_info_all_pd[ListingInfo1].astype(datetime64)-user_update_info_all_pd[UserupdateInfo2].astype(datetime64)nuser_update_info_all_pd[update_diff_days] = user_update_info_all_pd[update_diff_days].astype(str).str.replace( days 00:00:00.000000000,).astype(int)n# print user_update_info_all_pd.head()nupdate_diff_min = user_update_info_all_pd.groupby(Idx)[update_diff_days].min()nupdate_diff_max = user_update_info_all_pd.groupby(Idx)[update_diff_days].max()nupdate_freq = freq = update_count/(1.0+update_diff_max-update_diff_min)nnall_update_info_final_pd = pd.DataFrame()nall_update_info_final_pd[update_count] = update_countnall_update_info_final_pd[update_idff_min] = update_diff_minnall_update_info_final_pd[update_idff_max] = update_diff_maxnall_update_info_final_pd[update_freq] = freqnall_update_info_final_pd[update_period] = update_diff_max-update_diff_minnall_update_info_final_pd = all_update_info_final_pd.reset_index()n# 增加那些欄位信息有更改n# update_info_list = user_update_info_all_pd[UserupdateInfo1].unique()nprint user_update_info_all_pd.shapenIdx_userupdateInfo1 = user_update_info_all_pd[[Idx,UserupdateInfo1]].drop_duplicates()nprint Idx_userupdateInfo1.shapenIdx_userupdateInfo1_pd = Idx_userupdateInfo1.assign(c = 1).set_index(["Idx", "UserupdateInfo1"]).unstack("UserupdateInfo1").fillna(0)nIdx_userupdateInfo1_pd = Idx_userupdateInfo1_pd.reset_index()n# print len(update_info_list)nprint Idx_userupdateInfo1_pd.shapenprint Idx_userupdateInfo1_pd.head(5)nIdx_userupdateInfo1_pd.to_csv(second_userupdate_file,index=False)n# do some process in the filenIdx_userupdateInfo1_pd = pd.read_csv(second_userupdate_file,header=None)ncolumns_list = [Idx]nfor i in range(55):n temp_str = user_update_+str(i)n columns_list.append(temp_str)nIdx_userupdateInfo1_pd.columns = columns_listnnprint all_update_info_final_pd.shapenall_update_info_final_pd.to_csv(all_update_info_file,index=None, encoding=gb18030)n#n#n# # merge the log info and update info to the save_master_factorizeV2_file_nannall_master_facotrizeV2_file_nan = pd.read_csv(second_save_master_factorizeV2_file_nan, encoding=gb18030)nprint all_master_facotrizeV2_file_nan.shapenall_master_facotrizeV2_file_nan_log = all_master_facotrizeV2_file_nan.merge(all_log_info_final_pd, on=Idx,how=left)nall_master_facotrizeV2_file_nan_log_update = all_master_facotrizeV2_file_nan_log.merge(all_update_info_final_pd, on=Idx, how=left)nprint all_master_facotrizeV2_file_nan_log_update.shapenn# merge the userupdateinfor1_pdnall_master_facotrizeV2_file_nan_log_update = all_master_facotrizeV2_file_nan_log_update.merge(Idx_userupdateInfo1_pd,on=Idx,how=left)nprint all_master_facotrizeV2_file_nan_log_update.shapenall_master_facotrizeV2_file_nan_log_update.to_csv(second_save_master_factorizeV2_file_nan_log_updateV2,index=None)n
模型選擇
最初採用三種模型,後來因為性能以及計算能力的問題,捨去了LR和network
- lr_model.py:Logistic Regression模型來預測,效果不好,放棄;
- network.py: 用keras封裝的神經網路模型,機器太差調參跑不了,放棄;
- XgboostClassifier.py: 將xgboost封裝為sklearn pipeline支持的類,方便調參,且時間成本相對nn較低, 效果在初賽也比較好,故選擇Xgboost作為model。 代碼基本解釋:
- xgb_model.py: 查看對應的cv分數,初步判斷num_boost為eta的一些初步取值範圍;
- xgb_feature_selection.py: 通過xgboost的feature importance觀察那些feature的重要性更高,然後對 那一類特徵做基本的特徵組合處理(feature_ensemble.py);
- xgb1_20_2.py RandomSearch 尋找xgboost最優參數;
- xgb1_20_2_pos_weight.py 與xgb1_20_2.py效果一樣,考慮不平衡樣本數量,多了scale_pos_weight, 但是發現效果一般;
- feature_ensemble.py 從xgb_feature_selection.py中選擇一批importance比較高的特徵值,然後做組合 特徵計算。
最終訓練流程圖
流程圖可以歸納為兩個部分,一個是feature的生成:從原始數據中得到一些features,然後fit到xgboost,得到feature的 importance,然後對feature importance比較靠前的features做ensemble,相當於增加feature的維度數,最後fit到model,得到最終的預測結果。
總結
- 特徵工程太複雜,而有點枯燥,個人對這個興趣不大,且機器不給力,很多方案沒有有效驗證;
- 模型調參應該先根據xgb.cv進行粗調,盲目調參太費時間;
- 特徵選擇策略對最終結果影響很大,這裡耗費時間太多太多,單機每次嘗試太費時間;
- 使用多模型做ensemble處理能夠有效防止單模型的帶來的隨機性問題,一般都能提高 AUC;
- 以為是24號完成數據提交即可,前面只是一直在測試數據方案,最終數據提交沒有等數 據cv跑出來後做ensemble,人工定的幾個結果做的ensemble,且沒有考慮local CV的score來做 ensemble的weight。
感謝群裡面一些同學在拍拍貸比賽過程中的相關分享
推薦閱讀: