1. 程式人生 > >機器學習實戰---使用者流失預測

機器學習實戰---使用者流失預測

在過去的幾年裡,隨著移動通訊裝置和4G網路的普及,移動,電信,聯通這三大通訊運營商之間的競爭愈發激烈,如何獲取新使用者?如何減少老使用者流失?成了三大運營商頭疼的問題,此次案例我們根據某個運營商的真實資料,通過資料分析和建立使用者流失預測模型,來理解使用者流失的一些重要規律。

本次案例分為這麼幾個部分:

  • 資料匯入及預處理
  • 資料探索性分析
  • 資料建模(採用決策樹模型)
  • 模型結果評價
  • 決策樹視覺化
  • 總結和思考

【資料匯入及預處理】

1.資料來源:

本次案例資料取自“狗熊會”公眾號上分享的手機客戶流失資料,大家有興趣可以關注一下這個公眾號,上面有很多關於資料分析和建模的行業案例,並提供一些行業資料下載練習。

2.資料匯入:

下載的檔案為 ' CustomerSurvival.csv ',我們將資料匯入到Python中:

# 匯入分析用到的模組
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('ggplot')
import seaborn as sns
sns.set_style('darkgrid')
sns.set_palette('muted')
# 匯入csv檔案
df = pd.read_csv('C:/Users/Administrator/Desktop/CustomerSurvival.csv',encoding='utf-8')
df.head()

3. 資料理解及變數解釋

先看一下資料的概況:

df.info()

一共10個欄位,且都是數值型變數,4975行資料,為便於分析,我們將中文欄位改為英文:

df.columns = ['id','pack_type','extra_time','extra_flow','pack_change',
             'contract','asso_pur','group_user','use_month','loss']
df.info()

變數含義及解釋:

  • id -- 使用者的唯一標識
  • pack_type -- 使用者的月套餐的金額,1為96元以下,2為96到225元,3為225元以上
  • extra_time -- 使用者在使用期間的每月額外通話時長,這部分需要使用者額外交費。數值是每月的額外通話時長的平均值,單位:分鐘
  • extra_flow -- 使用者在使用期間的每月額外流量,這部分需要使用者額外交費。數值是每月的額外流量的平均值,單位:兆
  • pack_change -- 是否曾經改變過套餐金額,1=是,0=否
  • contract -- 使用者是否與聯通簽訂過服務合約,1=是,0=否
  • asso_pur -- 使用者在使用聯通移動服務過程中是否還同時辦理其他業務,1=同時辦理一項其他業務,2=同時辦理兩項其他業務,0=沒有辦理其他業務
  • group_use -- 使用者辦理的是否是集團業務,相比個人業務,集體辦理的號碼在集團內撥打有一定優惠。1=是,0=否
  • use_month -- 截止到觀測期結束(2012.1-2014.1),使用者使用聯通服務的時間長短,單位:月
  • loss -- 在25個月的觀測期內,使用者是否已經流失。1=是,0=否

---因變數是 'loss',是否流失,也是我們預測的目標值

---自變數分為三類:

# 連續型變數:extra_time,extra_flow, use_month

# 二元分類變數:pack_change,contract, group_use

# 多元分類變數:pack_type,asso_pur

4. 資料的預處理

先看一下各個欄位是否有缺失:

df.isnull().any()

每個欄位都沒有缺失值,資料很完整,可以不作缺失值處理。

再判斷一下使用者的id是否唯一:

len(df.id.unique())

輸出為:4975,與行數目一致,id沒有重複值,每條記錄都是唯一的。

【資料的探索性分析】

1. 資料的描述性統計:

df.describe()

可以看到extra_time和extra_flow有正負值,正數表示使用者有額外的通話時長和流量,負數為使用者在月底時剩餘的套餐時長和流量。從四分位數中可看出超過一半的使用者有額外通話時間,流量的話只有小部分使用者超額使用了。另外其他的分型別變數在描述統計上並未發現有異常的地方。

