Makine Öğrenimi ve Derin Öğrenme ile Müşteri Kayıp (Churn) Analizi-1

Churn analizini kabaca “bir müşterinin” “bir ürünü veya hizmeti”, “terk etme ihtimali” üzerine gerçekleştirilen analitik çalışmaların bütünü olarak tanımlayabiliriz. Amacımız müşterinin bizi terk etmeden (terk etmeye yaklaşmadan) önce bu durumun farkında olmak (hatta müşterinin kendisi de bu durumun farkında olmayabilir) ve sonrasında bir takım önleyici faaliyetlerde bulunmak.

Şekil 1. Churn analizi ile müşteri durumu grafik gösterimi

Makine öğrenimi alanında bir sınıflandırma problemi olarak da ele alınan bu analizde; kullanıcıların demografik bilgileri, geçmiş üst seviye aksiyonları, aldıkları hizmeti/ürünü kullanma sıklıkları ve süreleri, hizmeti/ürünü kullanırken yaptıkları aksiyonlar arasındaki bağlantılar vb. tonla bilginin toplanması, işlenmesi ve değerlendirilmesi gerekiyor.

Veri Seti

Makalemizde Orange isimli bir telekom firmasına ait Churn veri setini kullanacağız. Veri setini buradan indirebilirsiniz. Veri setimizde başlık (header) bilgileri bulunmuyor. Bu nedenle, 21 elemanlı columns isimli bir dizi değişkeni tanımlıyoruz ve pandas paketi read_csv fonksiyonu yardımıyla veri setimizi oluşturuyoruz. Son olarak da ünlü shape fonksiyonunu kullanarak veri setimize bir göz atıyoruz.

import numpy as np
#sonuçların yeniden üretilebilir olmasını amaçlıyoruz
np.random.seed(1)
from sklearn import cross_validation
from sklearn import preprocessing
import pandas as pd
columns = [
'state',
'account length',
'area code',
'phone number',
'international plan',
'voice mail plan',
'number vmail messages',
'total day minutes',
'total day calls',
'total day charge',
'total eve minutes',
'total eve calls',
'total eve charge',
'total night minutes',
'total night calls',
'total night charge',
'total intl minutes',
'total intl calls',
'total intl charge',
'number customer service calls',
'churn']
data = pd.read_csv('churn.data.txt', header = None, names = columns)
#Datasetin orjinali hali
print("Dataset orjinal hali: " + str(data.shape))

Çıktı:

Dataset orjinal hali: (3333, 21)

Bu çıktı bize 3333 tane örnek veri (sample/instance)ve 21 tane öznitelik (attribute) olduğunu söylüyor. ‘churn’ isimli özniteliği sınıflandırma amacıyla kullanacağız.


Ön İşleme Adımları

Dört adımdan oluşan ön işleme aşamasında ilk olarak string değerlere (Yes/No ve True/False) sahip olan üç tane özniteliğin değerlerini “0” ve “1” ile değiştiriyoruz.

#Preprocessing Adım 1: yes, no, true, false mapping
mapping = {'no': 0., 'yes':1., 'False.':0., 'True.':1.}
data.replace({'international plan' : mapping, 'voice mail plan' : mapping, 'churn' : mapping}, regex = True, inplace = True)

İkinci adımda, örnek olması amacıyla işimize yaramayacağını düşündüğümüz üç tane özniteliği veri setinden çıkartıyoruz. Normalde bu işlemi yaparken “entropi tabanlı bilgi kazancı değerine göre öznitelik sıralama” yöntemi gibi “öznitelik seçim yöntemlerini” (feature selection) kullanmamız gerektiğini de not etmekte fayda var.

#Preprocessing Adım 2: phone number, area code, state özniteliklerinin kaldırılması
data.drop('phone number', axis = 1, inplace = True)
data.drop('area code', axis = 1, inplace = True)
data.drop('state', axis = 1, inplace = True)
print("Dataset preprocessing sonrasi: " + str(data.shape))

Çıktı:

Dataset preprocessing sonrasi: (3333, 18)

Veri setindeki örnek sayısı değişmedi ancak öznitelik sayısı 21'den 18'e düştü. Şimdi de kalan 18 tane özniteliğin veri türlerine bir göz atalım.

print("Veri turleri:")
print(data.dtypes)

Çıktı:

Veri turleri:
account length int64
international plan float64
voice mail plan float64
number vmail messages int64
total day minutes float64
total day calls int64
total day charge float64
total eve minutes float64
total eve calls int64
total eve charge float64
total night minutes float64
total night calls int64
total night charge float64
total intl minutes float64
total intl calls int64
total intl charge float64
number customer service calls int64
churn float64

