Makine Öğrenmesi ile Müşteri Kaybı (CHURN) Olasılık Tahminlemesi, Bankacılık Sektöründen Örnek Veri Seti ve Python Uygulaması

Yiğit Şener
8 min readAug 3, 2020

--

Müşteri kaybı (churn) tahminlemesi şirketler için büyük önem arz etmektedir. McKinsey’nin araştırmasına göre hızlı büyüyen şirketlerin karlılığını istitkrarlı bir biçimde sürdürülebilir kılmaları için müşteri tutundurma yani düşük churn oranına sahip olmaları beklenmektedir.

Bu yazıdaki amacımız ise churn etme olasılığı bulunan müşterileri segmente ederek aşağıdaki şekilde olduğu gibi gruplara ayırmaktır. Bu gruplandırma sonrası pazarlama veya kampanya faaliyetleri konusunda dilediğiniz stratejileri geliştirebilirsiniz.

Bu yazıda örnek bankacılık datası üzerinden makine öğrenmesi modelleri kullanılarak churn tahmini (prediction) uygulaması Python programlama dili ile yapılmış olup kodların tümüne Kaggle hesabım üzerinden (link) erişebilirsiniz.

1. Problemin Anlaşılması

Bankacılık ile ilgili örnek veri setine buradan ulaşabilirsiniz. Amacımızı tanımlayalım.

Veri desenine bakarak churn edenlere göre bir makine öğrenmesi modeli oluşturup churn etme eğiliminde olanları tespit edecek algoritmanın yaratılması.

Amacımız gayet net olduğuna göre artık veri setini inceleyebiliriz.

2. Verinin Anlaşılması

Öncelikle veri setinde gelen değişkenleri tanıyalım.

  1. Customer ID: Müşteri numarası, tekil numaralardan oluşur.
  2. Surname: Müşterinin soyadı.
  3. Geography: Müşterinin ülkesi.
  4. Gender: Müşterinin cinsiyeti.
  5. Credit Score: Müşterinin KKb skorunu yansıtır.
  6. Age: Müşterinin yaşı.
  7. Tenure: Müşterinin bankadaki müşteri olma süresi/yaşı.
  8. Balance: Hesapta bulunan tutar.
  9. Number of Products: Müşterinin sahip olduğu ürün sayısı
  10. Credit Card: Müşterinin kredi kartı olup olmadığı bilgisi.
  11. Active Status: Müşterinin aktiflik durumu.
  12. Estimated Salary: Müşterinin tahminlenen maaşı.
  13. Exited (churn): Müşterinin ayrılıp ayrılmadığı

Hem veri setinin incelenmesi hem de ileri ki modelleme aşamaları için aşağıdaki kütüphaneleri Python ortamına tanıtalım.

"""Veri hazırlık kütüphaneleri"""
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split

"""Modelleme Kütüphaneleri"""
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.neural_network import MLPClassifier
from sklearn.naive_bayes import GaussianNB
import xgboost as xgb
from xgboost import XGBClassifier
from sklearn.metrics import confusion_matrix,accuracy_score

"""Model Eleme"""
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV

"""Diğer"""
import os
import warnings
from sklearn.utils.testing import ignore_warnings
from sklearn.exceptions import ConvergenceWarning
%matplotlib inline
warnings.filterwarnings("ignore", category=FutureWarning)
warnings.filterwarnings("ignore", category = ConvergenceWarning)

Veri setini Pandas kütüphanesindeki DataFrame objesine alarak ilk beş satırını inceleyelim.

# Veri setinin Yüklenmesi
dt = pd.read_csv(“../input/churn-modelling/Churn_Modelling.csv”)
# İlk beş satır
dt.head()

Müşteri numarası (CustomerID) ve soyad (surname) gibi modellemeye girmeyecek değişkenleri veri setinden çıkarıyoruz.

dt = dt.drop(columns=["RowNumber","CustomerId","Surname"])

Veri setinde 10,000 adet müşteri bulunmaktadır. Ülkelere göre incelediğimizde %50'si Fransa’da, %25'i Almanya’da ve %25'i de İspanya’da bulunmaktadır. Ayrıca %54 erkek ve %46 kadındır.

Sayısal değişkenleri betimleyici istatistiksel yöntemler ile inceleyelim.

dt.describe()