在這裡特別注意下use_month這個變數,資料的觀測區間為2012.1-2014.1,一共25個月,且案例中關於流失的定義為:

超過一個月沒有使用行為(包括通話,使用流量)的使用者判定為流失

在資料集中use_month小於25個月的基本都是流失狀態,所以這個變數對於流失的預測並沒有什麼關鍵作用,後續匯入模型時需剔除這個變數。

2. 變數的分佈

首先看一下兩個連續型變數:extra_time和extra_flow的資料分佈:

plt.figure(figsize = (10,5))
plt.subplot(121)
df.extra_time.hist(bins = 30)
plt.subplot(122)
df.extra_flow.hist(bins = 30)

extra_time呈現的是右偏分佈,extra_flow近似服從正態分佈,與描述統計中的情況大致吻合

接下來看看分型別變數的分佈:

fig,axes = plt.subplots(nrows = 2,ncols = 3, figsize = (10,6))
sns.countplot(x = 'pack_type',data = df,ax=axes[0,0])
sns.countplot(x = 'pack_change',data = df,ax=axes[0,1])
sns.countplot(x = 'contract',data = df,ax=axes[0,2])
sns.countplot(x = 'asso_pur',data = df,ax=axes[1,0])
sns.countplot(x = 'group_user',data = df,ax=axes[1,1])
sns.countplot(x = 'loss',data = df,ax=axes[1,2])

可以看到pack_type, pack_change, asso_pur的型別分佈非常不均衡,例如asso_pur,辦理過套餐外業務的使用者數量極少,導致樣本缺乏足夠的代表性,可能會對模型的最終結果產生一定的影響。

3. 自變數與因變數之間的關係:

對於extra_time和extra_flow繪製散點圖觀察:

plt.figure(figsize = (10,6))
df.plot.scatter(x='extra_time',y='loss')
df.plot.scatter(x='extra_flow',y='loss')

從散點圖上似乎感覺兩個自變數與是否流失並無關係,為了更好的展示其相關性,我們對extra_time和extra_flow進行分箱處理,再繪製條形圖:

# 增加分箱後的兩個欄位
bin1 = [-3000,-2000,-500,0,500,2000,3000,5000]
df['time_label'] = pd.cut(df.extra_time,bins = bin1)
bin2 = [-3000,-2000,-500,0,500,2000,3000]
df['flow_label'] = pd.cut(df.extra_flow,bins = bin2)
# 觀察一下分箱後的資料分佈
time_amount = df.groupby('time_label').id.count().sort_values().reset_index()
time_amount['amount_cumsum'] = time_amount.id.cumsum()
time_amount['prop'] = time_amount.apply(lambda x:x.amount_cumsum/4975,axis =1)
flow_amount = df.groupby('flow_label').id.count().sort_values().reset_index()
flow_amount['amount_cumsum'] = flow_amount.id.cumsum()
flow_amount['prop'] = flow_amount.apply(lambda x:x.amount_cumsum/4975,axis=1)

---對extra_time進行累加統計,發現【-500,500】這個區間的使用者佔了80%,符合二八定律

---對extra_flow進行累加統計,發現【-500,500】佔了95%,且(-500,0】的使用者佔80%,可以說只有小部分使用者每月會超額使用流量。

繪製條形圖:

sns.countplot(x = 'time_label',hue = 'loss',data =df)
sns.countplot(x = 'flow_label',hue = 'loss',data =df)

可以明顯的看出使用者使用的通話時間和流量越多,流失概率越低,這些超額使用的使用者在使用者分類中屬於'高價值使用者',使用者粘性很高,運營商應該把重點放在這些使用者身上,採取有效的手段預防其流失。

接著看其他自變數與流失的關係:

fig,axes = plt.subplots(nrows = 2,ncols = 3, figsize = (12,8))
sns.countplot(x = 'pack_type',hue = 'loss',data =df,ax = axes[0][0])
sns.countplot(x = 'pack_change',hue = 'loss',data =df,ax = axes[0][1])
sns.countplot(x = 'contract',hue = 'loss',data =df,ax = axes[0][2])
sns.countplot(x = 'asso_pur',hue = 'loss',data =df,ax = axes[1][0])
sns.countplot(x = 'group_user',hue = 'loss',data =df,ax = axes[1][1])

初步得出以下結論:

1).套餐金額越大,使用者越不易流失,套餐金額大的使用者忠誠度也高