Üçüncü ön işleme adımında ise “churn olan” ve “churn olmayan” örnek veri sayısını eşitlemeye çalışıyoruz. Amacımız bu tarz veri setlerindeki en büyük problemlerden birisi olan “sınıf dengesizliği” (class imbalance) problemini çözmek. Bunun için öncelikle aşağıdaki gibi veri setini ikiye parçalıyoruz; data1 veri seti “churn olan verileri”, data2 veri seti “churn olmayan verileri” içeriyor.

data1 = data[data['churn']==1]
print("Churn olanlar-data1:"+ str(data1.shape))
data2 = data[data['churn']==0]
print("Churn olmayanlar-data2:"+ str(data2.shape))

Çıktı:

Churn olanlar-data1:(483, 18)
Churn olmayanlar-data2:(2850, 18)

Sınıf dengesizliği problemini çözmek için basit ama etkili bir çözüm uygulayalım: churn olmayan 483 tane örnek veriyi churn olan 483 tane örnek veri ile aşağıdaki gibi birleştirelim ve ekrana son veri seti ile ilgili bilgi yazdıralım. Son veri setinde toplam 966 tane örnek veri olmasını bekliyoruz.

#Her iki sinifta da esit sayida ornek olmasini istiyoruz
data = data1.append(data2[:483])
print("Son veriseti :"+ str(data.shape))

Çıktı:

Son veriseti :(966, 18)

Eğitim ve Test Verisinin Oluşturulması

Hem DNN (Deep Neural Network) modelinin hem de klasik ML (Machine Learning) modellerinin oluşturulması ve test edilmesi öncesinde elde ettiğimiz son veri setini “eğitim” ve “test” veri setleri olarak parçalıyoruz. Biz bu çalışmada verinin %80'ini eğitim %20'sini ise test için kullanacağız.

#Egitim  ve test verisini parcaliyoruz --> 80% / 20%
X = data.ix[:, data.columns != 'churn']
Y = data['churn']
X_train, X_test, Y_train, Y_test = cross_validation.train_test_split(X, Y, test_size=0.2, random_state=0)
X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
#ölçeklendirme
scaler = preprocessing.MinMaxScaler((-1,1))
scaler.fit(X)
XX_train = scaler.transform(X_train.values)
XX_test = scaler.transform(X_test.values)
YY_train = Y_train.values 
YY_test = Y_test.values

Burada gerçekleştirdiğimiz diğer önemli işlem ise veri seti üzerinde ölçeklendirme işlemi yapmamız. Dördüncü ve son ön işleme adımını, preprocessing paketi içerisindeki MinMaxScaler fonksiyonunu kullanarak gerçekleştiriyoruz. Öncelikle veri setimizi -1 ve 1 arasında ölçeklendirip daha sonra dönüştürme işlemini uyguluyoruz.


Makine Öğrenimi Algoritmaları ile Model Değerlendirme

Sırada; oluşturduğumuz eğitim ve test veri setlerini kullanarak, modelleri eğitme ve onları test ederek, model sonuçlarını değerlendirme adımımız var. Makalede 6 tane temel (base) ve 3 tane kolektif (ensemble) sınıflandırma modeli kullanacağız. Detaylar aşağıdaki gibi (linklerin üzerine tıklayarak detaylarına bakabilirsiniz):

Temel Sınıflandırma Modelleri: Logistic Regression, Naive Bayes, Decision Tree (CART), K-NN, SVM(Support Vector Machines), LDA (Linear Discriminant Analysis)
Kolektif Sınıflandırma Modelleri: AdaBoostClassifier, BaggingClassifier, RandomForestClassifier

İlk olarak, modellere ait scikit-learn kütüphane paketlerini import ediyoruz.

# Sınıflandırma Modellerine Ait Kütüphaneler
from sklearn.naive_bayes import GaussianNB
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier, BaggingClassifier

Daha sonra modelleri dinamik bir listeye ekliyoruz. Aşağıda da gördüğümüz üzere; listenin ilk elemanını “sınıflandırma modelinin ismi”, ikinci elemanını “modelin kurucu (constructor) metodunun çağrılması” şeklinde tanımladık.

models = []
models.append(('Logistic Regression', LogisticRegression()))
models.append(('Naive Bayes', GaussianNB()))
models.append(('Decision Tree (CART)',DecisionTreeClassifier()))
models.append(('K-NN', KNeighborsClassifier()))
models.append(('SVM', SVC()))
models.append(('LDA', LinearDiscriminantAnalysis()))
models.append(('AdaBoostClassifier', AdaBoostClassifier()))
models.append(('BaggingClassifier', BaggingClassifier()))
models.append(('RandomForestClassifier', RandomForestClassifier()))

