賽題鏈接:
https://challenge.xfyun.cn/topic/info?type=telecom-customer&ch=ds22-dw-zs03-dl
隨著市場(chǎng)飽和度的上升,電信運(yùn)營(yíng)商的競(jìng)爭(zhēng)也越來越激烈,電信運(yùn)營(yíng)商亟待解決減少用戶流失,延長(zhǎng)用戶生命周期的問題。對(duì)于客戶流失率而言,每增加5%,利潤(rùn)就可能隨之降低25%-85%。因此,如何減少電信用戶流失的分析與預(yù)測(cè)至關(guān)重要。
鑒于此,運(yùn)營(yíng)商會(huì)經(jīng)常設(shè)有客戶服務(wù)部門,該部門的職能主要是做好客戶流失分析,贏回高概率流失的客戶,降低客戶流失率。某電信機(jī)構(gòu)的客戶存在大量流失情況,導(dǎo)致該機(jī)構(gòu)的用戶量急速下降。面對(duì)如此頭疼的問題,該機(jī)構(gòu)將部分客戶數(shù)據(jù)開放,誠(chéng)邀大家?guī)椭麄兘⒘魇ьA(yù)測(cè)模型來預(yù)測(cè)可能流失的客戶。
不難看出,這個(gè)賽題做的是客戶流失預(yù)測(cè),參賽選手需要建立模型,來預(yù)測(cè)客戶是否會(huì)流失。
首先是不需要進(jìn)行數(shù)據(jù)處理的,數(shù)據(jù)本身不包含缺失值、異常值等
特征工程
主要是針對(duì)重要度非常高的特征進(jìn)行處理,這里主要是將兩個(gè)相關(guān)性較高的特征進(jìn)行乘除處理得到新的特征
部分補(bǔ)充特征如下:
data['每月平均每分鐘費(fèi)用'] = data['平均月費(fèi)用']/(data['每月平均使用分鐘數(shù)']+0.1)
data['每月平均超額每分鐘費(fèi)用'] = data['平均超額費(fèi)用']/(data['平均超額使用分鐘數(shù)']+0.1)
data['過去六個(gè)月每月平均每分鐘費(fèi)用'] = data['過去六個(gè)月的平均月費(fèi)用']/(data['過去六個(gè)月的平均每月使用分鐘數(shù)']+0.1)
data['過去六個(gè)月每次通話平均使用分鐘數(shù)'] = data['過去六個(gè)月的平均每月使用分鐘數(shù)']/(data['過去六個(gè)月的平均每月通話次數(shù)']+0.1)
data['過去六個(gè)月每月平均每次通話使用費(fèi)用'] = data['過去六個(gè)月的平均月費(fèi)用']/(data['過去六個(gè)月的平均每月通話次數(shù)']+0.1)
#涉及特征構(gòu)造的特征
'過去三個(gè)月的平均每月使用分鐘數(shù)''過去三個(gè)月的平均每月通話次數(shù)' '過去三個(gè)月的平均月費(fèi)用'
'過去六個(gè)月的平均每月使用分鐘數(shù)''過去六個(gè)月的平均每月通話次數(shù)''過去六個(gè)月的平均月費(fèi)用'
'客戶生命周期內(nèi)的總通話次數(shù)''客戶生命周期內(nèi)的總使用分鐘數(shù)''客戶生命周期內(nèi)的總費(fèi)用'
'計(jì)費(fèi)調(diào)整后的總分鐘數(shù)''計(jì)費(fèi)調(diào)整后的呼叫總數(shù)''計(jì)費(fèi)調(diào)整后的總費(fèi)用'
'當(dāng)前手機(jī)價(jià)格''平均月費(fèi)用'
針對(duì)上面每一行的特征進(jìn)行乘積處理得到新的特征。
將連續(xù)變量離散化,合并成較少的狀態(tài)。特征離散化后,模型會(huì)更穩(wěn)定,降低了模型過擬合的風(fēng)險(xiǎn)
#分箱特征
feature_col = ['在職總月數(shù)','當(dāng)前手機(jī)價(jià)格','當(dāng)前設(shè)備使用天數(shù)','當(dāng)月使用分鐘數(shù)與前三個(gè)月平均值的百分比變化','每月平均每分鐘費(fèi)用','計(jì)費(fèi)調(diào)整后的總費(fèi)用','每月平均使用分鐘數(shù)','客戶生命周期內(nèi)的總費(fèi)用','客戶生命周期內(nèi)的總使用分鐘數(shù)','計(jì)費(fèi)調(diào)整后的總分鐘數(shù)','計(jì)費(fèi)調(diào)整后的呼叫總數(shù)','客戶生命周期內(nèi)的總通話次數(shù)','過去三個(gè)月每次通話平均使用分鐘數(shù)','客戶生命周期內(nèi)的平均每月使用分鐘數(shù)','過去三個(gè)月每月平均每分鐘費(fèi)用','平均未接語音呼叫數(shù)','使用高峰語音通話的平均不完整分鐘數(shù)']
下面是采用的兩種分箱方法
###等頻分箱
labels_col = [i for i in range(5)]
for col in feature_col:
data_train[col+"_bin"]=pd.cut(data_train[col],5,labels=labels_col)
還可以采用卡方分箱
import scorecardpy as sc
df = pd.concat([data_train['每月平均每分鐘費(fèi)用'],data_train['是否流失']],axis=1)
bins = sc.woebin(df,y='是否流失',method='chimerge')
bins
''''''
{'每月平均每分鐘費(fèi)用': variable bin count count_distr \
0 每月平均每分鐘費(fèi)用 [-inf,0.1) 46477 0.309847
1 每月平均每分鐘費(fèi)用 [0.1,0.20000000000000004) 55120 0.367467
2 每月平均每分鐘費(fèi)用 [0.20000000000000004,0.30000000000000004) 17830 0.118867
3 每月平均每分鐘費(fèi)用 [0.30000000000000004,0.8) 18856 0.125707
4 每月平均每分鐘費(fèi)用 [0.8,inf) 11717 0.078113
good bad badprob woe bin_iv total_iv breaks \
0 25512 20965 0.451083 -0.197415 0.012037 0.030512 0.1
1 27711 27409 0.497261 -0.012078 0.000054 0.030512 0.20000000000000004
2 8517 9313 0.522322 0.088227 0.000925 0.030512 0.30000000000000004
3 8563 10293 0.545874 0.182893 0.004193 0.030512 0.8
4 4655 7062 0.602714 0.415666 0.013304 0.030512 inf
is_special_values
0 False
1 False
2 False
3 False
4 False }
''''''
#得到分箱結(jié)果為
bins=[-1,0.1,0.2,0.3,0.8]
模型選取
前期,我一直是用的LightGBM,因?yàn)長(zhǎng)ightGBM的運(yùn)行速率比較快
后期,我開始嘗試其他的XGBoost/CatBoost等模型,但是發(fā)現(xiàn)CatBoost效果都不是很好,就沒有深入往下去鉆了
模型調(diào)參可以采用網(wǎng)格調(diào)參和貝葉斯調(diào)參
#xgb模型
from sklearn.model_selection import KFold
from sklearn.metrics import roc_auc_score
cv_scores = []
kfold = KFold(n_splits=5, shuffle=True, random_state=828)
# oof_preds = np.zeros((X_train.shape[0],))
# test_preds = np.zeros((X_test.shape[0],))
for fold, (train_index, val_index) in enumerate(kfold.split(X_train, y_train)):
X_tr, X_val, y_tr, y_val = X_train.iloc[train_index],X_train.iloc[val_index],y_train.iloc[train_index],y_train.iloc[val_index]
dtrain = xgb.DMatrix(X_tr, y_tr)
dvalid = xgb.DMatrix(X_val, y_val)
dtest = xgb.DMatrix(X_test)
params={
'booster':'gbtree',
'objective': 'binary:logistic',
'eval_metric': 'auc',
'max_depth': 20,
'subsample':1,
'min_child_weight': 0.85,
'colsample_bytree':0.5,
'learning_rate': 0.01,
'n_estimators': 5000,
'seed': 28
}
watchlist = [(dtrain, 'train'), (dvalid, 'test')]
gbm = xgb.train(params,
dtrain,
num_boost_round=2000,
evals=watchlist,
verbose_eval=100,
early_stopping_rounds=200)
val_pred= gbm.predict(dvalid)
cv_scores.append(roc_auc_score(y_val, val_pred))
print(cv_scores)
#lgb模型
folds = 10
seed = 2022
kf = StratifiedKFold(n_splits=folds, shuffle=True, random_state=seed)
train = np.zeros(train_x.shape[0])
test = np.zeros(test_x.shape[0])
cv_scores = []
for i, (train_index, valid_index) in enumerate(kf.split(train_x, train_y)):
print('************************************ {} ************************************'.format(str(i + 1)))
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
trn_x, trn_y, val_x, val_y = train_x.iloc[train_index], train_y[train_index], train_x.iloc[valid_index], train_y[valid_index]
train_matrix = clf.Dataset(trn_x, label=trn_y)
valid_matrix = clf.Dataset(val_x, label=val_y)
params = {
'boosting_type': 'gbdt',
'objective': 'binary',
'metric': 'auc',
'seed': 2022,
'n_jobs': -1,
'min_child_weight': 0.5,
'num_leaves':100,
'lambda_l2': 0.3,
'feature_fraction': 0.9,
'bagging_fraction': 0.8,
'bagging_freq': 5,
'learning_rate': 0.02
}
model = clf.train(params,
train_matrix,
90000,
valid_sets=[train_matrix, valid_matrix],
categorical_feature=[],
verbose_eval=3000,
early_stopping_rounds=10000)
val_pred = model.predict(val_x, num_iteration=model.best_iteration)
test_pred = model.predict(test_x, num_iteration=model.best_iteration)
print(list(sorted(zip(features, model.feature_importance("gain")), key=lambda x: x[1], reverse=True))[:20])
train[valid_index] = val_pred
test = test_pred / kf.n_splits
cv_scores.append(roc_auc_score(val_y, val_pred))
print(cv_scores)
模型融合
最后后選定了兩個(gè)模型來融合,一個(gè)是LightGBM,一個(gè)是XGBoost),然后,直接按預(yù)測(cè)概率加權(quán)融合的話效果有一點(diǎn)提升的。
lgb_test = pd.read_csv('../code/lgb.csv')
xgb_test = pd.read_csv('../code/xgb.csv')
oof_preds_xgb=xgb_test['是否流失']
oof_preds_lgb=lgb_test['是否流失']
df_oof_res = pd.DataFrame({'客戶ID': test['客戶ID'],
'oof_preds_xgb': oof_preds_xgb,
'oof_preds_lgb': oof_preds_lgb
})
# 模型融合
df_oof_res['xgb_rank'] = df_oof_res['oof_preds_xgb'].rank(pct=True)
df_oof_res['lgb_rank'] = df_oof_res['oof_preds_lgb'].rank(pct=True)
df_oof_res['preds'] = 0.45 * df_oof_res['xgb_rank'] + 0.55 * df_oof_res['lgb_rank']
def gen_submit_file(df_oof_res, test_preds, save_path):
df_test_submit = df_oof_res[['客戶ID', 'preds']]
df_test_submit.columns = ['客戶ID', '是否流失']
print(f'saving result to: {save_path}')
print(df_test_submit)
df_test_submit.to_csv(save_path, index=False)
print('done!')
return df_test_submit
# 結(jié)果產(chǎn)出
df_submit = gen_submit_file(df_oof_res, df_oof_res['preds'], save_path='../prediction_result/ronghe_result.csv')
一鍵三連,一起學(xué)習(xí)??
聯(lián)系客服