实验——贝叶斯决策论预测贷款是否违约
目錄
一 實驗說明
1.1 背景
1.2 實驗數據說明
1.3 實驗注意事項
二 實驗數據分析
三 實驗原理
3.1 主要原理
3.2 處理分類數據
3.3 數據離散化
3.4 拉普拉斯平滑
四 代碼實現
4.1 眾數填充缺失值
4.2 處理分類數據
4.3 離散化
4.4 構造分類器
4.5 主函數與評估模型
五 實驗結果
參考
一 實驗說明
1.1 背景
????????信用風險是指銀行向用戶提供金融服務后,用戶不還款的概率。信用風險一直是銀行貸款決策中廣泛研究的領域。信用風險對銀行和金融機構,特別是商業銀行來說,起著至關重要的作用,但是一直以來都比較難管理。
????????本實驗以貸款違約為背景,要求使用貝葉斯決策論的相關知識在訓練集上構建模型,在測試集上進行貸款違約預測并計算分類準確度。
1.2 實驗數據說明
????????訓練數據集train.csv包含9000條數據,測試數據集test.csv包含1000條數據。注意,訓練集和測試集中都有缺失值存在。以下是字段說明:
| loan_id | 貸款記錄唯一標識 |
| user_id | 借款人唯一標識 |
| total_loan | 貸款數額 |
| year_of_loan | 貸款年份 |
| interest | 當前貸款利率 |
| monthly_payment | 分期付款金額 |
| Grade/class | 貸款級別 |
| employment_type | 所在公司類型 |
| industry | 工作領域 |
| work_year | 工作年限 |
| home_exist | 是否有房 |
| censor_status | 審核情況 |
| issue_date | 貸款發放的月份 |
| use | 貸款用途類別 |
| post_code | 貸款人申請時郵政編碼 |
| region | 地區編碼 |
| debt_loan_ratio | 債務收入比 |
| del_in_18month | 借款人過去18個月逾期30天以上的違約事件數 |
| scoring_low | 借款人在貸款評分中所屬的下限范圍 |
| scoring_high | 借款人在貸款評分中所屬的上限范圍 |
| known_outstanding_loan | 借款人檔案中未結信用額度的數量 |
| known_dero | 貶損公共記錄的數量 |
| pub_dero_bankrup | 公開記錄清除的數量 |
| recircle_bal | 信貸周轉余額合計 |
| recircle_util | 循環額度利用率 |
| initial_list_status | 貸款的初始列表狀態 |
| app_type | 是否個人申請 |
| earlies_credit_mon | 借款人最早報告的信用額度開立的月份 |
| title | 借款人提供的貸款名稱 |
| policy_code | 公開可用的策略代碼=1新產品不公開可用的策略代碼=2 |
| f系列匿名特征 | 匿名特征f0-f4,為一些貸款人行為計數特征的處理 |
| early_return | 借款人提前還款次數 |
| early_return_amount | 貸款人提前還款累積金額 |
| early_return_amount_3mon | 近3個月內提前還款金額 |
| isDefault | 貸款是否違約(預測標簽) |
1.3 實驗注意事項
實驗不限制使用何種高級語言,推薦使用python中pandas庫處理csv文件。
在進行貝葉斯分類之前重點是對數據進行預處理操作,如,缺失值的填充、將文字表述轉為數值型、日期處理格式(處理成“年-月-日”三列屬性或者以最早時間為基準計算差值)、無關屬性的刪除等方面。
數據中存在大量連續值的屬性,不能直接計算似然,需要將連續屬性離散化。
另外,特別注意零概率問題,貝葉斯算法中如果乘以0的話就會失去意義,需要使用平滑技術。
實驗目的是使用貝葉斯處理實際問題,不得使用現成工具包直接進行分類。
實驗代碼中需要有必要的注釋。
二 實驗數據分析
????????訓練樣本是沒有預處理的數據,直接用來訓練模型是不現實的,首先我們要進行數據的分析。
????????貸款記錄唯一標識(loan_id)、借款人唯一標識(user_id)屬于標志屬性,與實際問題沒有關聯,可以刪除。
????????貸款年份(year_of_loan)、貸款發放的月份(issue_date)、借款人最早報告的信用額度開立的月份(earlies_credit_mon)、貸款人申請時郵政編碼(post_code)、地區編碼(region)。這些時間地點屬性對貸款人的還款行為沒有過多影響,也可以剔除。【1】
? ? ? ? 通過對剩下的字段畫出頻率分布直方圖分析:
? ? ? ? ?是否個人申請(app.type)、公開可用的策略(policy_code)、匿名特征(f1)屬于歸一化屬性,即一個變量大部分的觀測都是相同的特征,那么認為此類特征變量無法顯著區分目標變量,可以考慮將其刪除。
? ? ? ? 而工作領域(industry)、(貸款級別)class、(工作年限)work_year、(所在公司類型)employer_type,這些字段都是屬于分類變量,需要進行分類處理。
? ? ? ? 同時字段total_loan、interest、monthly_payment、debt_loan_ratio、scoring_low、scoring_high、 recircle_b、recircle_u、early_return_amount_3mon的取值是連續值(小數),因此需要對這些數據進行離散化。特征離散化后,模型會更穩定,降低了模型過擬合的風險。
? ? ? ? 對于缺失的數據,可以利用眾數填充。
三 實驗原理
3.1 主要原理
? ? ? ? 本次實驗原理主要為樸素貝葉斯決策論。什么是貝葉斯決策論可以看我的上一篇文章:
(3條消息) 貝葉斯決策論理論_Sunburst7的博客-CSDN博客
????????如果使用普通的貝葉斯方法計算后驗概率時,類條件概率??是特征向量??上所有特征的聯合概率,難以從有限的訓練樣本中直接得到。為了避開這個障礙,樸素貝葉斯分類器采用了屬性條件獨立性假設:對已知類別,假設所有屬性相互獨立,換言之,假設每個屬性獨立地對分類結果發生影響。基于這個假設,我們的后驗概率可以修改為:【2】
????????
? ? ? ? 對于特征向量中的每一個特征的條件概率容易計算?去除相同的證據因子,再取對數,我們得到模型分類器:
????????其中j是預測類型,J為預測類型空間。
? ? ? ? ?對于本實驗的二分類問題,只要簡單的比較兩個類型的g(x)即可。
3.2 處理分類數據
????????有時候,根據某種特性而不是數量來度量對象會更有效。我們常常使用這種定性的信息來判斷一個觀察值的屬性,比如按照性別、顏色或者車的品牌這樣的類別對其分類。本身沒有內在順序的特征類別稱為 nominal。分類的特征總是有某種天然順序的稱為ordinary。【3】
- 對于內部沒有順序的分類(性別,水果類型):通常使用one-hot編碼
- 對于內部有順序的分類(非常同意、同意、保持中立、反對..):將 ordinal 分類轉換成數值,同時保留其順序。最常見的方法就是,創建一個字典,將分類的字符串標簽映射為一個數字,然后將 其映射在特征上。
? ? ? ? 本次實驗中的class、industry、work_year、employer_type都屬于分類特征,我們可以采用將特征值映射到一個數字的方法。
3.3 數據離散化
? ? ? ? 對于連續型的特征,在計算似然時很難找到同類型的數據,這樣就會出現類條件概率為0的情況,使得分類器判別出現誤差,為了避免這種情況,需要將連續特征離散化。這里用到的技術通常被稱為數據分箱:【4】
? ? ? ? 常見的分箱方法主要分為有監督與無監督兩種,本實驗采用卡方分箱對數據離散化:
-
無監督分箱:不需要提供預測標簽,僅憑借特征就能實現分箱
-
等寬分箱
-
等頻分箱
-
-
有監督分箱:需要結合預測標簽的值,通過算法實現分箱
-
決策樹分箱
-
卡方分箱:關于卡方分箱的原理可以看參考【5】的博客
-
3.4 拉普拉斯平滑
????????計算要預測數據集的某個特征似然時,如果在觀察樣本庫(訓練集)中沒有出現過,會導致類條件概率結果是0。在貝葉斯分類中如果乘以0的話,整個后驗概率就會失去意義。
????????為了解決零概率的問題,法國數學家拉普拉斯最早提出用加1的方法估計沒有出現過的現象的概率,所以加法平滑也叫做拉普拉斯平滑。假定訓練樣本很大時,每個分量x的計數加1造成的估計概率變化可以忽略不計,但可以方便有效的避免零概率問題。因此特征向量中某一個特征的類條件概率密度的計算公式可以改寫為:
????????? ? ? ? 其中N表示特征xi有幾種不同取值? ? ? ?
四 代碼實現
4.1 眾數填充缺失值
? ? ? ? dataframe.isnull().any()返回一個Series,某行/列存在缺失值為True,axis=0表示跨行檢測,即每一列跨行檢測,返回一個與列等寬的Series
? ? ? ? dataframe.fillna(value,inplace=True)表示用value填充DataFrame中NaN的數據,inplace=True表示填充內存中的DataFrame而不是副本。
# 按照眾數填充缺失值 def fillMissingColumn(dataframe: pd.DataFrame):# 檢測出有缺失值的列,返回一個Series,有缺失值的列為True,無缺失值的列為Falsemissing_column = dataframe.isnull().any(axis=0) # 按列檢測for index, value in missing_column.items():if value:print(index + " needs to fill missing values")# 利用眾數填充有缺失值的行dataframe[index].fillna(dataframe[index].mode()[0], inplace=True) # 一定要設置inplace=True 修改內存的值4.2 處理分類數據
? ? ? ? 將class(A,B,C,D,E)利用func函數映射到1-5。mapper定義了映射字典,將分類特征映射到對應的整數。最后利用dataframe.replace(dict)替換
# 處理nominal型的分類數據-industry:一共有14類 使用one-hot編碼太大,還是采用簡單編碼 # 處理ordinary型的分類數據:employer_type class work_year def classifyOrdinary(dataframe: pd.DataFrame):# 創建class特征 映射器,將A-1,B-2...F-6func = lambda x: ord(x) - 64 # ord()將字母轉變為ASCII碼# 將class特征分類dataframe['class'] = dataframe['class'].apply(func)# 創建編碼映射器mapper = {'industry': {'金融業': 0,'電力、熱力生產供應業': 1,'公共服務、社會組織': 2,'住宿和餐飲業': 3,'信息傳輸、軟件和信息技術服務業': 4,'文化和體育業': 5,'建筑業': 6,'房地產業': 7,'采礦業': 8,'交通運輸、倉儲和郵政業': 9,'農、林、牧、漁業': 10,'制造業': 11,'批發和零售業': 12,'國際組織': 13},'work_year': {'< 1 year': 0,'1 year': 1,'2 years': 2,'3 years': 3,'4 years': 4,'5 years': 5,'6 years': 6,'7 years': 7,'8 years': 8,'9 years': 9,'10+ years': 10,},'employer_type': {'普通企業': 1,'幼教與中小學校': 2,'政府機構': 3,'上市企業': 4,'高等教育機構': 5,'世界五百強': 6}}# 離散化dataframe = dataframe.replace(mapper, inplace=True)4.3 離散化
????????參考【4】中的博客,利用scorecardpy包先計算出分箱區間:
import pandas as pd import scorecardpy as sc# 導入兩列數據 df = pd.DataFrame({'年齡': [29,7,49,12,50,34,36,75,61,20,3,11],'Y' : [0,0,1,1,0,1,0,1,1,0,0,0]}) ? bins = sc.woebin(df, y='Y', method='chimerge') # 卡方分箱 sc.woebin_plot(bins)? ? ? ? 使用np.digitize(DataFrame,bin:list)進行離散化
"""連續的屬性值無法計算似然(后驗概率)!需要將其離散化——數據分箱需要離散化的列有:total_loan、interest、monthly_payment、debt_loan_ratio、scoring_low、scoring_high、recircle_b、recircle_u、early_return_amount_3mon、early_return_amount、title參考博客:https://blog.csdn.net/Orange_Spotty_Cat/article/details/116485079 """ def discretize(dataframe):# 使用分箱技術dataframe['total_loan'] = np.digitize(dataframe['total_loan'], bins=[8000, 21000, 24000, 31000])# dataframe['interest'] = np.digitize(dataframe['interest'], bins=range(5, 36, 1)) # 4.779-33.979dataframe['interest'] = np.digitize(dataframe['interest'], bins=[7, 9, 10, 12, 16, 21])# dataframe['monthly_payment'] = np.digitize(dataframe['monthly_payment'], bins=range(100, 2000, 100)) # 30.44-1503.89dataframe['monthly_payment'] = np.digitize(dataframe['monthly_payment'], bins=[250, 500])# dataframe['debt_loan_ratio'] = np.digitize(dataframe['debt_loan_ratio'], bins=range(10, 1000, 10)) # 0-999dataframe['debt_loan_ratio'] = np.digitize(dataframe['debt_loan_ratio'], bins=[11, 15, 26])# dataframe['scoring_low'] = np.digitize(dataframe['scoring_low'], bins=range(10, 1000, 10)) # 540-910.9dataframe['scoring_low'] = np.digitize(dataframe['scoring_low'], bins=[560, 600, 630, 660, 680, 780])# dataframe['scoring_high'] = np.digitize(dataframe['scoring_high'], bins=range(10, 2000, 10)) # 585.0-1131.818182dataframe['scoring_high'] = np.digitize(dataframe['scoring_high'], bins=[730, 750, 860])# dataframe['recircle_b'] = np.digitize(dataframe['recircle_b'], bins=range(10000, 770000, 10000)) # 0.0-779021.0dataframe['recircle_b'] = np.digitize(dataframe['recircle_b'], bins=[16000])# dataframe['recircle_u'] = np.digitize(dataframe['recircle_u'], bins=range(1, 120, 1)) # 0.0-120.6153846dataframe['recircle_u'] = np.digitize(dataframe['recircle_u'], bins=[38, 56, 66, 70])# dataframe['early_return_amount_3mon'] = np.digitize(dataframe['early_return_amount_3mon'],bins=range(1, 5500, 10)) # 0.0-5523.9dataframe['early_return_amount_3mon'] = np.digitize(dataframe['early_return_amount_3mon'], bins=[50, 150, 1250])dataframe['title'] = np.digitize(dataframe['title'], bins=[1])dataframe['early_return_amount'] = np.digitize(dataframe['early_return_amount'], bins=[5000,10000,15000,20000])4.4 構造分類器
? ? ? ? 為了避免大量重復的計算類條件概率(有很多特征取值相同,計算的類條件概率也是重復的),先用兩個numpy的二維數據(利用下標訪問)存儲所有可能特征值的類條件概率。計算出還剩下29個特征,經過離散化后每個特征的取值類型在100種以內。初始化為0。
? ? ? ? train.columns.get_loc(columns_label)用于返回某個列名的列索引值。
"""樸素貝葉斯分類,假設每個字段之間相互獨立:小數的連乘可能下溢,因此對p(x|w)*p(w)取對數 為了防止零概率情況使log無意義,使用拉普拉斯平滑技術定義一個分類器 g(x) = lnp(x|w)+lnp(w)輸入一個特征向量x與一個數據集,輸出它的分類 """ # 創建兩個dataframe分別緩存isDefault=0與isDefault=1的似然值 storeage0 = np.zeros((29,100)) storeage1 = np.zeros((29,100)) def classifier(x, train: pd.DataFrame):# 分別統計貸款沒違約與貸款違約的情況type0 = train[train['isDefault'] == 0]type1 = train[train['isDefault'] == 1]# 計算行數sum_type0 = type0.count().values[0]sum_type1 = type1.count().values[0]# 計算先驗概率prior_0 = sum_type0 / (sum_type0 + sum_type1)prior_1 = sum_type1 / (sum_type0 + sum_type1)# print(str(prior_0) + " " + str(prior_1))# 初始化分類器值(加上lnp(w))g0 = math.log(prior_0)g1 = math.log(prior_1)# print(str(g0) + " " + str(g1))# 計算所有列的似然/類條件概率密度for column in train.columns:if column != 'isDefault': # 去除預測標簽的影響likelihood0, likelihood1 = 0, 0if storeage0[train.columns.get_loc(column)][int(x[column])] > 0:# 緩存中已有數據likelihood0 = storeage0[train.columns.get_loc(column)][int(x[column])]else:# 計算拉普拉斯平滑后的似然likelihood0 = (type0[type0[column] == x[column]].count().values[0] + 1) / (sum_type0 + train[column].nunique())# 按照行-列索引,列—特征值 將數據保存在緩存中storeage0[train.columns.get_loc(column)][int(x[column])] = likelihood0# 對 isDefault = 1的訓練集數據進行一次同樣的操作,計算后驗概率if storeage1[train.columns.get_loc(column)][int(x[column])] > 0:likelihood1 = storeage1[train.columns.get_loc(column)][int(x[column])]else:likelihood1 = (type1[type1[column] == x[column]].count().values[0] + 1) / (sum_type1 + train[column].nunique())storeage1[train.columns.get_loc(column)][int(x[column])] = likelihood1# 取對數ln_likelihood0 = math.log(likelihood0)ln_likelihood1 = math.log(likelihood1)# print("type0: likelihood: " + str(likelihood0) + " ln:" + str(ln_likelihood0))# print("type1: likelihood: " + str(likelihood1) + " ln:" + str(ln_likelihood1))g0 += ln_likelihood0g1 += ln_likelihood1# print('------------------------------------------------------------------')# print(str(g0) + " " + str(g1))if g0 >= g1:# 預測為不違約return 0else:return 14.5 主函數與評估模型
????????假設貸款不違約(isDefault=0)為正例,貸款違約(isDefault=1)為負例。利用正確率、精度、召回率評估模型。
# 讀取訓練集 trainSet = pd.read_csv('train.csv') # 讀取測試集 testSet = pd.read_csv('test.csv') # 刪除無關數據列(用戶的id,貸款年份(year_of_loan)、貸款發放的月份(issue_date)、借款人最早報告的信用額度開立的月份(earlies_credit_mon)、貸款人申請時郵政編碼(post_code)、地區編碼(region)等信息,主觀判斷其對是否違約影響甚微。都是無關屬性) trainSet = trainSet.drop(['year_of_loan', 'loan_id', 'user_id', 'earlies_credit_mon', 'issue_date', 'post_code', 'region'], axis=1) testSet = testSet.drop(['year_of_loan', 'loan_id', 'user_id', 'earlies_credit_mon', 'issue_date', 'post_code', 'region'], axis=1) # 畫出頻率分布直方圖分析數據 fig,axs=plt.subplots(8,4,figsize=(40,20),sharex=False,sharey=False) for i in range(10):for j in range(4):if i*4+j<31:axs[i][j].set_title(trainSet.columns[i*4+j])axs[i][j].bar(x=pd.value_counts(trainSet[trainSet.columns[i*4+j]]).index,height=pd.value_counts(trainSet[trainSet.columns[i*4+j]]).values) plt.show()# 而app_type policy_code 與 f1都是歸一化屬性,直接去除 trainSet = trainSet.drop(['app_type', 'policy_code', 'f1'], axis=1) testSet = testSet.drop(['app_type', 'policy_code', 'f1'], axis=1) # 數據預處理 fillMissingColumn(trainSet) classifyOrdinary(trainSet) discretize(trainSet)fillMissingColumn(testSet) classifyOrdinary(testSet) discretize(testSet)# 保存分類器的分類結果 isDefault_f = [] for index, row in testSet.iterrows():isDefault_f.append(classifier(row, trainSet)) # 將分類結果添加到測試集中 testSet['forecast'] = isDefault_f # 創建評估數據集 evaluation = testSet[['isDefault', 'forecast']] print(evaluation) # 假設 沒有違約(isDefault == 0)為正例 TP = 0 # 真正例 TN = 0 # 真負例 FP = 0 # 假正例 FN = 0 # 假負例 for index, row in evaluation.iterrows():if row['isDefault'] == 0 and row['forecast'] == 0:TP += 1if row['isDefault'] == 0 and row['forecast'] == 1:FN += 1if row['isDefault'] == 1 and row['forecast'] == 0:FP += 1if row['isDefault'] == 1 and row['forecast'] == 1:TN += 1Accuracy = (TP + TN) / (TP + TN + FP + FN) Precision = TP / (TP + FP) Recall = TP / (TP + FN) print("正確率: %f" % Accuracy) print("精確率: %f" % Precision) print("召回率: %f" % Recall)五 實驗結果
? ? ? ? 測試集共有1000行數據,forecast為我們預測的數據。正確率還有待改進。
參考
【1】(3條消息) Lending Club貸款違約預測_Mango的博客-CSDN博客
【2】機器學習—周志華
【3】Python機器學習手冊:從數據預處理到深度學習
【4】數據科學貓:數據預處理 之 數據分箱(Binning)_Orange_Spotty_Cat的博客-CSDN博客
【5】從論文分析,告訴你什么叫 “卡方分箱”? - 云+社區 - 騰訊云 (tencent.com)
總結
以上是生活随笔為你收集整理的实验——贝叶斯决策论预测贷款是否违约的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【路径规划】基于粒子群算法实现机器人栅格
- 下一篇: Cocos2d-Html5--打怪升级之