机器学习实验笔记-基于信用卡数据建立行为评分模型的机器学习方法
Posted lagoon_lala
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了机器学习实验笔记-基于信用卡数据建立行为评分模型的机器学习方法相关的知识,希望对你有一定的参考价值。
基于信用卡数据建立行为评分模型的机器学习方法
很久之前的一个答疑, 应该不会再影响评分了, 记录以供复习. 数据集与代码放在CSDN下载区域, 也可以留言索要.
https://download.csdn.net/download/lagoon_lala/87636214
目录
数据预处理
读取数据
我们使用pandas库读取数据文件, 可以看到其中包括目标变量共有262个变量, 148077个样本.
data = pd.read_csv('data/data_model.csv') print("读入数据的样本与变量个数:",data.shape)#(148077, 262) |
import pandas as pd data = pd.read_csv('data/data_model.csv') # print(data.shape)#(148077, 262) data |
总样本数量:148077 总变量数量:262 (148077, 262)
数据概况
本文用到的数据为银行真实业务场景下的脱敏数据,在这个数据集上进行数据处理及建模。其中包括261项脱敏的特征,以及是否是逾期的标签项,共148077条数据。
特征类型包含:
客户基本信息, 账户信息, 开卡, 总体交易行为, 分期行为, 取现行为, 还款行为, 消费行为.
缺失值分析
根据数据介绍,在数据样本中,变量的缺失值统一以特殊数值表示。特征变量缺失率表明某个特征缺失是否严重。缺失率高,说明这些特征缺失严重,可能会对预测模型带来干扰。一般来说,对于高维数据和普通模型,通过删除缺失率较高的特征,可以减少噪音特征对模型的干扰。
统计缺失率高的特征
missing_rate = pd.read_csv('data/missing_rate.csv', index_col=0) high_missing_features=missing_rate[missing_rate['missing_rate'] > 0.7] high_missing_features |
缺失率高的特征为
xaccount_phonechange | 最近一次号码变更距观察点时长 |
xaccount_addrchange | 最近一次地址变更距观察点时长 |
xaccount_empchange | 最近一次单位名称修改时长 |
xaccount_autopaytime | 持卡人首次关联自扣时长 |
tran_cyclenum_STMT3roi | 近三期账单循环交易笔数环比增长率 |
tran_cyclenum_STMT6roi | 近六期账单循环交易笔数环比增长率 |
tran_cyclenum_STMT12roi | 近十二期账单循环交易笔数环比增长率 |
由于存在信息的样本太少, 难以训练, 直接删除
(若不删除, 也可前4个特征的缺失原因为未修改, 缺失值可以定义为99999
后3个特征无缺失原因, 前n期账单交易金额为0, 可以定义为99999)
删除缺失率高的特征
data_cleaned = data.drop(high_missing_features.index, axis=1) data_cleaned |
得到的数据特征数已经减少:
148077 rows × 255 columns
法二 根据特征名删除
由变量说明表可得, 以下特征缺失率较高(缺失率 > 70%):
xaccount_phonechange
xaccount_addrchange
xaccount_empchange
xaccount_autopaytime
tran_cyclenum_STMT3roi
tran_cyclenum_STMT6roi
tran_cyclenum_STMT12roi
# 删除缺失率高的特征 high_missing_features=['xaccount_phonechange','xaccount_addrchange','xaccount_empchange','xaccount_autopaytime','tran_cyclenum_STMT3roi','tran_cyclenum_STMT6roi','tran_cyclenum_STMT12roi'] data_cleaned = data.drop(high_missing_features, axis=1) print("删除缺失率高的特征:",data_cleaned.shape) |
缺失值填充
其中, 一些缺失值的填充有意义, 无需修改, 如:
xaccount_credlimitnum | 额度调整次数 |
xaccount_credlimitrenum | 额度降低次数 |
各类占比的缺失值均因为金额为0, 原为-1, 改设为0
data_cleaned = data_cleaned.replace(-1, 0) |
可以看到其中为-1的值已设为0
对这些特征的缺失值进行填充为0:
TRAN_NOW_NUMMONRIO | 近一期月交易笔数环比增长率 | ||
TRAN_STMT3_NUMMONRIO | 近三期月均交易笔数环比增长率 | ||
TRAN_STMT6_NUMMONRIO | 近六期月均交易笔数环比增长率 | ||
TRAN_STMT12_NUMMONRIO | 近十二期月均交易笔数环比增长率 | ||
TRAN_NOW_AMTMONRIO | 近一期交易金额环比增长率 | ||
TRAN_STMT3_AMTMONRIO | 近三期月均交易金额环比增长率 | ||
TRAN_STMT6_AMTMONRIO | 近六期月均交易金额环比增长率 | ||
TRAN_STMT12_AMTMONRIO | 近十二期月均交易金额环比增长率 | ||
tran_num_STMT1roi | 近一期账单交易笔数环比增长率 | ||
tran_num_STMT3roi | 近三期账单交易笔数环比增长率 | ||
tran_num_STMT6roi | 近六期账单交易笔数环比增长率 | ||
tran_num_STMT12roi | 近十二期账单交易笔数环比增长率 | ||
tran_amt_STMT1roi | 近一期账单交易金额环比增长率 | ||
tran_amt_STMT3roi | 近三期账单交易金额环比增长率 | ||
tran_amt_STMT6roi | 近六期账单交易金额环比增长率 | ||
tran_amt_STMT12roi | 近十二期账单交易金额环比增长率 | ||
tran_mpamortize_num_STMT1roi | 近一期账单大额交易笔数环比增长率 | ||
tran_mpamortize_num_STMT3roi | 近三期账单大额交易笔数环比增长率 | ||
tran_mpamortize_num_STMT6roi | 近六期账单大额交易笔数环比增长率 | ||
tran_mpamortize_num_STMT12roi | 近十二期账单大额交易笔数环比增长率 | ||
bill_num_STMT1roi | 当月消费笔数环比增长率 | ||
bill_num_STMT3roi | 近三期月均消费 笔数环比增长率 | ||
bill_num_STMT6roi | 近六期月均消费 笔数环比增长率 | ||
bill_num_STMT12roi | 近十二期月均消费 笔数环比增长率 | ||
bill_amt_STMT1roi | 当月消费金额环比增长率 | ||
bill_amt_STMT3roi | 近三期月均消费金额环比增长率 | ||
bill_amt_STMT6roi | 近六期月均消费金额环比增长率 | ||
bill_amt_STMT12roi | 近十二期月均消费金额环比增长率 | ||
设为0(或利用随机森林等方法对缺失值进行预测).
features_resetNA=['TRAN_NOW_NUMMONRIO','TRAN_STMT3_NUMMONRIO','TRAN_STMT6_NUMMONRIO','TRAN_STMT12_NUMMONRIO','TRAN_NOW_AMTMONRIO','TRAN_STMT3_AMTMONRIO','TRAN_STMT6_AMTMONRIO','TRAN_STMT12_AMTMONRIO','tran_num_STMT1roi','tran_num_STMT3roi','tran_num_STMT6roi','tran_num_STMT12roi','tran_amt_STMT1roi','tran_amt_STMT3roi','tran_amt_STMT6roi','tran_amt_STMT12roi','tran_mpamortize_num_STMT1roi','tran_mpamortize_num_STMT3roi','tran_mpamortize_num_STMT6roi','tran_mpamortize_num_STMT12roi','bill_num_STMT1roi','bill_num_STMT3roi','bill_num_STMT6roi','bill_num_STMT12roi','bill_amt_STMT1roi','bill_amt_STMT3roi','bill_amt_STMT6roi','bill_amt_STMT12roi'] data_cleaned[features_resetNA] = data_cleaned[features_resetNA].replace(9999, 0) # data_cleaned[features_resetNA].replace(9999, 0,inplace=True) data_cleaned |
为避免部分特征值过大, 将剩余的定义为9999的缺失值修改为999
data_cleaned = data_cleaned.replace(9999, 999) |
类别型变量处理
特征中变量类型为字符的可以处理为类别型变量. 将变量类型为字符的特征转换为类别型变量.
# 使用get_dummies()函数将变量类型为字符的特征转换为类别型变量 data_dummies = pd.get_dummies(data) data_dummies |
异常值处理
所用特征皆为正数才有意义, 若出现负数则设为相反数
data_cleaned = data_dummies.mask(data_dummies < 0, -data_dummies) (data_cleaned< 0).sum() |
划分数据/标签
首先, 从data_cleaned中提取'target'变量, 并将其保存为y
然后, 从data_cleaned中提取所有其他变量, 并将其保存为X:
y = data_cleaned['target'] X = data_cleaned.drop('target', axis=1) X,y |
划分训练集测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) print("划分训练集测试集:",X_train.shape,X_test.shape) |
数据标准化
归一化和标准化
归一化将数据缩放到[0,1]范围内,公式为:
$$x_norm = \\fracx - \\min(x)\\max(x) - \\min(x)$$
标准化将数据转化为均值为0,标准差为1的正态分布,公式为:
$$x_std = \\fracx - \\mu\\sigma$$
其中,$\\mu$为均值,$\\sigma$为标准差。
原始数据特征的数据密度分布不均匀,而归一化和标准化能够尽可能地消除这种情况下由特殊数据带来的影响。
归一化和标准化的适用场景
(1)数据的分布本身就服从正态分布,使用Z-Score。
(2)有离群值的情况:使用Z-Score。
Min-Max对于离群值十分敏感,因为离群值的出现,会影响数据中max或min值,从而使Min-Max的效果很差。相比之下,虽然使用Z-Score计算方差和均值的时候仍然会受到离群值的影响,但是相比于Min-Max法,影响会小一点。
(3)如果对输出结果范围有要求,用归一化。
(4)如果数据较为稳定,不存在极端的最大最小值,用归一化。
(5)如果数据存在异常值和较多噪音,用标准化,可以间接通过中心化避免异常值和极端值的影响。
此处考虑到数据存在离群值, 故使用标准化.
import numpy as np # 标准化 def standardize(X): X_mean = np.mean(X, axis=0) X_std = np.std(X, axis=0) X_std = np.where(X_std==0, 1, X_std) X_std = X_std.reshape(1, -1) X_std = np.repeat(X_std, X.shape[0], axis=0) X_std = (X - X_mean) / X_std return X_std |
# 数据标准化 X_train_std = standardize(X_train) X_test_std = standardize(X_test) |
作图展示归一化和标准化的效果
为了展示归一化和标准化的效果,我们可以通过绘制直方图或者箱线图来比较原始数据和预处理后的数据的分布情况。
将原始数据、归一化后的数据和标准化后的数据的直方图和箱线图绘制在了同一张图中,方便对比效果。可以看出,归一化后的数据在每个特征维度上都被缩放到了[0, 1]的范围内,而标准化后的数据在每个特征维度上呈现出了均值为0,标准差为1的正态分布。
import matplotlib.pyplot as plt # 绘制直方图 fig, axs = plt.subplots(1, 2, figsize=(12, 4)) axs[0].hist(X_train.iloc[:, 0], bins=50) axs[0].set_xlabel('Feature 1') axs[0].set_ylabel('Frequency') axs[0].set_title('Original Data') # axs[1].hist(X_norm[:, 0], bins=50) # axs[1].set_xlabel('Feature 1') # axs[1].set_ylabel('Frequency') # axs[1].set_title('Normalized Data') axs[1].hist(X_train_std.iloc[:, 0], bins=50) axs[1].set_xlabel('Feature 1') axs[1].set_ylabel('Frequency') axs[1].set_title('Standardized Data') plt.show() # 绘制箱线图 fig, axs = plt.subplots(1, 2, figsize=(12, 4)) axs[0].boxplot(X_train) axs[0].set_xticklabels(['Feature 1', 'Feature 2', 'Feature 3', 'Feature 4']) axs[0].set_ylabel('Value') axs[0].set_title('Original Data') # axs[1].boxplot(X_norm) # axs[1].set_xticklabels(['Feature 1', 'Feature 2', 'Feature 3', 'Feature 4']) # axs[1].set_ylabel('Value') # axs[1].set_title('Normalized Data') axs[1].boxplot(X_train_std) axs[1].set_xticklabels(['Feature 1', 'Feature 2', 'Feature 3', 'Feature 4']) axs[1].set_ylabel('Value') axs[1].set_title('Standardized Data') plt.show() |
标准化后的直方图与箱线图如下
可以看出标准化后的数据分布更加均匀了.
方法二:
在实际应用中,归一化和标准化通常是根据具体情况选择一种或者同时进行的。如果需要同时进行归一化和标准化,首先进行归一化,然后计算归一化后数据的均值和标准差,最后根据归一化后数据的均值和标准差进行标准化。
# 归一化和标准化 def normalize_and_standardize(X): X_min = np.min(X, axis=0) X_max = np.max(X, axis=0) X_norm = (X - X_min) / (X_max - X_min) X_mean = np.mean(X_norm, axis=0) X_std = np.std(X_norm, axis=0) X_std = np.where(X_std==0, 1, X_std) X_std = X_std.reshape(1, -1) X_std = np.repeat(X_std, X.shape[0], axis=0) X_std = (X_norm - X_mean) / X_std return X_norm, X_std |
方法三:
首先, 使用sklearn库中的MinMaxScaler函数对数据进行归一化. 这将所有数据缩放到0-1之间. 然后, 使用sklearn库中的StandardScaler函数对数据进行标准化. 这将所有数据缩放到均值为0, 标准差为1的范围内.
from sklearn.preprocessing import MinMaxScaler, StandardScaler scaler_minmax = MinMaxScaler() scaler_standard = StandardScaler() X_minmax = scaler_minmax.fit_transform(X) X_standard = scaler_standard.fit_transform(X) |
然后, 使用matplotlib绘制归一化和标准化的效果:
plt.figure(figsize=(12, 6)) plt.subplot(1, 2, 1) plt.hist(X_minmax) plt.title('MinMaxScaler') plt.subplot(1, 2, 2) plt.hist(X_standard) plt.title('StandardScaler') plt.show() |
归一化和标准化的效果如图
对数值进行归一化和标准化主要是为了保证模型对异常数据的鲁棒性,使其更加稳定,降低过拟合风险。
标签不平衡
样本不均衡情况
0代表未逾期用户,1代表逾期用户。
在总共148077条数据中,有144012条属于未逾期用户,4065条属于逾期用户,比例达到35.4:1,样本极其不均衡。
# 统计标签类别的不平衡情况 target_counts = data['target'].value_counts() # 可视化标签类别的分布情况 import matplotlib.pyplot as plt plt.bar(target_counts.index, target_counts.values) plt.xlabel('Target') plt.ylabel('Counts') plt.title('Target Distribution') plt.show() target_counts |
上采样缓解不平衡情况
使用sklearn中的SMOTE类对数据集进行上采样:
from imblearn.over_sampling import SMOTE sm = SMOTE(random_state=42) X_res, y_res = sm.fit_resample(X, y) |
查看上采样后的标签类别分布:
pd.Series(y_res).value_counts() |
变量筛选
对过滤式, 包裹式, 嵌入式三种特征选择算法, 利用标准化后的特征进行特征选择. 并比较三种特征选择算法的性能.
使用了SelectKBest进行过滤式特征选择,使用了RFE进行包裹式特征选择,使用了SelectFromModel进行嵌入式特征选择。在嵌入式特征选择中,使用了带L1正则化的逻辑回归模型,并设置max_features参数为50,表示要选择的特征数为50。
过滤式特征选择
过滤式特征选择中单变量特征选择, 分别单独的计算每个变量的统计指标,根据该指标来判断哪些特征重要,剔除那些不重要的特征。
SelectKBest 移除得分前 k 名以外的所有特征(取top k).
selector_filter = SelectKBest(f_classif, k=5) X_train_filter = selector_filter.fit_transform(X_train_std, y_train) X_test_filter = selector_filter.transform(X_test_std) |
包裹式特征选择
包裹式特征选择中递归特征消除 (Recursive Feature Elimination)使用一个基模型来进行多轮训练,每轮训练后,移除若干权值系数的特征,再基于新的特征集进行下一轮训练。
对特征含有权重的预测模型(例如,线性模型对应参数coefficients),RFE通过递归减少考察的特征集规模来选择特征。首先,预测模型在原始特征上训练,每个特征指定一个权重。之后,那些拥有最小绝对值权重的特征被踢出特征集。如此往复递归,直至剩余的特征数量达到所需的特征数量。
clf_wrapper = RandomForestClassifier(n_estimators=10, random_state=42) selector_wrapper = RFE(clf_wrapper, n_features_to_select=5, step=1) X_train_wrapper = selector_wrapper.fit_transform(X_train_std, y_train) X_test_wrapper = selector_wrapper.transform(X_test_std) |
嵌入式特征选择
嵌入式特征选择是将特征选择过程与模型训练过程融合在一起,通过优化模型的目标函数来进行特征选择。嵌入式特征选择基于机器学习模型的方法。有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中,例如回归模型,SVM,决策树,随机森林等等。
使用L1范数作为惩罚项的线性模型(Linear models)会得到稀疏解:大部分特征对应的系数为0。当希望减少特征的维度以用于其它分类器时,可以通过 feature_selection.SelectFromModel 来选择不为0的系数。特别指出,常用于此目的的稀疏预测模型有 linear_model.Lasso(lasso回归), linear_model.LogisticRegression(逻辑回归)和 svm.LinearSVC(SVM支持向量机)
对于SVM和逻辑回归,参数C控制稀疏性:C越小,被选中的特征越少。对于Lasso,参数alpha越大,被选中的特征越少。
本文使用了带L1正则化的逻辑回归模型进行嵌入式特征选择。
使用逻辑回归模型对标准化后的训练数据进行拟合,然后使用SelectFromModel类选择特征。SelectFromModel类是一个元变换器(meta-transformer),它可以将任何带有coef_或feature_importances_属性的基本估计器转换为特征选择器。本文将拟合后的逻辑回归模型作为基本估计器,并将prefit参数设置为True,表示已经在训练数据上拟合好了模型。然后,将max_features参数设置为50,表示要选择的特征数为50。
在选择特征的过程中,SelectFromModel类会使用逻辑回归模型的coef_属性(也就是系数)来衡量每个特征的重要性。具体来说,如果某个特征的系数绝对值小于一个阈值(这里是1e-5),则认为该特征不重要,将其舍弃。最后使用transform方法将选择后的特征应用到训练数据和测试数据中。
clf_embedded = LogisticRegression(penalty='l1', solver='liblinear') clf_embedded.fit(X_train, y_train) selector_embedded = SelectFromModel(clf_embedded, prefit=True, max_features=5) X_train_embedded = selector_embedded.transform(X_train_std) X_test_embedded = selector_embedded.transform(X_test_std) |
性能评估
回归模型对三种特征选择后的数据进行训练,并计算了测试准确率。
clf = LogisticRegression() clf.fit(X_train_filter, y_train) score_filter = clf.score(X_test_filter, y_test) clf.fit(X_train_wrapper, y_train) score_wrapper = clf.score(X_test_wrapper, y_test) clf.fit(X_train_embedded, y_train) score_embedded = clf.score(X_test_embedded, y_test) print('过滤式特征选择的测试准确率:', score_filter) print('包裹式特征选择的测试准确率:', score_wrapper) print('嵌入式特征选择的测试准确率:', score_embedded) |
可以看出, 过滤式特征选择在本文使用的信用数据集的效果最好.
故使用过滤式特征选择得到的特征.
X_train=X_train_filter X_test=X_test_filter |
只使用单一特征选择算法的版本:
使用scikit-learn库中的特征选择算法SelectKBest类进行特征选择. 以确定哪些变量可以被排除,以减少模型的复杂性.
from sklearn.feature_selection import SelectKBest selector = SelectKBest(k=50) X_new = selector.fit_transform(X_res, y_res) # 查看选择的特征: selected_features = X_res.columns[selector.get_support()] X_new=X_res[selected_features] X_new |
计算特征的重要度
使用sklearn中的ExtraTreesClassifier类计算特征的重要度
from sklearn.ensemble import ExtraTreesClassifier import numpy as np model = ExtraTreesClassifier() model.fit(X_new, y_res) importances = model.feature_importances_ |
作图展示特征的重要度排序
对特征的重要度从大到小排序:
indices = np.argsort(importances)[::-1] |
然后, 使用matplotlib绘制特征的重要度排序:
plt.figure(figsize=(12, 6)) plt.bar(X_new.columns[indices], importances[indices]) plt.xticks(rotation=90) plt.title('Feature Importance') plt.show() |
绘制热力图展示变量间的相关度
import seaborn as sns # 绘制热力图 # sns.heatmap(X_new.corr(), annot=True) sns.heatmap(X_new.corr()) plt.show() |
我们用热力图来表示不同变量之间的关系,单元格颜色越浅,代表该单元格交叉的两个变量相关性越强。注意, 只能表示线性相关程度
模型选择
算法选择
对3种性能分类模型进行模型选择.
使用sklearn中的KNeighborsClassifier, DecisionTreeClassifier和LogisticRegression类进行模型选择.
使用K折交叉验证来评估模型性能, 使用模型对训练集和测试集进行预测.
使用sklearn中的accuracy_score函数来评估模型性能.
from sklearn.neighbors import KNeighborsClassifier from sklearn.tree import DecisionTreeClassifier from sklearn.linear_model import LogisticRegression from sklearn import svm from sklearn.model_selection import KFold from sklearn.metrics import accuracy_score knn = KNeighborsClassifier() dt = DecisionTreeClassifier() lr = LogisticRegression() svc = svm.SVC() for model in models: model.fit(X_train, y_train) y_pred = model.predict(X_test) # print(model.score(X_new, y_res)) accuracy = accuracy_score(y_test, y_pred) print(type(model).__name__,':', accuracy) kf = KFold(n_splits=3) for train_index, test_index in kf.split(X_new): X_train, X_test = X_new[train_index], X_new[test_index] y_train, y_test = y_res[train_index], y_res[test_index] for model in models: model.fit(X_train, y_train) y_pred = model.predict(X_test) accuracy = accuracy_score(y_test, y_pred) print(type(model).__name__,':', accuracy) |
得到几个模型评分分别为
KNeighborsClassifier : 0.9123860775974307
DecisionTreeClassifier : 0.9699678847322281
LogisticRegression : 0.7200937418626855
可以看出决策树的模型效果最好, 准确率达到了0.9699678847322281
最佳模型参数选择
使用sklearn中的GridSearchCV类进行模型选择:
# 导入GridSearchCV from sklearn.model_selection import GridSearchCV # 定义参数网格 param_grid = 'max_depth': [3, 5, 7], 'min_samples_split': [2, 4, 6], 'min_samples_leaf': [1, 3, 5]
clf = DecisionTreeClassifier() # 创建GridSearchCV实例 grid_search = GridSearchCV(clf, param_ 以上是关于机器学习实验笔记-基于信用卡数据建立行为评分模型的机器学习方法的主要内容,如果未能解决你的问题,请参考以下文章 基于Python的信用评分卡模型分析-give me some credit数据集AUC 0.93 |