Bu yazıda, bir önceki yazıda olduğu gibi yine bir classification(sınıflandırma) işlemi gerçekleştireceğim. Fakat bu kez lojistik regresyon değil, bir başka sınıflandırma metodu olan karar ağaçlarını kullanacağım. Bunu yaparken de doğru tahminleme oranını artırmak adına çok kullanılan ve etkinliği kanıtlanmış bir yöntem olan güçlendirme(boosting) yöntemini kullanacağım. Karar ağaçlarının ve boosting yönteminin teorik detaylarına yeri geldikçe değineceğiz. Boosting yöntemini kullanarak sınıflandırma ile alakalı 2 yazıdan oluşan bir seri planlıyorum. Bu iki yazı içerisinde de birbirinden farklı boosting algoritmaları kullanacağım ve bunların detaylarına değineceğim. Boosting metodu tabii ki sadece sınıflandırma için değil regresyon işlemleri için de uygulanabilir fakat ben bu yazılarda sınıflandırma üzerinde durmayı planlıyorum. Serinin ilk yazısını Python üzerinde, ikincisini ise R üzerinde gerçekleştireceğim. Böylece farklı boosting algoritmalarının farklı programlama dilleri üzerinde nasıl uygulandığını da görmüş olacağız.

İkinci olarak yazıda kullanacağımız veri setinden, bağımlı ve bağımsız değişkenlerden bahsedelim. Öncelikle verinin kendisine ve veri ile alakalı bilgilere buradan ulaşabilirsiniz. Burada veriler direkt olarak text formatında bulunuyor. Ben bunları kolaylık olması açısından bir excel csv dosyasına topladım ve işlemleri buradan gerçekleştireceğim. Siz dilerseniz text dosyası ile de devam edebilirsiniz. Elimizdeki veri aslında 1994 yılına ait bir nüfus veritabanının bir alt kümesi. Veri içerisinde 14 özellik(feature) bulunuyor. Tahminlemeye çalışacağımız kolon ise “Income” yani gelir kolonu. Modelin amacı kişinin gelirinin 50K’dan yüksek veya 50K’ya küçük-eşit olma durumunu tahminlemek. Yani iki sınıflı tahminleme (binary classification) gerçekleştireceğiz.

Evet artık yavaş yavaş başlayabiliriz. Her şeyden önce veriyi Python üzerine alıyorum:

import os
os.getcwd()
import pandas as pd
import numpy as np
Data = pd.read_csv('AdultData.csv')
Data_pd = pd.DataFrame(data=Data)
len(Data_pd)
len(Data_pd.columns)
Data_pd.head()

Yine “.getcwd()” komutu ile çalıştığımız dosyayı öğrenerek csv verisini oraya attık ve daha sonra Pandas kütüphanesindeki “.read_csv()” komutunu kullanarak da Python üzerine aldık. Sonraki işlemde yine efektif işlem yapabilmek açısından veri setini bir pandas data frame nesnesine dönüştürdük ve son 3 satırda da verinin satır, sütun sayılarını öğrenerek ilk 5 satırını gözlemlemek adına “.head()” komutunu kullandık. Satır ve sütun sayıları:

1

İlk 5 satır da aşağıdaki gibi:

2

Veri içerisinde 48841 gözlem, 15 de değişken bulunuyor. Bu değişkenlerden bir tanesi “Income” yani bağımlı değişken, diğerleri de modelde tahminci görevi görecek olan bağımsız değişkenler.

Veriyi tanıdığımıza göre, “Data Preprocessing” aşamasına geçebiliriz. Bu aşamada veriyi makine öğrenmesi algoritmalarının rahatça kullanabileceği bir biçime getiriyoruz. Bu işlem içerisinde en çok önem atfedilmesi gereken noktalardan biri, missing(kayıp) verilerin ne olacağıdır. Bu noktada belirleyici olan ise bu kayıp verilerin miktarıdır. Öncelikle şunu söyleyelim ki bizim verisetimizin kendisinde kayıp veriler “?” ile gösterildiği için Python üzerinde de öyle gözükecek. Bu yüzden önce “?” işaretlerinin tümünü kayıp veri olarak Python’a tanıtmamız gerekiyor. Tüm “?” işaretlerini kayıp veriye çevirelim ve işleme o şekilde devam edelim:

Data_pd = Data_pd.replace('?', np.nan)
len(Data_pd[Data_pd.isnull().any(axis=1)])
for col in Data_pd.columns:
print(col + ' kolonundaki kayip deger sayisi = ' , sum(pd.isnull(Data_pd[col])))