Yukarıdaki betimleyici istatistikler incelendiği zaman; kredi skor 350 ve 850 arasında olup ortalaması 652 puan seviyesindedir. Yaş ortalaması ise 36'dır. Müşterilik yaşı olan Tenure maksimum 10 yıldır. Hesapta bulunan tutarlar incelendiğinde veri setinin %25'lik bölümünde 0 tutarının olduğu görülmekte olup bu normal dağılmayan bir duruma işaret etmektedir. Müşterilerin %71'inde kredi kartı bulunmaktadır. Aktiflik oranı %52 ve ortalama maaş 100,000'dir. Churn edenlerin oranı ise %20'dir.

Eksik/Kayıp veri olup olmadığını kontrol edelim.

pd.DataFrame(dt.isnull().sum(),columns=["Count"])

Tabloda görüldüğü üzere hiçbir değişkende eksik veri bulunmamaktadır.

Exited yani churn edip etmediğini belirten değişken modelleme için bağımlı (hedef/target) değişken konumundadır. Bu yüzden diğer değişkenlerin, target ile olan ilişkini daha iyi gözlemleyebilmek adına aşağıdaki grafikler üzerinden incelememizi gerçekleştirebiliriz.

# Exited (churn) -- CreditScore (Kredi skoru)
sns.violinplot( x=dt["Exited"], y=dt["CreditScore"], linewidth=5)
plt.title("Credit Score Distribution of Churn (Exited)")
plt.show()
# Exited (churn) -- Age (Yaş)
sns.violinplot( x=dt["Exited"], y=dt["Age"], linewidth=5)
plt.title("Age of Customers Distribution of Churn (Exited)")
plt.show()
# Exited (Churn) -- Tenure (Müşterilik Yaşı)
sns.violinplot( x=dt["Exited"], y=dt["Tenure"], linewidth=5)
plt.title("Tenure of Customers Distribution of Churn (Exited)")
plt.show()
# Exited (Churn) -- Balance (Hesap Tutar)
sns.violinplot( x=dt["Exited"], y=dt["Balance"], linewidth=5)
plt.title("Balance of Customers Distribution of Churn (Exited)")
plt.show()

Balance değişkeninde normal olmayan bir dağılım gözlemlendiği için boxplot üzerinden yeniden inceliyoruz.

# Balance boxplot
dt[["Balance"]].boxplot()
# Exited (Churn) -- NumOfProducts (Ürün Sayısı)
sns.violinplot( x=dt["Exited"], y=dt["NumOfProducts"], linewidth=5)
plt.title("Number of Products of Customers Distribution of Churn (Exited)")
plt.show()
# Exited (Churn) -- EstimatedSalary (Maaş)
sns.violinplot( x=dt["Exited"], y=dt["EstimatedSalary"], linewidth=5)
plt.title("Estimated Salary of Customers Distribution of Churn (Exited)")
plt.show()

Yukarıdaki şekiller altı sürekli değişken ile hedef değişken arasındaki ilişkiyi violin grafiği şeklinde göstermektedir. Balance, Tenure, Estimated Salary ve Credit Score Churn sonucu için düzensiz görünmektedir. Ayrıca müşterilerin ayrılmadan önce ürünlerini azalttığı grafiğe bakarak yorumlanabilir.

Değişkenler arasındaki korelasyon ilişkisini inceleyeceğimiz kodu yazalım.

# Korelasyon Matrisi
correlationColumns = dt[["CreditScore","Age","Tenure"
,"Balance","NumOfProducts","EstimatedSalary"]]
sns.set()
corr = correlationColumns.corr()
ax = sns.heatmap(corr
,center=0
,annot=True
,linewidths=.2
,cmap="YlGnBu")
plt.show()

Matris incelendiği zaman Balance ve Ürün sayısı (NumodProduct) arasında arasında ters yönlü bir korelasyon görülmektedir

3. Verinin Modellemeye Hazırlanması

Makine öğrenim süreci öncesi veriyi belirli bir kıvama getirmemiz gerekiyor. Öncelikle bağımlı (target) ve bağımsız (predictors) değişkenleri birbirinden ayıralım.

# predictors and target (exited - churn)
predictors = dt.iloc[:,0:10]
target = dt.iloc[:,10:]

Makine öğreniminde algoritmalar, değişkenlerde yer alan değerleri sayısal olarak görmelidir. Bu yüzden Gender ve Geography değişkenlerinde yer alan karakterleri (string) rakamlara çeviriyoruz.

# erkek = 1, kadın = 0
predictors['isMale'] = predictors['Gender'].map({'Male':1, 'Female':0})
# Geography one shot encoder
predictors[['France', 'Germany', 'Spain']] = pd.get_dummies(predictors['Geography'])
# Kullanıma gerek duyulmayan kolonlar siliniyor.
predictors = predictors.drop(columns=['Gender','Geography','Spain'])