Son adımda; her modeli “for döngüsü” kullanarak daha önce oluşturduğumuz eğitim ve test veri setleri üzeriden eğitip, her modelin ACC (Accuracy / Doğruluk) ölçütüne göre başarısını elde ediyoruz.

# Modelleri test edelim
for name, model in models:
model = model.fit(X_train, Y_train)
Y_pred = model.predict(X_test)
from sklearn import metrics
print("Model -> %s -> ACC: %%%.2f" % (name,metrics.accuracy_score(Y_test, Y_pred)*100))

Çıktı:

Logistic Regression -> ACC: %71.65
Naive Bayes -> ACC: %76.80
Decision Tree (CART) -> ACC: %80.41
K-NN -> ACC: %66.49
SVM -> ACC: %46.91
LDA -> ACC: %72.16
AdaBoostClassifier -> ACC: %72.68
BaggingClassifier -> ACC: %81.96
RandomForestClassifier -> ACC: %85.05

Yüzdesel olarak hesaplanan ACC ölçütünün değerlendirilmesinde; %100'e en yakın sonuç en iyi sonuçtur ve bu sonucu üreten model en iyi sınıflandırma modelidir, şeklinde değerlendirme yapıyoruz.

En başarılı sınıflandırma modeli: Random Forest (ACC: %85.05)
En başarısız sınıflandırma modeli: SVM (ACC: %46.91)
O zaman son durum yukarıdaki gibi ve biz hem Churn Analizi olayını hem de Makine Öğrenimi mevzunu yaladık-yuttuk-bitirdik :)

Tabiki durum böyle değil :) Biz şu anda hazır bir veri setini bir şekilde elde edip, Python scikit-learn kütüphanelerini kullanarak deneysel bir çalışma yaptık.

Kendimizi biraz zorlayıp yorum yapmaya çalışalım…

Yorum 1: Veri setinin kalitesine güvenerek işe başladık. Kayıp veri var mı diye bakmadık. Öznitelikler arasındaki ve öznitelikler ile sınıf arasındaki ilişkileri değerlendirmedik. Bir histogram grafiğini bile kendimize fazla gördük. Bunları yapsaydık sonuç değişir miydi? Cevap sizden:(Evet / Hayır / Muhtemelen / Dene Gör).

Biz yine de veri setimizin tamamı üzerinde bir histogram grafiği nasıl alacağımızı aşağıda belirtmiş olalım.

import matplotlib.pyplot as plt
num_bins = 10
data.hist(bins=num_bins, figsize=(20,15))
plt.savefig("churn_histogram")
plt.show()

Yorum 2: Sınıflandırma modellerini tasarlarken varsayılan (default) parametre değerlerini kullandık, her modelin oluşturulması sırasında birçok parametre mevcut ve farklı parametre değerleri ile farklı değerlendirme sonuçları elde etmek mümkün. Bu durumda her bir sınıflandırma modelinin teorik alt yapısını orta/iyi seviyede bilmek gerekiyor. Örneğin, Bagging ve Boosting kolektif (ensemble) sınıflandırma modellerini ele alalım. Bu yaklaşımların neden ve nasıl ortaya çıktığını bilmemiz gerekiyor. Ayrıca Bias — Variance ile Overfitting — Underfitting arasındaki ilişkiyi anlamamız önemli. Varsayılan parametreleri kullanarak, AdaBoostClassifier sınıflandırma modelinin ACC ölçütü değerini %72.68 bulmuştuk. Varsayılan parametre değerlerini değiştirerek bu başarıyı arttırmaya çalışalım. Açık kaynak kütüphanelerinde bana göre en iyi yol, kaynak kod detayına inmek :) Önce GitHub’daki kaynak koduna gidelim ve modelin kurucu metoduna bakalım.

Kaynak Kod (AdaBoosClassifier):

https://github.com/scikit-learn/scikit-learn/blob/a24c8b46/sklearn/ensemble/weight_boosting.py#L297

Kurucu Metot Satırları (GitHub Kaynak Kodu):

GitHub üzerindeki AdaBoost sınıflandırma modelinin kurucu metoduna ait kaynak kodu

Kurucu metodun parametrelerine baktığımızda ilk olarak dikkatimi n_estimators ve learning_rate parametreleri ve değerleri çekiyor. Algoritma mantığını bildiğimiz için bu veri seti üzerinde hemen basit bir deneme yapalım. Örneğin, learning_rate değerini “0.5” yapalım ve sınıflandırma modelini tekrar oluşturarak çalıştıralım.