Üstteki kod bloğunun ne yaptığını detaylı olarak açıklayalım. Öncelikle veri setindeki “?” işareti olan değerleri “numpy” kütüphanesinden “NaN” ile değiştirdik ki bunlar kayıp verileri temsil edecek. Bu işlemi yaparken çok basit olan “.replace” komutunu kullandık. İkinci satırda,  veri setinde kayıp değer içeren kaç satır var bunu görmek istedim. “.isnull” komutuna aksis numarasının 1 (yani satırlar) olduğunu söyledim ve “any” yani bir satırda herhangi bir değer bile kayıp olsa getir dedim. Son aşamada ise kolonlar bazında kaç kayıp değer var bunu görmek istedim ki kolonların içerdiği kayıp değer sayısını öğrenelim. Bunu yaparken bir for döngüsünden faydalandım. Veri setinin her kolonu için o kolondaki kayıp değerleri saydırdık ve konsola yazdırdık. Bunu “print” komutu sağladı. Bu komutun ikinci argümanında ise yine veri setinin kolonlarına “.isnull” komutunu uygulayarak kayıp değer içerip içermediklerini öğrendik. Evet komutların sonuçlarına bakalım:

3

3620 satırda kayıp veri bulunuyor.

4

Her kolondaki kayıp veri sayısı da bu şekilde. En çok kayıp veri içeren kolon 2809 değeri ile “occupation” kolonu. Önce satır ve sütunlardaki kayıp veri sayılarına neden baktık onu açıklayalım. Bazı durumlarda, örneğin satırlardaki kayıp veri sayısı az ise, kayıp veri içeren satırlar veri setinden çıkarılabilir. Tabii bu veri setinin büyüklüğüne göre de değişecektir. Ya da bazı durumlarda ise belli bir kolondaki kayıp veri sayısı çok fazla olabilir, bu durumda da o kolon veri setinden çıkartılmaya müsaittir. Çünkü bir satır içerisinde sadece tek bir kolonda kayıp veri var ise ve siz o satırı veri setinden çıkarırsanız, diğer tüm kolonların sağlayacağı bilgiyi kaybetmiş olursunuz. Böyle durumlarda eğer kolon çok fazla kayıp veri içeriyorsa, satır yerine kolonun çıkartılması düşünülebilir. Fakat bizim veri setimizde, eğer kayıp veri içeren satırları çıkaracak olursak 3620 tane satırı kaybedeceğiz ki bu yüksek bir sayı. Aynı şekilde kolonlar bazında da en çok kayıp veri içeren “occupation” kolonu 2809 tane kayıp veri içeriyor. Halbuki orada 48 binden fazla gözlem var. Bu durumda kolonları çıkartma yöntemine de gidemeyiz.

Görünen o ki ne satır ne de sütun bazında veri setinden çıkartma yapamıyoruz. Bu durumda kayıp veriler için başka bir yöntem uygulamamız gerekiyor. Alternatif yöntemlerden bir tanesi de kayıp değerleri belirli veriler ile değiştirmek(imputation) yöntemi. Örneğin nümerik kolonlarda, kayıp veriler kolon ortalaması veya medyan değeri ile, kategorik kolonlarda ise en sık görülen kategori ile değiştirilebilir. Fakat unutmamak gerekir ki bu veri setine biraz yanlılık katacaktır ve bazı durumlarda ise bu yanlılık boyutu muazzam olabilir. Yanlılık katmasının sebebi ise, veriye dair bir nevi varsayım yürütüp onları kendi insiyatifimiz ile doldurmamız. Bu, bize veri içerisindeki gerçek ilişkileri ve yapıyı modele kavratma konusunda sıkıntı çıkaracaktır. Modelin kavradığı ilişkinin bir kısmı yapay olmuş olacaktır. Fakat bu metodu satır ya da kolon çıkarma metodu ile karşılaştırdığımızda kaybedeceğimiz bilgi kaybı bu veri seti için daha az olacak bu yüzden bu metodu tercih ediyoruz. Kayıp verilerin nasıl yönetileceği konusunda daha ileri düzey metodlar da bulunuyor(örneğin bir başka tahmin modeli ile kayıp verilerin tahmin edilmesi daha sonra asıl amaç olan tahmin modelinin kurulması) fakat bu yazıda bunlardan bahsetmeyi düşünmüyorum.