Model veri hazırlığının bir diğer aşaması da veri dönüştürme işlemidir (transformation). Veri setinde yer alan ve skalası değişkenlik gösteren Balance, Credit Score ve Estimated Salary değişkenlerinin değerleri 1 ve 0 arasına indirgenmesi için normalizasyon işlemi uygulanır.

# Normalizasyon formülü
normalization = lambda x:(x-x.min()) / (x.max()-x.min())
# Uygulama
transformColumns = predictors[["Balance","EstimatedSalary","CreditScore"]]
predictors[["Balance","EstimatedSalary","CreditScore"]] = normalization(transformColumns)

Model performansını ölçümlemek amacıyla veri setini train ve test olarak iki parçaya ayırıyoruz.

# Train and test splitting
x_train,x_test,y_train,y_test = train_test_split(predictors,target,test_size=0.25, random_state=0)
pd.DataFrame({"Train Row Count":[x_train.shape[0],y_train.shape[0]],
"Test Row Count":[x_test.shape[0],y_test.shape[0]]},
index=["X (Predictors)","Y (Target)"])

Tüm bu hazırlık çalışmalarından sonra artık verimiz makine öğrenimi modellemesi için uygun kıvama geldiğini söyleyebiliriz.

4. Makine Öğrenme Modellerinin Eğitilmesi

Veri ön hazırlığı için modellemeye uygun hale getirilen data için aşağıdaki sınıflandırma algoritmaları denenmiştir. Her bir denen algoritma için test veri sinden gelen data ile karşılaştırılıp doğruluk (accuracy) oranı bir tabloya yazılmıştır.

# Karar Ağacı - Decision Tree
dtc = DecisionTreeClassifier()
dtc.fit(x_train,y_train)
y_pred_dtc = dtc.predict(x_test)
dtc_acc = accuracy_score(y_test,y_pred_dtc)
# Lojistik Regresyon - Logistic Regression
logr = LogisticRegression()
logr.fit(x_train,y_train)
y_pred_logr = logr.predict(x_test)
logr_acc = accuracy_score(y_test,y_pred_logr)
# Naif Bayes - Naive Bayes
gnb = GaussianNB()
gnb.fit(x_train,y_train)
y_pred_gnb = gnb.predict(x_test)
gnb_acc = accuracy_score(y_test,y_pred_gnb)
# K En Yakın Komşu - K Neighbors Classifier
knn = KNeighborsClassifier( metric='minkowski')
knn.fit(x_train,y_train)
y_pred_knn = knn.predict(x_test)
knn_acc = accuracy_score(y_test,y_pred_knn)
# Rassal Ağaçlar - Random Forrest
rfc = RandomForestClassifier()
rfc.fit(x_train,y_train)
y_pred_rfc = rfc.predict(x_test)
rfc_acc = accuracy_score(y_test,y_pred_rfc)
# Sinir Ağları - Neural Network
nnc = MLPClassifier()
nnc.fit(x_train,y_train)
y_pred_nnc = nnc.predict(x_test)
nnc_acc = accuracy_score(y_test,y_pred_nnc)
# Xgboost Classifier
xgboast = xgb.XGBClassifier()
xgboast.fit(x_train, y_train)
xgboast = xgboast.score(x_test,y_test)
# Sonuçların bir tabloya yazdırılması
pd.DataFrame({"Algorithms":["Decision Tree","Logistic Regression","Naive Bayes","K Neighbors Classifier","Random Ferest","Neural Network","Xgboost Classifier"],
"Scores":[dtc_acc, logr_acc, gnb_acc, knn_acc, rfc_acc,nnc_acc,xgboast]})

Yukarıdaki tabloda görüldüğü Churn durumunu en iyi tahminleyen algoritma Xgboost olarak görülmektedir. Önde gelen sınıflandırma algoritmalarını denedik şimdi biraz daha optimize ederek modellerimizi deneyelim.

5 . Optimizasyon

Bu adımda çapraz doğrulama (cross validation) yöntemi kullanılarak veri setindeki tüm noktalara temas edilip modelin eğitilmesi hedeflenmiştir. Her bir veri kesiti için çalışacak olan algoritmanın ortalama doğruluk oranı da hesaplanmıştır.

