《Python金融大数据风控建模实战》 第8章 Logistic回归模型
- 本章引言
- Python代码实现及注释
本章引言
Logistic回归是建立评分卡模型最常用的方法,因其具有输出概率、可解释性好和模型参数少等优势,即使在其他各种机器学习算法突飞猛进的情况下,也仍然是工业界建立评分卡模型的主流方法,其模型表现也常作为参考标准,用于衡量其他机器学习模型的效果。
Python代码实现及注释
# 第8章:logistic回归模型
import os
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import variable_bin_methods as varbin_meth
import variable_encode as var_encode
from sklearn.metrics import confusion_matrix,recall_score,accuracy_score,precision_score
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression
import warnings
warnings.filterwarnings("ignore") ##忽略警告
##数据读取
def data_read(data_path,file_name):
df = pd.read_csv( os.path.join(data_path, file_name), delim_whitespace = True, header = None )
##变量重命名
columns = ['status_account','duration','credit_history','purpose', 'amount',
'svaing_account', 'present_emp', 'income_rate', 'personal_status',
'other_debtors', 'residence_info', 'property', 'age',
'inst_plans', 'housing', 'num_credits',
'job', 'dependents', 'telephone', 'foreign_worker', 'target']
df.columns = columns
##将标签变量由状态1,2转为0,1;0表示好用户,1表示坏用户
df.target = df.target - 1
##数据分为data_train和 data_test两部分,训练集用于得到编码函数,验证集用已知的编码规则对验证集编码
data_train, data_test = train_test_split(df, test_size=0.2, random_state=0,stratify=df.target)
return data_train, data_test
##离散变量与连续变量区分
def category_continue_separation(df,feature_names):
categorical_var = []
numerical_var = []
if 'target' in feature_names:
feature_names.remove('target')
##先判断类型,如果是int或float就直接作为连续变量
numerical_var = list(df[feature_names].select_dtypes(include=['int','float','int32','float32','int64','float64']).columns.values)
categorical_var = [x for x in feature_names if x not in numerical_var]
return categorical_var,numerical_var
if __name__ == '__main__':
path = 'D:\\code\\chapter8'
data_path = os.path.join(path ,'data')
file_name = 'german.csv'
##读取数据
data_train, data_test = data_read(data_path,file_name)
sum(data_train.target ==0)
data_train.target.sum()
##区分离散变量与连续变量
feature_names = list(data_train.columns)
feature_names.remove('target')
categorical_var,numerical_var = category_continue_separation(data_train,feature_names)
for s in set(numerical_var):
print('变量'+s+'可能取值'+str(len(data_train[s].unique())))
if len(data_train[s].unique())<=10:
categorical_var.append(s)
numerical_var.remove(s)
##同时将后加的数值变量转为字符串
index_1 = data_train[s].isnull()
if sum(index_1) > 0:
data_train.loc[~index_1,s] = data_train.loc[~index_1,s].astype('str')
else:
data_train[s] = data_train[s].astype('str')
index_2 = data_test[s].isnull()
if sum(index_2) > 0:
data_test.loc[~index_2,s] = data_test.loc[~index_2,s].astype('str')
else:
data_test[s] = data_test[s].astype('str')
###连续变量分箱
dict_cont_bin = {}
for i in numerical_var:
print(i)
dict_cont_bin[i],gain_value_save , gain_rate_save = varbin_meth.cont_var_bin(data_train[i], data_train.target, method=2,
mmin=3, mmax=12, bin_rate=0.01, stop_limit=0.05, bin_min_num=20)
###离散变量分箱
dict_disc_bin = {}
del_key = []
for i in categorical_var:
dict_disc_bin[i],gain_value_save , gain_rate_save ,del_key_1 = varbin_meth.disc_var_bin(data_train[i], data_train.target, method=2, mmin=3,
mmax=8, stop_limit=0.05, bin_min_num=20)
if len(del_key_1)>0 :
del_key.extend(del_key_1)
###删除分箱数只有1个的变量
if len(del_key) > 0:
for j in del_key:
del dict_disc_bin[j]
##训练数据分箱
##连续变量分箱映射
df_cont_bin_train = pd.DataFrame()
for i in dict_cont_bin.keys():
df_cont_bin_train = pd.concat([ df_cont_bin_train , varbin_meth.cont_var_bin_map(data_train[i], dict_cont_bin[i]) ], axis = 1)
##离散变量分箱映射
# ss = data_train[list( dict_disc_bin.keys())]
df_disc_bin_train = pd.DataFrame()
for i in dict_disc_bin.keys():
df_disc_bin_train = pd.concat([ df_disc_bin_train , varbin_meth.disc_var_bin_map(data_train[i], dict_disc_bin[i]) ], axis = 1)
##测试数据分箱
##连续变量分箱映射
df_cont_bin_test = pd.DataFrame()
for i in dict_cont_bin.keys():
df_cont_bin_test = pd.concat([ df_cont_bin_test , varbin_meth.cont_var_bin_map(data_test[i], dict_cont_bin[i]) ], axis = 1)
##离散变量分箱映射
# ss = data_test[list( dict_disc_bin.keys())]
df_disc_bin_test = pd.DataFrame()
for i in dict_disc_bin.keys():
df_disc_bin_test = pd.concat([ df_disc_bin_test , varbin_meth.disc_var_bin_map(data_test[i], dict_disc_bin[i]) ], axis = 1)
###组成分箱后的训练集与测试集
df_disc_bin_train['target'] = data_train.target
data_train_bin = pd.concat([df_cont_bin_train,df_disc_bin_train],axis=1)
df_disc_bin_test['target'] = data_test.target
data_test_bin = pd.concat([df_cont_bin_test,df_disc_bin_test],axis=1)
data_train_bin.reset_index(inplace=True,drop=True)
data_test_bin.reset_index(inplace=True,drop=True)
###WOE编码
var_all_bin = list(data_train_bin.columns)
var_all_bin.remove('target')
##训练集WOE编码
df_train_woe, dict_woe_map, dict_iv_values ,var_woe_name = var_encode.woe_encode(data_train_bin,data_path,var_all_bin, data_train_bin.target,'dict_woe_map', flag='train')
##测试集WOE编码
df_test_woe, var_woe_name = var_encode.woe_encode(data_test_bin,data_path,var_all_bin, data_test_bin.target, 'dict_woe_map',flag='test')
####取出训练数据与测试数据
x_train = df_train_woe[var_woe_name]
x_train = np.array(x_train)
y_train = np.array(data_train_bin.target)
x_test = df_test_woe[var_woe_name]
x_test = np.array(x_test)
y_test = np.array(data_test_bin.target)
########logistic模型
##设置优化参数
lr_param = {'C': [0.01, 0.1, 0.2, 0.5, 1, 1.5, 2],
'class_weight': [{1: 1, 0: 1}, {1: 2, 0: 1}, {1: 3, 0: 1}]}
##初始化网格搜索
lr_gsearch = GridSearchCV(
estimator=LogisticRegression(random_state=0, fit_intercept=True, penalty='l2', solver='saga'),
param_grid=lr_param, cv=3, scoring='f1', n_jobs=-1, verbose=2)
##执行超参数优化
lr_gsearch.fit(x_train, y_train)
'''
上述网格搜索中有两个超参数,第一个超参数有7种可能性取值,第二个超参数有3种可能性取值,采用2折交叉验证,则总共经历7×3×3=63次
模型训练与评估,即每种参数组合都要通过交叉验证的方式得到评估指标的平均结果,即在3折交叉数据中的2折数据上建模,在另1折数据集上
测试得到评估指标结果,将平均评估指标最好的的那组参数组合作为最优超参数。
'''
print('logistic model best_score_ is {0},and best_params_ is {1}'.format(lr_gsearch.best_score_,
lr_gsearch.best_params_))
##用最优参数,初始化Logistic模型
'''
这里采用scikit-learn中linear-model模型下的LogisticRegression()函数进行Logistic回归模型训练,其原型如下:
class sklearn.linear_model.LogisticRegression(penalty='l2', dual= False,tol=0.0001,C=1.0, fit_intercept=True,intercept_scaling=1,
class_weight=None,random_state=None,solver='warn', max_iter=100, multi_class = 'warn',verbose=0, warm_start=False, n_jobs=None,l1_ratio=None)
主要参数说明:
penalty:选择使用哪种正则项来抑制过拟合,为字符串,可以为None、'l1'、'l2'、'elasticnet',不同的正则项可选的优化策略不同,
默认为'l2',即使用L2正则,如果为None,则表示不加正则项
dual:布尔量,是否使用对偶形式,只能在L2正则且求解方式为liblinear solver才可以使用
tol:指定模型迭代收敛的阈值
C:正则项的惩罚系数。C=1/λ,因此C与实际作用在模型的惩罚成反比。即C越大,对正则项的惩罚效果越小,模型越容易过拟合,反之,
C越小,对正则项的惩罚效果越大,模型越容易过拟合
fit_intercept:模型训练时是否添加截距项,即w0。默认为True,即添加截距项。
class_weight:确定是否加权来抑制样本不均衡,可以为字典形式的加权也可以自动加权。当设置为'balanced'时,则按照正负样本的比例加权。
如果需要自定义加权,可以采用字典的形式,如{'1':3,'0':1}即正样本的权重为3,负样本的权重为1。通过影响损失函数
的计算来抑制样本不均衡问题。默认为None即不加权,正负样本的权重相同。
random_state:随机数设置,可以固定训练集,方便多个算法在同一个训练集上训练并比较模型的性能。
solver:字符串,用于选择不同的优化算法。
max_iter:整数,模型优化最大迭代次数。
主要属性说明:
coef_:用于查询模型训练后最终的权重系数,其数量与特征数相同,即每个特征训练一个权重。
intercept_:用于查看截距项
n_iter_:返回模型训练实际的迭代次数
主要方法说明:
fit(self,X,y[,sample_weight]):用于模型训练,在训练集上训练模型
perdict(self,X):用于模型训练,在测试集上得到预测结果,这里给出标签输出,即0,1输出。
predict_proba(self,X):用于模型预测。与perdict()方法不同的是,该方法给出概率预测结果,如ROC曲线就需要模型提供概率输出才可以进行计算
'''
LR_model_2 = LogisticRegression(C=lr_gsearch.best_params_['C'], penalty='l2', solver='saga',
class_weight=lr_gsearch.best_params_['class_weight'])
##训练Logistic模型
LR_model_fit = LR_model_2.fit(x_train, y_train)
##模型预测
y_score_train = LR_model_fit.predict_proba(x_train)[:, 1]
y_score_test = LR_model_fit.predict_proba(x_test)[:, 1]
##计算混淆矩阵与recall、precision
y_pred = LR_model_fit.predict(x_test)
cnf_matrix = confusion_matrix(y_test, y_pred)
recall_value = recall_score(y_test, y_pred)
precision_value = precision_score(y_test, y_pred)
acc = accuracy_score(y_test, y_pred)
print(cnf_matrix)
print('Validation set: model recall is {0},and percision is {1}'.format(recall_value,
precision_value))