Bizim veri setimizde sadece kategorik değişkenlerde kayıp veriler bulunuyor bu yüzden buralardaki kayıp verileri en sık kategori ile değiştireceğiz. Aşağıdaki kod bloğu ile bunu yapalım:

Data_pd = Data_pd.apply(lambda x:x.fillna(x.value_counts().index[0]))
len(Data_pd[Data_pd.isnull().any(axis=1)])

Kod bloğunda veri setine “.apply” metodu ile satır bazlı uygulama yaptık ve “.fillna” komutunu kullanarak kayıp değerleri en çok bulunan kategori ile değiştirdik. “index[0]” kısmı en yüksek düzeyde bulunan kategoriyi seçti. Burada “lambda”, bir fonksiyon belirtiyor diyebiliriz. Kayıp veri sayısına tekrar bakalım:

5

Evet görüldüğü üzere tüm kayıp değerler değiştirildi.

Data preprocessing kısmının ikinci aşamasında ise kategorik verilerin kodlanması geliyor. Geçen sefer kategorik verileri kodlamak için “One-Hot Encoding” yönteminden bahsetmiş ve kullanmıştım. Çok detayına girmeden tekrar bahsedeyim. Bu metod, kategorik bir değişkenin sınıflarını alarak bunları ayrı birer binary(1 ve 0 değerleri içeren) kolona dönüştürür. Bu veri setinde de yine kategorik değişkenleri tespit edip bu metodu kullanarak kodlayacağız. Aşağıdaki kod bloğu ile başlayalım:

Data_pd.dtypes
for col in Data_pd.iloc[:,[1, 3, 5, 6, 7, 8, 9, 13, 14]]:
Data_pd[col] = Data_pd[col].astype('category')
Data_pd.dtypes
Data_pd = pd.get_dummies(Data_pd, columns = ['workclass', 'education', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'native-country'], prefix = ['workclass', 'education', 'marital-status', 'occupation', 'relationship', 'race', 'sex', 'native-country'])
Data_pd.head()

Kod bloğu bir önceki yazıda kullandığımıza çok benzer. Yine veri setindeki kolonların tiplerine baktık ve gerekli olanların tipini “category” tipine dönüştürdük. Bunun için de yine for döngüsünden yararlandık. Burada “.astype(‘category’)” komutu tipleri değiştirmede kullanıldı. Bu işlemlerin sonucunu görelim:

6

Döngüden sonra:

7

Bir sonraki aşamada kategorik değişkenler için “dummy” değerleri atadık. Dummy(kukla) kolonların binary(iki sınıflı) kolonlar olduğunu ve 1, 0 değerlerini içerdiğini söylemiştik. Tekrar veri setinin ilk 5 satırını görelim:

8

Dikkat çekmek istediğim bir nokta da özellik sayımızın 106’ya çıkmış olması. (Bağımlı değişkeni kodlamadık) One-Hot Encoding metodunun değişken sayısını ciddi miktarda artırdığından bahsetmiştim.

Data preprocessing aşamasının üçüncü bir adımı olarak, “Feature Scaling” düşünülebilir. Bunun özelliklerin normalize edilmesi ve aynı ölçeğe indirgenmesi demek olduğundan bahsetmiştik. Bu işlemin önemi ise değişkenlerin içerisindeki verilerin çok yüksek değişim içerisinde olmalarının algoritmaların amaç fonksiyonun düzgün çalışmasına olan zararını engellemek. Ya da bir başka deyişle, çok değişkenlik gösteren özelliklerin amaç fonksiyonunu domine etmesinin önüne geçmek.

Bu işlemin önemi kullanılacak algoritmaya göre değişkenlik gösterecektir. Örneğin lojistik regresyonda daha önemli iken, karar ağaçlarında daha önemsiz hale gelebilir. Bunun nedeni ise nümerik işlemlerin, denklemlerin vs. daha çok olduğu algoritmalarda normalleştirme daha etkilidir. Karar ağaçlarında ise karşılaştırma yapılarak ağacın dallarından aşağıya inildiği için, bu işlemin önemi azalabilir. Fakat yine de, yapılması tercih edilebilir. Biz bu kez yapmadan devam edeceğiz. Data preprocessing aşamasında düşünlebilecek bir konu da verideki sınıf dağılımıdır. Bizim verimize baktığımızda gözlemlerin yaklaşık olarak %75’inin 50K’dan küçük ve %25’inin de 50K’ya büyük eşit olduğunu görüyoruz. Eğer model kalitesini ölçmek için “accuracy” metriğini kullanırsak bu konuda bize sıkıntı yaratabilir. Bundan bir önceki yazımda da bahsetmiştim. Tabii ki bunu engellemek için birden fazla yöntem bulunuyor. Biz ise burada kalite metriğini değiştirerek bu problemi halledeceğiz. Kullanacağımız kalite metriğinden aşağıda bahsedeceğim. Böylece data preprocessing aşamasını kapatıyoruz.