2).改過套餐的使用者流失的概率變小

3).簽訂過合約的流失比例較小,簽訂合約也意味著一段時間內(比如2年,3年)使用者一般都不會更換運營商號碼,可以說簽訂合約的使用者比較穩定

4).辦理過其它套餐業務的使用者因樣本量太少,後續再研究

5).集團使用者的流失率相比個人使用者低很多

最後通過相關性矩陣熱力圖觀察各變數之間的相關性:

internal_chars = ['extra_time','extra_flow','pack_type',
                 'pack_change','contract','asso_pur','group_user','loss']
corrmat = df[internal_chars].corr()
f, ax = plt.subplots(figsize=(10, 7))
plt.xticks(rotation='0')
sns.heatmap(corrmat, square=False, linewidths=.5, annot=True)

各自變數之間的相關性程度很低,排除了共線性問題。在對因變數的相關性上contract和group_user的係數相比其它變數較高,但也不是很強。

【資料建模】

因為自變數大多數為分型別,所以用決策樹的效果比較好,而且決策樹對異常值的敏感度很低,生成的結果也有很好的解釋性。

1.特徵的預處理

根據前面的探索性分析,並基於業務理解,我們決定篩選這幾個特徵進入模型:

extra_time,extra_flow,pack_type, pack_change, asso_pur

contract以及group_use,這些特徵都對是否流失有一定的影響。

對於extra_time,extra_flow這兩個連續型變數我們作資料轉換,變成二分類變數,這樣所有特徵都是統一的度量。

df['time_tranf'] = df.apply(lambda x:1 if x.extra_time>0 else 0,axis =1)
df['flow_tranf'] = df.apply(lambda x:1 if x.extra_flow>0 else 0,axis =1)
df.head()

將沒有超出套餐的通話時間和流量記為0,超出的記為1。

2. 建立自變數x, 因變數y的二維陣列:

x = df.loc[:,['pack_type','time_tranf','flow_tranf','pack_change','contract','asso_pur','group_user']]
x = np.array(x)
x
y = df.loss
y = y[:, np.newaxis]
y

3. 拆分訓練集和測試集,比例為7:3

from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size = 0.3)

4. 建立決策樹模型並擬合訓練:

from sklearn import tree
clf = tree.DecisionTreeClassifier(criterion='gini', --設定衡量的係數
                                    splitter='best', --選擇分類的策略
                                    max_depth=5, --設定樹的最大深度
                                    min_samples_split=10,--節點的最少樣本數
                                    min_samples_leaf=5 -- 葉節點的最少樣本數
                                    )
clf = clf.fit(x_train,y_train)  -- 擬合訓練

這裡我們採用決策樹中CART演算法,基於gini係數進行分類,設定樹的最大深度為5,區分一個內部節點需要的最少的樣本數為10,一個葉節點所需要的最小樣本數為5。

決策樹最大的缺點是容易出現過擬合,所以我們先看一下模型對於訓練資料和測試資料兩者的評分情況:

train_score = clf.score(x_train,y_train) # 訓練集的評分
test_score = clf.score(x_test,y_test)   # 測試集的評分
'train_score:{0},test_score:{1}'.format(train_score,test_score)

可以看到針對訓練集評分為0.874,針對測試集評分為0.867,兩者近乎相等,說明模型較好的擬合訓練集與測試集資料。