Model Oluşturma Kod Değişikliği:

models.append(('AdaBoostClassifier', AdaBoostClassifier(learning_rate=0.5)))

Yeni Çıktı (ACC):

AdaBoostClassifier -> ACC: %78.35
Gördüğünüz gibi ACC ölçütünün değeri %72.68'den %78.35'e yükseldi. Yani teoride iyi bir şeyler oldu, gerçek hayatta gerçekten iyi bir şey yaptık mı :)

Yorum 3: Değerlendirme ölçütü olarak ACC kullandık. Fakat ACC ölçütünün hesaplanması sırasında; bir karmaşıklık matrisi (confusion matrix) kullanılarak elde edilen Precision ve Recall değerlerinden yola çıkarak herhangi bir değerlendirme yapmadık. Bunun için aşağıdaki kod parçasını eklememiz yeterli olacaktı.

report = classification_report(YY_test, Y_pred)
print(report)

ACC ölçütüne ek olarak; F-Measure ve ROC (Receiver Operating Characteristic) eğrisi kullanabilirdik. Özellikle sınıflandırma modellerinin karşılaştırılmasında ROC eğrilerinin kullanılması çok daha mantıklı ve güvenli.

Yorum 4: Bazı sınıflandırma modelleri ölçeklendirme kullanılarak dönüştürülmüş (Normalize edilmiş) veri setlerinde daha iyi sonuç vermektedir. MinMaxScaler kullanarak bu işlemi veri setimizde zaten yaptık ancak bu veri setini (XX_train, XX_test) bir sonraki makalemizde, DNN modelinde kullanmak üzere saklamıştık ama yakalandık :) SVM sınıflandırma modelinin normalize edilmiş veri setinde daha iyi çalıştığı bilinen bir gerçek. O zaman; normalize edilmiş veri setini aşağıdaki kod değişikliğini yaparak tüm sınıflandırma modellerinin testinde kullanalım ve sonuçları değerlendirelim.

# Modelleri test edelim
for name, model in models:
model = model.fit(XX_train, YY_train)
Y_pred = model.predict(XX_test)
from sklearn import metrics
print("%s -> ACC: %%%.2f" % (name,metrics.accuracy_score(YY_test, Y_pred)*100))

Çıktı:

Logistic Regression -> ACC: %72.16
Naive Bayes -> ACC: %76.80
Decision Tree (CART) -> ACC: %80.41
K-NN -> ACC: %74.74
SVM -> ACC: %74.23
LDA -> ACC: %72.16
AdaBoostClassifier -> ACC: %78.35
BaggingClassifier -> ACC: %81.96
RandomForestClassifier -> ACC: %85.05

SVM sınıflandırma modelinin önceki veri setinde ACC değeri %46.91 iken normalize edilmiş veri setinde ACC değeri %74.23 oldu, bayağı sağlam bir artış var. Hatta, “it is statistically significant” diyebiliriz…

Bir saniye…K-NN sınıflandırma modelinin ACC değeri de %66.49'dan %74.74'e yükseldi. Öznitelikler arası uzaklık hesaplama mantığı ile çalışan ve Öklid algoritmasını kullanan K-NN sınıflandırma modeli, yeni veri setimizde öznitelikler (-1, 1) aralığında ölçeklendirilince haliyle daha iyi ACC başarı sonucu verdi. Bunu da yakaladık.

Sonuç olarak, Makine Öğrenimi biliyorum demek ancak dile kolay...

Deneyler yapabilmek için veriyi nasıl keşfedeceğimizi bilmemiz ve modellerin bilimsel teorisini anlamamız gerekiyor. Bunun için de Matematik ve İstatistik bilmek avantaj değil bir zorunluluk. Sadece yukarıdaki yorumlardan yola çıksak bile; onlarca saat zaman harcayacağımız yüzlerce deney yapmamız mümkün.

Sonraki makalemizde aynı veri seti üzerinde Yapay Sinir Ağları kullanarak Derin Öğrenme ile model oluşturmaya ve test etmeye çalışacağız…


Not 1: Makalenin kaynak kodlarının tamamına bu linkten ulaşabilirsiniz.

Not 2: Makine Öğrenimi ve Derin Öğrenme modellerini Python ile gerçekleştirecek ortamı oluşturmak için Mac OS üzerine Anaconda’yı kurdum. Siz de benim gibi yapmak isterseniz bu makaleden yararlanabilirsiniz.

Deniz Kılınç


Like what you read? Give Deniz KILINÇ a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.