Sıradaki aşamada, yine klasik olarak her yazıda yaptığımız gibi, veriyi train, validation ve test set olarak üçe böleceğiz. Train set üzerine modeli kuracak, validation set üzerinde hiperparametreleri belirleyecek ve test set üzerinde de modelin performansını ölçeceğiz. Aşağıdaki kod bloğu ile bölme işlemini gerçekleştirelim:

from sklearn.model_selection import train_test_split
train, test = train_test_split(Data_pd, train_size = 0.7, shuffle = True, random_state = 0)
validation, test = train_test_split(test, train_size = 0.5, shuffle = True, random_state = 0)
len(train)
len(validation)
len(test)

X_train = train.drop("Income", axis = 1)
Y_train = train['Income']
X_validation = validation.drop("Income", axis = 1)
Y_validation = validation['Income']
X_test = test.drop("Income", axis = 1)
Y_test = test['Income']

Kodu açıklayacak olursak, bu kez önceki yazılardan farklı olarak “sklearn” kütüphanesinden “train_test_split” içerisindeki komutu kullandık. Önceki yazılarda “pandas” kütüphanesini kullanmıştık. Veriyi %70 train, %15 validation ve %15 de test set olmak üzere üçe ayırdık. Burada “Shuffle” argümanı bölmeden önce veriyi karıştırır. “random_state” argümanı ile de tutarlı bölmeler yapılmasını sağlıyoruz. Kodun ikinci kısmında ise her zamanki gibi veri setlerinin bağımlı ve bağımsız değişkenlerinin kim olduğunu programa tanıtıyoruz ve onları birbirinden ayırıyoruz. Satır sayılarını da görelim:

9

Evet veri istediğimiz şekilde bölündü, devam edelim.

Karar ağaçları, hem regresyon hem de sınıflandırma işleminde kullanılan bir tekniktir. Bunlara genel anlamda “Classification and Regresson Trees(CART)” denmektedir. Biz sınıflandırma tarafına yoğunlaşacak olursak, karar ağacı, kendisine verilen bir girdiyi alarak, ağacın dalları arasında o girdinin getirdiyi veriye bakarak ilerler ve en son aşamada bir çıktı(tahmin) verir. Ağaç öncelikle bir kök düğüm ile başlar ve burada herhangi bir bölünme(dal) yoktur. Daha sonra bölünme işlemi başlar. Bölünmeler yapılırken ya da dallar oluşurken her özellik ile deneme yapılır ve o aşamada en düşük sınıflandırma hatasını veren özellik bölünmeyi gerçekleştirir. Bu da aslında şu demek oluyor; her özellik ile bir bölünme gerçekleştirilir ve sınıflandırma hatası en düşük olan seçilir. Sonraki aşamada tekrar özellikler göz önüne alınır ve yine sınıflandırma hatasını minimize eden özellik seçilir.

CART algoritması şu şekilde ilerler: İlk adımda boş bir ağaç ile başlanır.  Daha sonra bölme işlemini yapabilmek için üstte bahsettiğim gibi minimum sınıflandırma hatasını veren özellik seçilir ve bölme işlemi gerçekleşir. Bu bölünme işleminden sonra düğümler oluşur ve eğer oluşan düğümde bulunan verilerin tamamı aynı bağımsız değişken değerini içeriyorsa, bu durumda o düğümde o bağımsız değişken sınıfı tahmin olarak ortaya çıkar ve o düğüm üzerinden başka bölünme yapılmaz. Bu kurala uymayan düğümlerde ise tekrar bölme yapabilmek için bir özellik aranır ve bu da aslında ikinci adıma geri dönülmesi demek olur. Bu yüzden bu algoritmaya “Recursive Greedy Algorithm” denmektedir. Böyle denmesinin sebebi özyinelemeli olarak devam etmesidir.