5.優化模型引數

對於決策樹來說,可調引數有max_depth,min_samples_leaf,min_samples_split,

min_impurity_split等,一般用來解決模型過擬合的問題,也是一種“前剪枝”的方法。

我們以優化max_depth為例:

# 模型的引數調優--max_depth
# 建立一個函式,使用不同的深度來訓練模型,並計算評分資料
def cv_score(d):
    clf2 = tree.DecisionTreeClassifier(max_depth=d)
    clf2 = clf2.fit(x_train,y_train)
    tr_score = clf2.score(x_train,y_train)
    cv_score = clf2.score(x_test,y_test)
    return (tr_score, cv_score)
# 構造引數範圍,在這個範圍內構造模型並計算評分
depths = range(2,15)
scores = [cv_score(d) for d in depths]
tr_scores = [s[0] for s in scores]
cv_scores = [s[1] for s in scores]
# 找出交叉驗證資料集最高評分的那個索引
best_score_index = np.argmax(cv_scores)
best_score = cv_scores[best_score_index]
best_param = depth[best_score_index]
print('best_param : {0},best_score: {1}'.format(best_param,best_score))

最優的深度為5,最佳評分為0.868,這與我們一開始設定的最大深度相同。

我們還可以將模型引數與評分的關係畫出來,更可以清楚地看到其變化規律:

plt.figure(figsize = (4,2),dpi=150)
plt.grid()
plt.xlabel('max_depth')
plt.ylabel('best_score')
plt.plot(depths, cv_scores,'.g-',label = 'cross_validation scores')
plt.plot(depths,tr_scores,'.r--',label = 'train scores')
plt.legend()

在生成的圖中可以看出當深度為5時,交叉驗證資料集的評分與訓練集的評分比較接近,且兩者的評分比較高,當深度超過5以後,倆者的差距變大,交叉驗證資料集的評分變低,出現了過擬合情況。

【模型結果評價】

可以呼叫Scikit-learn的classification_report模組,生成分析報告。

from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))

這裡說一下評價模型好壞的指標:

精確率,召回率,F1,AUC和ROC曲線,KS值。

以這個使用者流失預測模型為例,它是一個有監督的二分類模型,我們把模型應用在測試集資料後會產生一個“預測表現”和“實際表現”,形成一個混淆矩陣:

精確率 = TP/(TP+FP) :在預測為流失的使用者中,預測正確的(實際也是流失)使用者佔比

召回率 = TP/(TP+FN) : 在實際為流失的使用者中,預測正確的(預測為流失的)使用者佔比

F1值為精確率和召回率的調和均值,相當於這兩個的綜合評價指標。

通過輸出的分析報告可以得出建立的預測模型的精確率為0.87,說明在預測為流失的使用者中,實際流失的使用者佔87%,召回率也為0.87,說明實際為流失的使用者中,預測為流失的佔87%,F1值為0.87,說明模型的綜合評價還不錯。

ROC曲線和AUC值經常作為衡量一個模型擬合程度的指標,ROC曲線是對多個混淆矩陣的結果組合,每個混淆矩陣都會計算TPR(召回率)和FPR,FPR為實際為不流失的使用者中,預測為流失的佔比(FP/(FP+TN)),以FPR為x軸,TPR為y軸,就得到了ROC曲線圖:

而AUC的值為ROC曲線下面的面積,如果這個模型十分精確,則ROC曲線經過(0,1)點,且AUC的值為1,不過在實際情形中,不會出現這麼精確的模型,一般AUC的值在0.5到1之間,如果AUC=0.5或小於0.5,說明這個模型很差。

畫ROC曲線的程式碼如下(小夥伴們可以直接copy跑一下):

from sklearn import svm, datasets  
from sklearn.metrics import roc_curve, auc  # 計算roc和auc  
from sklearn import cross_validation