# Cross validation test
models = []
models.append(('LR', LogisticRegression()))
models.append(('KNN', KNeighborsClassifier()))
models.append(('CART', DecisionTreeClassifier()))
models.append(('RFC', RandomForestClassifier()))
models.append(('NB', GaussianNB()))
models.append(('SVM', SVC()))
models.append(('xgboast', XGBClassifier()))
# evaluate each model in turning kfold results
results_boxplot = []
names = []
results_mean = []
results_std = []
p,t = predictors.values, target.values.ravel()
for name, model in models:
cv_results = cross_val_score(model, p,t, cv=10)
results_boxplot.append(cv_results)
results_mean.append(cv_results.mean())
results_std.append(cv_results.std())
names.append(name)
pd.DataFrame({"Algorithm":names,"Accuracy Mean":results_mean,
"Accuracy":results_std})

Yukarıdaki kodda görüldüğü üzere her bir algoritma liste içerisine alınmıştır. Cross validation (cv) değeri 10 verilmiştir. Bunun anlamı veri setini her bir (for) döngüde 10 eşit parçaya ayırarak farklı bir kesitini test olarak algoritmaya verecektir. Böylece bir algoritma 10 farklı doğruluk oranına sahip olmuş olacaktır. Aşağıdaki tabloda çıkan doğruluk (accuracy) oranlarının ortalaması ile standart sapmalarına bir bakalım.

Çıkan tablodan görüldüğü üzere XGBoost algoritması yüksek doğruluk oranı ve nispeten küçük görünen standart sapma ile başarılı duruyor. Şimdi sonuçlara bir de box plot üzerinden bakalım.

# boxplot ile algoritmaların karşılaştırılması
fig = plt.figure()
fig.suptitle('Algorithm Comparison')
ax = fig.add_subplot(111)
plt.boxplot(results_boxplot)
ax.set_xticklabels(names)
plt.show()

Boxplot üzerinde de görüldüğü üzere bazı algoritmaların outlier seviyesinde doğruluk oranları çıkmış olup XGBoost algoritmasında ise normal bir dağılım görülmektedir.

Model algoritmasını belirlediğimize göre XGBoost sınıflandırma algoritmasının parametrelerini optimize ederek en uygun değerleri belirleyelim. Bunun için Grid Search yönteminden yararlanacağız.

# Grid Seach XGboast
params = {
'min_child_weight': [1, 2, 3],
'gamma': [1.9, 2, 2.1, 2.2],
'subsample': [0.4,0.5,0.6],
'colsample_bytree': [0.6, 0.8, 1.0],
'max_depth': [3,4,5]
}
gd_sr = GridSearchCV(estimator=XGBClassifier(),
param_grid=params,
scoring='accuracy',
cv=5,
)
gd_sr.fit(predictors, target.values.ravel())
best_parameters = gd_sr.best_params_
pd.DataFrame(best_parameters.values(),best_parameters.keys(),columns=["Best Parameters"])

Tabloda görüldüğü üzere XGBoost algoritmasına ait churn tahmini için en optimum parametre değerlerini bulmuş olduk.

Değişkenlerin modele etkisini belirleyen feature importance değerlerine bakalım.

importances_xgboast = xgboost.feature_importances_
indices_xgboast = np.argsort(importances_xgboast)
features = predictors.columns
plt.title('Xgboost Classifier Feature Importances')
plt.barh(range(len(indices_xgboast)), importances_xgboast[indices_xgboast], color='b', align='center')
plt.yticks(range(len(indices_xgboast)), [features[i] for i in indices_xgboast])
plt.xlabel('Relative Importance')
plt.show()

Yazının ilk girişinde bahsedilen churn olasılık değerlerini aşağıdaki kod ile hesaplayabiliriz. Burada array/dizi içinde bulunan her bir değer bir müşterinin churn olasılığını yansıtmaktadır.

xgboast.predict_proba(x_test)array([[0.7257912 , 0.27420878],
[0.751231 , 0.24876897],
[0.7904643 , 0.20953572],
...,
[0.74283135, 0.25716865],
[0.8726263 , 0.12737373],
[0.9167795 , 0.08322046]], dtype=float32)

Sonuç

Eğer bir firma karını maksimize etmek yolunda müşteri stratejileri oluşturuyorsa burada churn konusunda detaylı ve uçtan uca bir çalışma yapması gerekiyor. Tabi burada iş sadece makine öğrenmesi ile bitmiyor. Tahmin sonuçlarına göre farklı aksiyonların işletilmesi ve bu süreçlerin iyi bir şekilde gözlemlenmesi sağlıklı bir işleyişi doğuracaktır.

Aşağıdaki linklerde yukarıda kullandığım bazı yöntemlere ait açıklayıcı yazılarım bulunmaktadır.

--

--