Karar ağaçlarının belirgin bir dezavantajı ise overfitting’ e çok yatkın olmalarıdır. Sürekli bölünme işleminden dolayı ağaç kısa sürede genelleştirilemez hale gelebilir ve fazla karmaşıklaşabilir. Bunu engellemek adına kullanılan yaygın iki yöntem “Early Stopping” ve “Pruning” dir. Early stopping yani erken durma işleminde üç metod vardır diyebiliriz. Bunlar ağacın derinliğini(kökten yapraklara giden en uzun yolun mesafesi ya da o yoldaki düğüm sayısı) sınırlamak, herhangi bir bölme işlemi(splitting) sınıflandırma hatasını anlamlı bir miktarda düşürmüyorsa orada durmak(o bölme işlemini yapmamak) ve herhangi bir düğümdeki gözlem sayısı belirli bir sayının altında ise durmak. Pruning yani budama metodu ise verinin ulaşabileceği maksimum karmaşıklığa kadar ulaşmasına izin verir, daha sonra anlamsız splitting işlemlerini tespit ederek bunları budar yani kaldırır. Pruning metodunun early stopping metodundaki ikinci adıma olan bir üstünlüğü ise, ikinci adımda sınıflandırma hatasını anlamlı bir biçimde düşürmediği için gerçekleştirilmeyen bir bölünmeden sonra gelebilecek olan potansiyel bir çok iyi bölünmeyi kaçırmayıp gerçekleşmesine izin vermesi, sonra gereksiz bölünmeleri kaldırmasıdır. Bu metodların detaylarına burada değinmeden yazıya devam ediyorum.

Karar ağaçlarının overfitting’ e bu yatkınlığından dolayı, istatistikte çoğu metodda da olduğu gibi burada da ingiliz bir teorisyen olan Occam’in metodu tercih edilir. Kısaca metod şunu der : rekabet eden hipotezler arasında, en az varsayım yapan seçilmelidir. Bunu karar ağaçları bazında düşündüğümüzde de, modellerin hata oranı benzer ise, en az değişkeni barındıran seçilmelidir. Yani basit olan daha iyidir. Şimdi karar ağacını Python üzerinde nasıl uygulayacağımızı görelim:

from sklearn.tree import DecisionTreeClassifier
DS = DecisionTreeClassifier(max_depth = 1, min_samples_leaf = 30)

Öncelikle gerekli modülü indirdik. Daha sonra “DS” adında bir nesne tanımlayıp ona “DecisionTreeClassifier” komutu ile bir karar ağacı atadık. Burada max_depth argümanı, yukarıda bahsettiğimiz gibi ağacın derinliğini sınırlar. Ağacın derinliği burada yaptığımız gibi 1 ise bu tarz sınıflandırıcılara “Decision Stump” denilir. Bu yüzden nesnenin adını da “DS” koyduk. İkinci kısımda ise bir yaprakta olacak minimum gözlem sayısını belirttik. Karar ağacının herhangi bir düğümünde daha fazla bölme yapılmıyor ve tahmin çıktısı veriliyorsa bu noktaya yaprak denir. Burada gerçekleştirmek istediğimiz ise az sayıda gözlem barındıran yapraklarda tahmin yapmaktan kaçınmak. Eğer az sayıda gözlemden tahmin yaparsak, bu işlem fazla yanlı olacak ve mantıklı bir tahmin olmayacaktır. Örneğin 11 gözlemden 6 tane >50K içeren bir yaprakta, tahmin çıktısı olarak >50K vermek mantıksız olacaktır çünkü gözlem sayısı sadece 11. Bu yüzden 30’dan az gözlem olan düğümlerden tahmin yapmayı burada engelliyoruz.

Karar ağaçlarına dair kısaca teorik kısma değindikten sonra, biraz da boosting işleminden bahsedelim ve Python üzerindeki uygulamaya geçelim. Boosting(güçlendirme) işleminin konsepti, tek bir sınıflandırıcıyı kullanarak tahmin elde etmek yerine, birden fazla zayıf sınıflandırıcının tahmin güçlerini birleştirerek en sonunda kompleks ve güçlü bir sınıflandırıcı elde etmek üzerinedir. Buradaki sınıflandırıcı metodu farklı teknikler olabilir. Örneğin lojistik regresyon, karar ağacı vb. Zayıf(yani kompleks olmayan) sınıflandırıcılar her seferinde veri üzerine tekrar tekrar kurulur ve bir önceki aşamada kurulan sınıflandırıcının yaptığı hataları yapmamaya çalışır. Boosting işleminin temel ilkesi budur ve bu zamana kadar da çok kuvvetli olduğu gözlemlenmiştir. Boosting algoritmaları bir tane değildir. En bilinenleri AdaBoost ve Gradient Boosting’dir. Biz bu yazıda AdaBoost algoritmasını kullanacağız. Bu yüzden Gradient Boosting’in detaylarına değinmeyeceğim. Bunu bir başka yazıda düşünüyorum.