random_state = np.random.RandomState(0)  
n_samples, n_features = x.shape  
X = np.c_[x, random_state.randn(n_samples, 200 * n_features)]  
X_train, X_test, y_train2, y_test2 = cross_validation.train_test_split(X, y, test_size=.2,random_state=0)  
svm = svm.SVC(kernel='linear', probability=True,random_state=random_state) 
y_score = svm.fit(X_train, y_train2).decision_function(X_test)  
fpr,tpr,threshold = roc_curve(y_test, y_score) # 計算真正率和假正率  
roc_auc = auc(fpr,tpr) # 計算auc的值  

plt.figure()  
lw = 2  
plt.figure(figsize=(6,6))  
plt.plot(fpr, tpr, color='darkorange',  
         lw=lw, label='ROC curve (area = %0.2f)' % roc_auc) ###假正率為橫座標,真正率為縱座標做曲線  
plt.plot([0, 1], [0, 1], color='navy', lw=lw, linestyle='--')  
plt.xlim([0.0, 1.0])  
plt.ylim([0.0, 1.05])  
plt.xlabel('False Positive Rate')  
plt.ylabel('True Positive Rate')  
plt.title('Receiver operating characteristic example')  
plt.legend(loc="lower right")  
plt.show()  

AUC的值為0.73,模型的擬合程度還有一定的提升空間。

除此之外,在評價模型時也會用到KS值,KS =max(TPR - FPR),KS值可以反映模型的最優區分效果。

在實際業務中,選擇哪個指標作為依據是要根據應用場景的,比如使用者流失預測案例,我們會更關注召回率,模型的目的要儘可能的找全實際將流失的使用者,也就是實際流失的使用者中,模型能預測到多少,這是評價這個模型結果的關鍵指標。所以在今後模型的優化中,我們要儘可能提高TPR。

【決策樹視覺化】

決策樹最大的好處是可以生成視覺化的樹結構,有助於我們對模型的理解。

在視覺化前我們需要下載graphviz並安裝,安裝完後開啟將其bin目錄新增到電腦的環境變數中,關於如何新增環境變數大家自行百度,操作非常簡單。

接下來匯入graphviz模組,繪製樹結構並匯出到外部檔案。

from sklearn.tree import export_graphviz
with open('D:/dataset/userloss.dot','w')as f:
    f=export_graphviz(clf,
                      feature_names=['pack_type','time_tranf','flow_tranf'
                      ,'pack_change','contract','asso_pur','group_user'],
                      class_names=['loss','not loss'],
                      filled=True, rounded=True,
                      special_characters=True,
                      out_file=f
                       )

執行這個程式後會在指定的資料夾裡生成一個dot檔案,我們需要通過cmd命令進入到這個檔案所在的目錄下,然後通過‘ dot -Tpng 檔名 -o 輸出檔名’的形式進行轉化,將dot檔案轉為png的圖片格式:

最後的視覺化結果如下圖:

放大看一下:

因為之前建模的時候設定的深度為5,所以樹有5層,裡面包含了分類的特徵選擇和對應的樣本數,非常清晰直觀。

【總結和思考】

  1. 從資料的探索性分析中我們可以看出,運營商關注的重點應該放在那些高價值的使用者上,比如使用通話和流量比較多的,套餐金額比較大的這些使用者。應採取相應的運營策略預防其流失,並且可以分析使用者流失的主要原因,是優惠福利不滿意還是競爭對手在某些方面比自己有優勢。在這個通訊行業激烈競爭的時代,且手機使用者數量已基本飽和,維護老使用者比獲取新使用者更容易。
  2. 在預測模型的優化上,還有很多的改進之處,比如調整決策樹的引數,特徵的精細化篩選,或者採用多種演算法進行模型評估。而且這個資料還有很多東西值得分析挖掘,使用者的分類,使用者的生命週期分析,各變數之間的交叉分析等等,今後還需對這個專案進行多方面的改進。

今天的使用者流失預測案例就寫到這啦~~bye。。