基于决策树、随机森林的宽带客户流失预测
一、数据探索
import pandas as pd
import numpy as np
# 1: 加载数据文件,查看数据信息
df = pd.read_csv('broadband.csv')
df.head() # broadband 即可:0-离开,1-留存
CUST_ID | GENDER | AGE | TENURE | CHANNEL | AUTOPAY | ARPB_3M | CALL_PARTY_CNT | DAY_MOU | AFTERNOON_MOU | NIGHT_MOU | AVG_CALL_LENGTH | BROADBAND | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 63 | 1 | 34 | 27 | 2 | 0 | 203 | 0 | 0.0 | 0.0 | 0.0 | 3.04 | 1 |
1 | 64 | 0 | 62 | 58 | 1 | 0 | 360 | 0 | 0.0 | 1910.0 | 0.0 | 3.30 | 1 |
2 | 65 | 1 | 39 | 55 | 3 | 0 | 304 | 0 | 437.2 | 200.3 | 0.0 | 4.92 | 0 |
3 | 66 | 1 | 39 | 55 | 3 | 0 | 304 | 0 | 437.2 | 182.8 | 0.0 | 4.92 | 0 |
4 | 67 | 1 | 39 | 55 | 3 | 0 | 304 | 0 | 437.2 | 214.5 | 0.0 | 4.92 | 0 |
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1114 entries, 0 to 1113
Data columns (total 13 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 CUST_ID 1114 non-null int64
1 GENDER 1114 non-null int64
2 AGE 1114 non-null int64
3 TENURE 1114 non-null int64
4 CHANNEL 1114 non-null int64
5 AUTOPAY 1114 non-null int64
6 ARPB_3M 1114 non-null int64
7 CALL_PARTY_CNT 1114 non-null int64
8 DAY_MOU 1114 non-null float64
9 AFTERNOON_MOU 1114 non-null float64
10 NIGHT_MOU 1114 non-null float64
11 AVG_CALL_LENGTH 1114 non-null float64
12 BROADBAND 1114 non-null int64
dtypes: float64(4), int64(9)
memory usage: 113.3 KB
# 2: 列名全部换成小写
df.rename(str.lower, axis='columns', inplace=True)
df.sample() # 查看一个样本数据
cust_id | gender | age | tenure | channel | autopay | arpb_3m | call_party_cnt | day_mou | afternoon_mou | night_mou | avg_call_length | broadband | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
674 | 835 | 1 | 48 | 28 | 4 | 1 | 250 | 35 | 143.7 | 183.5 | 382.9 | 4.85 | 0 |
# 3: 查看因变量 broadband 分布情况,看是否存在不平衡
# 随机森林适合这种样本不平衡的数据
from collections import Counter
print('Broadband: ', Counter(df['broadband']))
Broadband: Counter({0: 908, 1: 206})
二、拆分测试集与训练集
y = df['broadband'] # 标签
X = df.iloc[:, 1:-1] # 客户 id 没有用,故丢弃 cust_id
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.4, random_state=123)
三、决策树建模
树模型参数:
-
1.criterion gini or entropy
-
2.splitter best or random 前者是在所有特征中找最好的切分点 后者是在部分特征中(数据量大的时候)
-
3.max_features None(所有),log2,sqrt,N 特征小于50的时候一般使用所有的
-
4.max_depth 数据少或者特征少的时候可以不管这个值,如果模型样本量多,特征也多的情况下,可以尝试限制下
-
5.min_samples_split 如果某节点的样本数少于min_samples_split,则不会继续再尝试选择最优特征来进行划分如果样本量不大,不需要管这个值。如果样本量数量级非常大,则推荐增大这个值。
-
6.min_samples_leaf 这个值限制了叶子节点最少的样本数,如果某叶子节点数目小于样本数,则会和兄弟节点一起被剪枝,如果样本量不大,不需要管这个值,大些如10W可是尝试下5
-
7.min_weight_fraction_leaf 这个值限制了叶子节点所有样本权重和的最小值,如果小于这个值,则会和兄弟节点一起被剪枝默认是0,就是不考虑权重问题。一般来说,如果我们有较多样本有缺失值,或者分类树样本的分布类别偏差很大,就会引入样本权重,这时我们就要注意这个值了。
-
8.max_leaf_nodes 通过限制最大叶子节点数,可以防止过拟合,默认是"None”,即不限制最大的叶子节点数。如果加了限制,算法会建立在最大叶子节点数内最优的决策树。如果特征不多,可以不考虑这个值,但是如果特征分成多的话,可以加以限制具体的值可以通过交叉验证得到。
-
9.class_weight 指定样本各类别的的权重,主要是为了防止训练集某些类别的样本过多导致训练的决策树过于偏向这些类别。这里可以自己指定各个样本的权重如果使用“balanced”,则算法会自己计算权重,样本量少的类别所对应的样本权重会高。
-
10.min_impurity_split 这个值限制了决策树的增长,如果某节点的不纯度(基尼系数,信息增益,均方差,绝对差)小于这个阈值则该节点不再生成子节点。即为叶子节点 。
-
n_estimators:要建立树的个数
import sklearn.tree as tree
# 1. 直接使用交叉网格搜索来优化决策树模型,边训练边优化
from sklearn.model_selection import GridSearchCV
# 2. 网格搜索参数,选择最优参数
param_grid = {'criterion': ['entropy', 'gini'], # 树的深度评估指标 信息熵 vs 基尼系数
'max_depth': [2, 3, 4, 5, 6, 7, 8], # 树的深度
'min_samples_split': [4, 8, 12, 16, 20, 24, 28]# 最小拆分的叶子样本数
}
# 3. 定义一棵树对象
clf = tree.DecisionTreeClassifier()
# 4. 传入模型,网格搜索的参数,评估指标,cv交叉验证的次数
clfcv = GridSearchCV(estimator=clf, param_grid=param_grid, scoring='roc_auc',cv=4)
# 5. 训练模型
clfcv.fit(X_train, y_train)
# 6. 使用模型来对测试集进行预测
test_result = clfcv.predict(X_test)
# 7. 模型评估
import sklearn.metrics as metrics
print("决策树准确度:")
print(metrics.classification_report(y_test,test_result))
决策树准确度:
precision recall f1-score support
0 0.88 0.98 0.93 360
1 0.83 0.44 0.58 86
accuracy 0.87 446
macro avg 0.85 0.71 0.75 446
weighted avg 0.87 0.87 0.86 446
## 定义绘制ROC曲线的函数
def plot_ROC(preds,y_test):
"""
Args:
labels : ground truth
preds : model prediction
savepath : save path
"""
fpr1, tpr1, threshold1 = metrics.roc_curve(preds, y_test) ###计算真正率和假正率
roc_auc1 = metrics.auc(fpr1, tpr1) ###计算auc的值,auc就是曲线包围的面积,越大越好
plt.figure()
lw = 2
plt.figure(figsize=(10, 10))
plt.plot(fpr1, tpr1, color='darkorange',
lw=lw, label='AUC = %0.2f' % roc_auc1) ###假正率为横坐标,真正率为纵坐标做曲线
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('1 - Specificity')
plt.ylabel('Sensitivity')
# plt.title('ROCs for Densenet')
plt.legend(loc="lower right")
plt.show()
# 8. 决策树的AUC
print("决策树 AUC:")
fpr_test, tpr_test, th_test = metrics.roc_curve(y_test, test_result)
print('AUC = %.4f' %metrics.auc(fpr_test, tpr_test))
决策树 AUC:
AUC = 0.8045
plot_ROC(test_result,y_test)
# 9. 网格搜索后的最优参数
clfcv.best_params_
{'criterion': 'entropy', 'max_depth': 5, 'min_samples_split': 4}
# 10. 将最优参数代入到模型中,重新训练、预测
clf2 = tree.DecisionTreeClassifier(criterion='entropy', max_depth=5, min_samples_split=4)
clf2.fit(X_train, y_train)
test_res2 = clf2.predict(X_test)
# 11. 绘制图形 pip3 install graphviz
import graphviz
dot_data = tree.export_graphviz(clf2, out_file=None)
graph = graphviz.Source(dot_data)
graph.render('user')# 保存为pdf格式
from PIL import Image
Image.open('user.jpg')# 转化为jpg
四、随机森林建模
# 1. 网格搜索
param_grid = {
'criterion':['entropy','gini'],# 衡量标准
'max_depth':[5, 6, 7, 8], # 每棵决策树的深度
'n_estimators':[11,13,15], # 决策树个数 - 随机森林特有参数
'max_features':[0.3,0.4,0.5], # 每棵决策树使用的变量占比 - 随机森林特有参数
'min_samples_split':[4,8,12,16] # 叶子的最小拆分样本量
}
import sklearn.ensemble as ensemble # ensemble learning: 集成学习
# 2. 随机森林算法
rfc = ensemble.RandomForestClassifier()
rfc_cv = GridSearchCV(estimator=rfc, param_grid=param_grid,
scoring='roc_auc', cv=4)
rfc_cv.fit(X_train, y_train)
# 3. 使用随机森林对测试集进行预测
predict_test = rfc_cv.predict(X_test)
print('随机森林精确度...')
print(metrics.classification_report(predict_test, y_test))
随机森林精确度...
precision recall f1-score support
0 0.97 0.89 0.93 394
1 0.48 0.79 0.59 52
accuracy 0.87 446
macro avg 0.72 0.84 0.76 446
weighted avg 0.91 0.87 0.89 446
# 4. AUC的值
print('随机森林 AUC...')
fpr_test, tpr_test, th_test = metrics.roc_curve(predict_test, y_test) # 构造 roc 曲线
print('AUC = %.4f' %metrics.auc(fpr_test, tpr_test))
随机森林 AUC...
AUC = 0.8652
plot_ROC(predict_test,y_test)
# 5. 查看最佳参数(有可能不是最优参数,仍然需要调参)
rfc_cv.best_params_
{'criterion': 'gini',
'max_depth': 8,
'max_features': 0.5,
'min_samples_split': 4,
'n_estimators': 13}
# 6. 调整决策边界,调参
param_grid = {
'criterion':['entropy','gini'],
'max_depth':[7, 8, 10, 12], # 前面的 5,6 也可以适当的去掉,反正已经没有用了
'n_estimators':[11, 13, 15, 17, 19], # 决策树个数 - 随机森林特有参数
'max_features':[0.4, 0.5, 0.6, 0.7], # 每棵决策树使用的变量占比 - 随机森林特有参数
'min_samples_split':[2, 3, 4, 8, 12, 16] # 叶子的最小拆分样本量
}
# 7. 重复上述步骤,再次训练,寻找最优参数
rfc_cv = GridSearchCV(estimator=rfc, param_grid=param_grid,
scoring='roc_auc', cv=4)
rfc_cv.fit(X_train, y_train)
# 8. 使用随机森林对测试集进行预测
predict_test = rfc_cv.predict(X_test)
print('随机森林精确度...')
print(metrics.classification_report(predict_test, y_test))
print('随机森林 AUC...')
fpr_test, tpr_test, th_test = metrics.roc_curve(predict_test, y_test) # 构造 roc 曲线
print('AUC = %.4f' %metrics.auc(fpr_test, tpr_test))
随机森林精确度...
precision recall f1-score support
0 1.00 0.88 0.94 410
1 0.42 1.00 0.59 36
accuracy 0.89 446
macro avg 0.71 0.94 0.76 446
weighted avg 0.95 0.89 0.91 446
随机森林 AUC...
AUC = 0.9358
plot_ROC(predict_test,y_test)
# 8. 查看最优参数
rfc_cv.best_params_
{'criterion': 'entropy',
'max_depth': 10,
'max_features': 0.2,
'min_samples_split': 2,
'n_estimators': 15}