AdaBoost algoritmasını da kısaca açıklayalım. AdaBoost sonucunda, birden fazla zayıf sınıflandırıcı bir topluluk(ensemble) oluşturur ve final tahmin her adımda oluşan farklı sınıflandırıcıların yaptığı farklı tahminlerin katkısıyla oluşur. Her sınıflandırıcının final tahmine ne kadar katkıda bulunucağı, kendilerine atanan bir ağırlık(katsayı) ile belirlenir. Final tahminin ne olacağı da majority vote(en çok tahminlenen değer – sınıflandırma problemleri için) veya average(ortalama – regresyon problemleri için) metodlarından biri kullanılarak belirlenir. Yani sınıflandırma problemlerinde tahminler arasında en çok hangi sınıf varsa final tahmin olarak o seçilir diyebiliriz.

Algoritma her veri noktasına(ya da buna satır da diyebiliriz) eşit ağırlığı vererek başlar. Bu ağırlık da 1/N(satır sayısı) olarak verilir. Bunun nedeni ise hangi veri noktalarının tahminlenmesi zor, hangilerinin kolay olduğunun bilinmemesi. Bu yüzden hepsine eşit(uniform) ağırlık verilir ve algoritma bu şekilde başlatılır. Daha sonra bu ağırlıklar kullanılarak zayıf sınıflandırıcı model öğrenilir. Bu topluluğa katılacak olan ilk modeldir. Bir sonraki adımda bu modelin final tahmine yapacağı katkı(katsayı) belirlenir ve model topluluğa eklenir. Daha sonra her veri noktasının ağırlığı yeniden hesaplanır ve bu kez hesaplamalar yapılırken bir önceki adımda kurulan sınıflandırıcının yanlış tahminlediği veri noktalarına daha çok ağırlık atfedilir. AdaBoost algoritmasının genel mantığı da budur. Hatalı tahminlenmiş noktalara ağırlık vererek yeni adımda modelin bu noktaları doğru tahminlemesini sağlamak. Ağırlıklar yeniden hesaplandıktan sonra da bu ağırlıklar normalize edilir ki hem hesaplamalar kolaylaşsın hem de bazı noktaların ağırlığı çok küçülerek veya çok büyüyerek amaç fonksiyonunu saptırmasın. Bu adımlar sürekli tekrarlanarak belirlenen sayıda sınıflandırıcı model topluluğa eklenir ve final tahmin elde edilir.

Burada siz de farketmişsinizdir ki belirlenmesi gereken hiperparametreler bulunuyor. Bunlar en basitinden hangi sınıflandırıcıyı kullanacağımız ve bu sınıflandırıcının parametrelerinin ne olacağı. Bu işlemi yukarıda gerçekleştirdim. Sınıflandırıcıyı Decision Stump olarak seçtim ve parametrelerini de belirledim. Fakat bu işlem de tüm hiperparametre belirleme işlemleri gibi validation set veya cross-validation işlemi kullanılarak yapılmalıdır. Burada tüm hiperparametreleri belirlemek için bu metodu uygulamadım çünkü yazının fazla uzamasını istemedim. Bu yüzden bazı hiperparametreleri kendim belirledim. Sınıflandırıcının kendi hiperparametreleri dışında, boosting algoritmasının da kendine ait hiperparametreleri bulunuyor. Bunların en önemli iki tanesi “learning rate” ve “number of estimators”. Yani her sınıflandırıcının final çıktıya(tahmine) katkısı ve kurulacak sınıflandırıcı model sayısı. Learning rate hiperparametresini yine biz seçeceğiz fakat unutulmamalıdır ki bu ve bunun gibi diğer tüm hiperparametreler detaylı ve dikkatli cross-validaiton, validation set teknikleri kullanılarak seçilmelidir. Benim burada bu teknikler ile seçeceğim tek hiperparametre ise kurulacak sınıflandırıcı sayısı olacak. Üstte de bahsettiğim gibi yazının çok uzamaması ve temel mesajı vermek adına bu işlemleri uygulamayacağım. Belki başka yazıda daha detaylı olarak uygularız.

Son olarak boosting işleminde over-fitting’den de bahsedelim. Normal sınıflandırma metodlarının aksine, boosting işlemi over-fitting’e çok daha dayanıklıdır. Fakat yine de kurulan model sayısı arttıkça, az da olsa over-fitting’e düşülür. Bunu önlemek adına da, learning rate ve number of estimators gibi hiperparametreler kullanılır. Düşük learning rate değeri modeli daha dayanıklı ve daha genelleştirilebilir yapar fakat daha çok model gerektirir. Bu da hesaplama zorluklarına yol açabilir.

Burada AdaBoost’un iki farklı versiyonunu kullanacak ve ikisinin de ortaya çıkardığı sonuçları göstereceğiz. Bir tanesi üstte de bahsettiğimiz normal versiyon, ki discrete AdaBoost olarak geçer, diğeri de Real AdaBoost denilen ve discrete versiyon gibi direkt olarak tahmin edilen sınıf etiketlerindeki hatalara değil de tahmin edilen sınıf olasılıklarına yoğunlaşan versiyon olacak. Artık uygulamaya geçelim:

from sklearn.ensemble import AdaBoostClassifier
from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_score

N_estimators_range = [10, 50, 75, 100, 200, 400, 600, 800, 1000, 1300, 1700, 2000]

for noe in N_estimators_range:
ada_discrete = AdaBoostClassifier(
base_estimator = DS,
learning_rate = 0.1,
n_estimators = noe,
algorithm = "SAMME")
ada_discrete.fit(X_train, Y_train)
ada_real = AdaBoostClassifier(
base_estimator = DS,
learning_rate = 0.1,
n_estimators = noe,
algorithm = "SAMME.R")
ada_real.fit(X_train, Y_train)
discrete_pre = ada_discrete.predict(X_validation)
real_pre = ada_real.predict(X_validation)
discrete_f1 = f1_score(Y_validation, discrete_pre, average = 'micro')
real_f1 = f1_score(Y_validation, real_pre, average = 'micro')
discrete_acc = accuracy_score(Y_validation, discrete_pre)
real_acc = accuracy_score(Y_validation, real_pre)
print('Number of Estimators: ', noe, ' Discrete AdaBoost F1 Score: ', discrete_f1, ' Real AdaBoost F1 Score: ', real_f1, ' Discrete AdaBoost Accuracy Score: ', discrete_acc, ' Real AdaBoost Accuracy Score: ', real_acc)

Üstteki kod bloğunu açıklayalım. Öncelikle gerekli modülleri indirdik ve üstte de konuştuğumuz gibi deneme yapacağımız sınıflandırıcı sayıları için bir dizi belirledik. Bu dizideki her sayı ile boosting algoritmasını çalıştıracağız ve en iyi sonucu vereni kullanacağız. For döngüsü içerisinde, “AdaBoostClassifier” metodunun içinde üstte oluşturduğumuz “Decision Stump” nesnesini base_estimator argümanına yani kullanacağımız sınıflandırıcıya, 0.1 değerini -ki bu değer bizim belirlediğimiz değer- learning_rate argümanına, diziden gelecek olan tahminci sayısını tabii ki n_estimators argümanına atadık ve en son aşamada da algorithm argümanında “SAMME” ve “SAMME.R” yani sırasıyla discrete ve real AdaBoost algoritmalarını verdik. Bu algoritmaları da tabii ki “.fit” komutuyla train set üzerine oturttuk. Bunların kalite metriklerini de accuracy ve F1 score olarak belirledik. Accuracy’nin ne olduğundan zaten bahsetmiştik.

F1 score ise iki ana terimden, “precision” ve “recall” ‘dan oluşur. Precision ifadesi, sınıflandırdığımız gözlemler arasından kaçını doğru sınıflandırdığımızı belirtir. Yani “True Positive” dediğimiz kesimi temsil eder. Buna bir sınıflandırıcının negatif bir gözlemi pozitif olarak sınıflandırmama yeteneği de diyebiliriz. Recall ise, veri içerisindeki pozitif gözlemlerin kaçını gerçekten bulabildiğimiz, yani sınıflandırıcının tüm pozitifler içerisinden kaç tane pozitifi tespit edebildiğinin ölçüsüdür. F1 değeri ise bu iki terimin ağırlıklı harmonik ortalaması olarak gösterilebilir ve dengesiz sınıflardan oluşan veri setlerinde bu yüzden metrik olarak accuracy’e üstündür. Üstte de bahsettiğimiz gibi dengesiz sınıf problemini bu metriği kullanarak aşacağız.

Biz üstteki kod bloğunda accuracy değerini de görsel amaçlarla kod bloğuna ekledik ve en sonunda da her serideki tahminci sayısı için bunları “print” komutunu kullanarak konsola yazdırdık. Sonuçları görelim:

1011121314

Evet baktığımızda tahminci model sayısı arttıkça, hem accuracy hem de F1 değerinin arttığını görüyoruz. Boosting işleminin en belirgin özelliklerinden biri de normal sınıflandırma veya regresyon modellerinde olan trainin error sürekli düşerken test error’un bir yere kadar düşüp daha sonra yükselmesi olayının geçerli olmaması. Burada training error yine düşüyor fakat test error bir yere kadar düştükten sonra yaklaşık olarak sabit kalıyor. Yani bir yerden sonra tabii ki artmalar azalmalar oluyor fakat bunlar çok büyük farklardan ibaret olmuyor. Üstte de baktığımızda 600 tahminciden sonra hem F1 hem de accuracy değerlerinde çok ufak artışlar meydana geliyor ki bunlar önemsiz olarak sayılabilir. Yani burada 600 tahminciyi seçmekle 2000 tahminciyi seçmek arasında bir fark yok. Bu durumda her zaman en yüksek tahminciyi seçelim diyebilirsiniz. Fakat bu yanlış olacaktır çünkü boosting algoritmaları çok yüksek hesaplama süresi ister ve learning rate düşük ise doğru bir öğrenme için çok fazla sayıda tahminci model isteyecektir ve bu da hesaplama yükü demektir. O zaman madem burada 600 ve 2000 arasında pek fark yok, hesaplama kolaylığı açısından 600 değerini seçmek bizim için en uygunu olacaktır. Bizim veri setimizde ikisi de seçilebilir çünkü çok büyük bir veri setimiz yok. Fakat ML projelerinde genellikle daha büyük veriler ile uğraşıldığından dolayı bu seçim büyük önem arzetmektedir.

Finalde bulduğumuz accuracy veya F1 skorlarının çok yüksek olmamasını ise learning rate gibi boosting parametrelerini ve max_depth, min_samples_leaf gibi ağaç bazlı parametreleri keyfi olarak seçmemize bağlayabiliriz. Bunlar da doğru bir biçimde seçilseydi, çok daha başarılı bir modele ulaşılırdı.

Evet bu durumda tahminci model sayısını 600 olarak belirliyor ve yolumuza devam ediyoruz. Sıradaki aşamada hiperparametrelerini belirlediğimiz modeli test verisi üzerinde denemek geliyor. Aşağıdaki kod bloğu ile bunu gerçekleştirelim:

ada_discrete = AdaBoostClassifier(
base_estimator = DS,
learning_rate = 0.1,
n_estimators = 600,
algorithm = "SAMME")
ada_real = AdaBoostClassifier(
base_estimator = DS,
learning_rate = 0.1,
n_estimators = 600,
algorithm = "SAMME.R")
ada_discrete.fit(X_train, Y_train)
ada_real.fit(X_train, Y_train)
ada_discrete_predictions = ada_discrete.predict(X_test)
ada_real_predictions = ada_real.predict(X_test)
ada_discrete_f1_score = f1_score(Y_test, ada_discrete_predictions, average = 'micro')
ada_real_f1_score = f1_score(Y_test, ada_real_predictions, average = 'micro')
ada_discrete_accuracy = accuracy_score(Y_test, ada_discrete_predictions)
ada_real_accuracy = accuracy_score(Y_test, ada_real_predictions)
print('Discrete AdaBoost F1 Score: ', ada_discrete_f1_score, '\n', 'Real AdaBoost F1 Score: ', ada_real_f1_score, '\n', 'Discrete AdaBoost Accuracy: ', ada_discrete_accuracy, '\n', 'Real AdaBoost Accuracy: ', ada_real_accuracy)

Kod bloğu üsttekine çok benzer olduğu için detaylara değinmeyeceğim. Tek fark bu kez işlemleri validation değil test set üzerinde gerçekleştirdik. Sonuca bakalım:

15

Gördüğümüz gibi F1 skorları birbirine çok benzer aynı zamanda accuracy değerleri de öyle. Üstte de bahsettiğimiz gibi daha optimal bir model için diğer hiperparametreler de detaylı bir şekilde belirlenmeli ve hepsi için validation set veya cross-validation metoduna başvurulmalıdır. Sonuç olarak üstteki final özelliklere sahip bir “boosted decision tree” sınıflandırıcı oluşturmuş olduk. Boosted yani güçlendirilmiş yöntemlerle ilgili bir dahaki yazıda görüşmek üzere :))