Makine Öğrenmesi ile Sınıflandırma (Python) “Mushroom Dataset”
Bu yazımda, seçtiğim veri seti üzerinde, denetimli makine öğrenmesi üzerine, sınıflandırma problemine yönelik bir uygulamayı paylaşıyorum. Teorik bilgisine dair “Yeni Başlayanlar İçin Denetimli Makine Öğrenmesi” yazıma, basitçe bir veri setinin EDA’sına yönelik bilgi edinebilmek için de “EDA (Python)” yazıma göz atabilirsiniz.
Sahip olunan veri seti hangi problemlerin çözümünde kullanılabilir? Saptanmış olan problemin çözümü için hangi öznitelikler bizi anlamlı bilgiye götürür?
∘ 1. Veri Bilgisi (Data Knowledge)
∘ 2. EDA
∘ 3. MODEL
∘ 4. SONUÇ
1. Veri Bilgisi (Data Knowledge)
Yapılması gereken ilk şey, sınıflandırmak istenilen verinin incelenerek, veriye dair bilgilerin toplanmasıdır. Veride ki sınıfların ne olduğu ve bu sınıfları belirlemeye etki edebilecek öznitelikler (features), yapılan çalışma sonucu elde edilen çıktıların anlamlandırılabilmesi için çok önemlidir. Burada incelenen veri seti, The UC Irvine Machine Learning Repository’den indirildi. Veri setinin adı Mushroom. Audobon Society Field topluluğunun rehberinden edinilen bilgiler ile birlikte, mantarların fiziksel özelliklerine göre, yenilebilir veya yenilemeyen tür olup olmaması konusunda sınıflandırma yapıldı. Öncelikle değişkenlerin ne olduğunu incelemekle başlandı. Bunun için veri bilgisinin tutulduğu dosyayı okuyarak, Tablo1’de ki özellikler düzenlendi. Bunlar mantarların farklı anatomik kısımlarına ait şekil, renk gibi fiziksel özellikleri, yaşam alanları ve popülasyonları ile ilgili. Bu özelliklerin neyi ifade ettiğine dair ufak bir araştırma yapıldı. (Mantarlar hakkında literatür bilgim yok.) Ayrıca, veri setine ait bilgilerin yer aldığı dosya incelendi ve sap_kök yani “stalk_root” özelliğinde “?” ile tanımlanmış eksik verilerin varlığı tespit edildi. Python ortamına geçmek için hazırız.
2. EDA
Öncelikle değişkenlerin tutulduğu sütunların adlarının, Excel dosyasında kısaltmaları ile tutulduğu görüldü. Bu durumun, dataframe veri setini incelerken karışıklık yaratmaması için sütunların adları orijinal adları ile değiştirildi ve hedef değişken sütunu (target) dataframein sonuna alındı (Şekil1).
mushroom = mushroom.rename(columns={'p':'target','x':'cap_shape','s':'cap_surface','n':'cap_color',
't':'bruises','p.1':'odor','f':'gill_attachement','c':'gill_spacing',
'n.1':'gill_size','k':'gill_color','e':'stalk_shape','e.1':'stalk_root',
's.1':'stalk_surface_above_ring','s.2':'stalk_surface_below_ring',
'w':'stalk_color_above_ring','w.1':'stalk_color_below_ring','p.2':'veil_type',
'w.2':'veil_color','o':'ring_number','p.3':'ring_type','k.1':'spore_print_color',
's.3':'population','u':'habitat'})
sp = list(mushroom.columns)
sp[0],sp[22] = sp[22],sp[0]
mushroom = mushroom[sp]
mushroom.tail()Veriyi incelerken “stalk_root” değişkeninde eksik değerlerin olduğu öğrenilmişti. Eksik değerlerin 8123 değerin içerisinde kaç adet olduğu öğrenildi. Hedef değişkenin (target) sınıflarının dengeli mi (balance) yoksa dengesiz mi (unbalanced) olduğuna, hem bu değerler düşürülmeden (drop) önce hem de düşürüldükten sonra bakıldı. Eksik veriler satır olarak düşürülmeden önce sınıflarda gözlemlenen balanced dağılım, 2448 eksik veri düşürüldükten sonra unbalanced bir dağılım gösterdi (Şekil2). Bu bilgi aklımızda, alınan metrikler iyileştirilmeye çalışıldığında, burası tekrar değerlendirilebilir. Öncelikle, eksik verileri tüm veri setinin satırlarından düşürerek devam edildi. En sonda sütun olarak düşürüldüğünde alınan sonuçlarda incelenebilir. Ayrıca, verinin info()’suna bakıldığında tüm değişkenlerin kategorik veri olduğu gözlemlendi.
mushroom["stalk_root"].value_counts()
mushroom_new = mushroom.drop(mushroom[(mushroom["stalk_root"] == "?")].index, axis=0)
(mushroom_new["stalk_root"] == "?").sum()
mushroom.target.value_counts().plot.pie(autopct='%1.1f%%')
mushroom_new.target.value_counts().plot.pie(autopct='%1.1f%%')Her bir değişkenin, countplot yardımıyla popülasyon genelinde ki dağılımına bakıldı. Böylece veri setinin genel yapısı anlaşılabilir ve bir sınıfın diğerlerine göre aşırı temsil edilip edilmediği gözlemlenebilir. barplot yardımıyla da özelliklerin hedef değişkenle olan ilgisi anlamlandırılır. Burada grafikleri görselleştirmek için ipywidgets kütüphanesinden interact fonksiyonu kullanıldı.
‘s: batık şapka şekli’ hedef değişken üzerinde 100%’e yakın bir etki gösteriyor olsada, 5643 adet mantarın içerisinde sadece 32 adedi bu şapka şekline sahip. ‘f: düz şapka şekli’ diğer şapka çeşitleri ile kıyaslandığında, veri seti genelinde ve hedef değişkenine göre birbirine en yakın dağılımı göstermiş gözüküyor (Şekil3). Bu tarz durumlar veri setinde dikkate alınmalı çünkü gözlemlenen değerlerin arasında ki farkları anlamak, anormal bir durumu tespit edebilir. Şapka şekli dışında ki diğer veriler gözlemlendiğinde, sınıflandırmada kullanılması açısından daha uygun veriler bulundu. Bu sebeple bu özellik (feature) modele verilmeden önce düşürüldü.
mushroom_new["target"] = mushroom_new.target.map({"p":0, "e":1})
def … _box(col):
sns. … plot(data = mushroom_new,
y= mushroom_new[col],
… ,
palette='bright')
cols = mushroom_new.columns[:-1]
interact(column_box, col=cols)Şekil4’de dağılımı görselleştirilen ‘stalk_root’ değişkeninde eksik veriler tespit edildiği için bu verilerin olduğu satırlar tüm veri setinden düşürülmüştü. Bu verisetinin unbalanced eğilimini arttırmıştı. Burada belki birbirinden farklı mevcut kök çeşitlerinin, veri setinde sahip olduğu mod değerlerine bakılarak doldurulabilirdi fakat sınıflandırma için daha uygun değişkenler mevcutsa, böyle bir risk göze alınmadan da devam edilebilir. Hatta diğer değişkenlerin sonuca yönelik performansını, bu veri setinde ki ağırlıklandırması ile düşüreceği öngörülüyorsa, direkt sütun olarak da düşürülebilir. Böylece, mantar sayısında değişim yaratmadan da analize devam edilebilir. Şimdi düşürülen satırlar ile birlikte değişkenleri incelemeye devam edelim. ‘veil_type’ yani peçe tipine ait iki sınıfın (evrensel/kısmi) dağılımı incelendiği zaman, toplam 5643 adet mantarın tamamı kısmi peçe tipine aittir (Şekil5). Evrensel peçe tipine ait herhangi bir veri yoktur. Bu sebeple bu değişken sınıflandırmaya etkisi açısından anlamsızdır.
Veri hakkında denilebilir ki, mantar türlerinin yalnızca şekil veya şapkalarının görünüşlerine göre zehirli olma durumu belirlenemez. Ya da şapka rengi, tek başına bir mantarın yenilebilir olması için belirleyici bir özellik değildir. Sınıflandırmaya sokulacak değişkenler olarak burada, ‘cap_surface’, ’cap_color’, ’bruises’, ’gill_size’, gill_color’, ’stalk_shape’ ve ‘habitat’ olarak değerlendirildi. Bunlar sadece veri setinde ki genel dağılımına göre seçilmedi, hedef değişkene olan etkisi açısından da değerlendirdi. Şekil6’da veri setinde ki genel dağılımın grafikleri mevcut. Şekil7’de ise hedef değişkene yönelik dağılımlar mevcut. Aynı değişkenlerle birlikte bir de mantar sayısından düşürülmeden, ‘stalk_root’ sütununun düşürülmesi ile sınıflandırma yapılarak elde edilen metrikler, en sonda verildi.
3. MODEL
Model olarak parametrik bir model olan Logistic Regression ve karar ağaçları tabanında geliştirilmiş bir model olan Random Forest kullanıldı.
3.1. Logistic Regression
Bu başlık altında verinin ön işleme süreci de verildi. Öncelikle veri seti train ve test olarak ayrıldı. Verinin eğitim sırasında görmeyeceği oranı 20% olarak seçildi. Scikit-learn kütüphanesine ait pipeline sınıfı kullanılarak, değişkenler üzerinde yapılacak encode işlemi modelle birlikte aynı blokta yazıldı. Pipeline daha düzenli ve tekrar edilebilir kod yapısı sunar. Veride ki değişkenlerin kendilerine ait sınıfları 2’den fazla olduğu için kategorik verilerin dönüşümlerinde Ordinal Encode seçildi. Model olarak ise Logistic Regresyon kullanıldı. (Şekil8).
X_train, X_test, y_train, y_test = train_test_split(X,y, test_size = 0.2, random_state=42)
oren = OrdinalEncoder(handle_unknown='use_encoded_value', unknown_value= -1)
trans = make_column_transformer((oren,["habitat","cap_surface","cap_color","bruises","gill_size","gill_color",
"stalk_shape"]), remainder="passthrough")
modelLogit = Pipeline([("encoder", trans), ("lr", LogisticRegression(random_state=42))])
modelLogit.fit(X_train,y_train)3.2. Random Forest
Burada karar ağacı tabanlı bir algoritma olan Random Forest uygulandı (Şekil9). Parametrik bir algoritma olan Logistic Regression’dan alınan sonuç ile kıyaslandı.
modelForest = Pipeline([("encoder", trans), ("rf", RandomForestClassifier(random_state=42))])
modelForest.fit(X_train,y_train)3.3. Random Forest (Grid Search)
Aşırı uydurma ihtimaline karşı, hiperparametreleri optimize etmek için grid search yöntemi kullanılabilir. Karar ağaçlarının sayısı ile ilişkili olan hiperparametre “n_estimators”dır. Sayısı yükseldikçe model iyileşme gösterse de belirli bir sınırdan sonra aşırı uydurmaya neden olacaktır. Ağacın maksimum derinliğini belirleyen parametre ise “max_depth”dir. Bir düğümün dallamadan önce kaç adet sample içermesi gerektiği “min_samples_split” ile belirlenir. Bu hiperparametre ile model basitleştirilerek aşırı uydurmanın önüne geçilebilir. Bu yöntemin çeşitli parametreleri vardır. İhtiyaca yönelik incelenerek, içerisinden seçilebilir ve denemeler yapılarak modelin en iyi performansı yakalanabilir.
params = {"rf__n_estimators":[150,155,160],
"rf__max_depth":[6,7],
"rf__min_samples_split":[2,3,4],
"rf__min_samples_leaf":[5,6,7],
"rf__max_features" :["sqrt",0.5,0.6,0.7]}
# 5-fold cross validation ile
grid_search = GridSearchCV(estimator=modelForest3,
param_grid=params,
cv=5,
scoring=recall_five,
return_train_score=True,
n_jobs= -1)
grid_search.fit(X_train, y_train)
print(grid_search.best_params_)4. SONUÇ
Burada modelden alınan sonuçların belirli metrikler üzerinden ne kadar başarılı olunduğu görülecek. Confusion matrix, TruePositive: doğru diye tahmin edilen ve gerçekte de doğru olan doğru değerler, FalsePositive: doğru diye tahmin edilen ama gerçekte yanlış olan değerler, TrueNegative: yanlış diye tahmin edilen ve gerçekte de yanlış olan değerler, FalseNegative: yanlış diye tahmin edilen ama gerçekte doğru olan değerlerin, sonuçlarını üzerinde gösteren matristir. Confussion matrix kullanılarak elde edilen metrikler üzerinden modelin ne kadar güvenilir sonuç verdiği gözlemlenir. Özellikle sınıflar arasında dengesizlik varsa, modelin nasıl iyileştirilebileceği hakkında fikir verir. Bunlar:
Get Aysen Çeliktaş’s stories in your inbox
Join Medium for free to get updates from this writer.
— Doğruluk (Accuracy): Tüm tahminlerin, doğru tahminlere oranı. (TP + TN) / (TP + TN + FP + FN)
— Hassasiyet (Precision): Pozitif olarak tahmin edilen durumların, gerçekten pozitif olanlara oranı. TP / (TP + FP)
— Duyarlılık (Recall) veya True Positive Rate: Gerçek pozitif durumların, model tarafından doğru olarak pozitif tahmin edilenlere oranı. TP / (TP + FN)
— F1 Skoru: Precision ve recall’un harmonik ortalaması. 2 * (Precision * Recall) / (Precision + Recall)
4.1. Logistic Regression
Burada train ve test veri setleri için skorlar karşılaştırıldığında, değerlerin birbirine yakın olduğu görüldü. Model train veri setinde metrikler üzerinden iyi skorlar verirken, test veri setinde kötü skorlar vermemiş. Ayıca her ikisinde de metrikler 80% üzerinde değer vermiş. (Şekil10/Şekil11). Aşırı uyum gösterme (overfitting) ya da yetersiz uyum (underfitting) gibi bir durum gözlemlendiği söylenemez. Modelin gerçekten yenilebilir olan mantarların, ne kadarını yenilebilir olarak sınıflandırdığının oranı, duyarlılık (recall) skoru ile görülmekte. Modelin yenilebilir olarak sınıflandırdığı mantarların yüzde kaçı gerçekten yenilebilir mantar, bunu da hassasiyet (precision) değeri ile gözlemlenmektedir.
Sınıfların birbirinden ne kadar ayrıldığına dair bilgi edinimi için ROC (Receiver Operating Characteristic Curve) eğrisi ve hassasiyet-duyarlılık eğrisi (precision-recall curve) kullanılır. Bunlardan balanced dağılıma sahip veri setleri için ROC curve, unbalanced dağılım gösterenler için ise precision-recall curve tercih edilir. Bizim veri setimiz unbalanced eğilim gösteren bir veri seti bu sebeple precision-recall curve grafiği paylaşıldı. Test olarak ayrılan veri seti için ROC curveden elde edilen AUC (Area Under the Curve) 87% sonuç verirken, precision-recall curveden elde edilen AUC 86% vermiştir. (Şekil12). Bu aralıklar 80–90% arası olduğunda, model iyi bir model olarak kabul edilir. Ancak söz konusu yanlışlıkla zehirli mantar yemekse, bu oran ne kadar kabul edilmeli? Bu noktada, verilenler karşılığında istenilenler önemli. Metriklerin değerlendirme yorumları, veri setine göre değişebilir.
predictedVals = modelLogit.predict(X_test)
predictedProbs = modelLogit.predict_proba(X_test)
predictedVals_train = modelLogit.predict(X_train)
predictedProbs_train = modelLogit.predict_proba(X_train)
plot_confusion_matrix(modelLogit,X_test, y_test)
print("TEST CM\n" , confusion_matrix(y_test, predictedVals))
print("-*"*30)
print("TRAIN CM \n", confusion_matrix(y_train, predictedVals_train))
print("TEST REPORT \n", classification_report(y_test, predictedVals, digits= 3))
print("-*"*30)
print("TRAIN REPORT \n", classification_report(y_train, predictedVals_train, digits= 3))y_probas = modelLogit.predict_proba(X_test)
skplt.metrics.plot_precision_recall(y_test, y_probas)
plt.show()4.2. Random Forest
Bu model ile ulaşılan metrikler içerisinde recall değerinin 99% ile sonuç verdiği gözlemlendi. Accuracy, precision ve f-1 skorun 100%, ROC eğrisi alanının ise 1 değerini sonuç olarak vermesinden dolayı, modelin sonucunun şüpheli olma riski gözlemlendi. (Şekil13/Şekil14). Böyle bir durumda hedef değişken ile mükemmel korelasyon gösteren sızıntı özelliklerin (leakage features) olup olmadığına bakılmalı.
ypred_test = modelForest.predict(X_test)
ypred_train = modelForest.predict(X_train)
print("Evaluation metrics for test dataset:")
print("Confusion matrix:")
print(confusion_matrix(y_test, ypred_test))
print("Classification report:")
print(classification_report(y_test, ypred_test))
print("Evaluation metrics for train dataset:")
print("Confusion matrix:")
print(confusion_matrix(y_train, ypred_train))
print("Classification report:")
print(classification_report(y_train, ypred_train))
y_probas = modelForest.predict_proba(X_test)
skplt.metrics.plot_precision_recall(y_test, y_probas)
plt.show()4.3. Random Forest (Grid Search)
Buradan alınan sonuçta, grid search kullanılmayan Random Forest modeline göre metriklerden alınan skor değerleri düşmüştür. Grid search kullanılmadan alınan sonuçta f-1 skor değeri 1, grid search kullanılarak alınan sonuçta bu değer 97%’dir. (Şekil15). Burada aynı zamanda özniteliklerin model açısından önemi (feature importance) incelenebilir. Burada, kullanılan bu modele özgü, değişkenler içerisinden gelen en çok katkının “stalk_shape” değişkeninden, en az katkının ise “cap_surface” değişkeninden geldiği görülür. (Şekil16). Data leakage varsayımı göz ardı edilmemelidir.
Son olarak, “stalk_root” değişkeni sütun olarak düşürülerek bu değişkene ait eksik değerlerin (missing values) bulunduğu satırları silmeden veri tekrar hazırlandı. Böylece veri setinde ki 8123 mantar sayısı da korunmuş oldu. Bu değişken model için önemli görülmediğinden ötürü, eksik değerler satırlardan silinmiş olsa bile sütun olarak da düşürülmüştü. Bu veri seti için zaten gözden çıkarılmış bir değişkendi. Fakat bu her zaman böyle olmaz. Bazen, eksik verilerin yer aldığı değişken modelin başarılı bir sınıflandırma gerçekleştirebilmesi için çok önemli olur. Böyle zamanlarda eksik verileri kategorik ya da sayısal (numeric) olma durumuna uygun olacak şekilde mod, mean gibi değişik seçeneklerle doldurmak ya da veri sayısının azalmasını göze alarak satırlardan düşme seçenekleri değerlendirilir. Bu da veri setinin genelinde de değişiklilere sebep olur. Buraya döndüğümüzde, sütun olarak düşürdüğümüz veri setinde ki hedef değişkenin başlangıçta ki dağılımını koruyarak devam edelim. Veride ki hedef değişkeninin sınıfının dağılımı bu sefer balanced. (Şekil17)
Yazı çok uzadığı için burada direkt veride ki bu değişikliğin, sonuca nasıl etki ettiği ROC curve üzerinden gösterildi. Model olarak yukarıdaki gibi Logistic Regression uygulandı. Yukarıda uygulanan modelin ROC curve grafiği ile birlikte, verinin son halinden alınan grafik yan yana kıyaslandı. Solda bulunan grafik eksik verilerin satırlardan düşürülerek, azaltılan mantar sayısı üzerinden çalışılan modelin ROC eğrisiyken, sağda bulunan grafik ise eksik verilerin satır olarak değil de ait olduğu değişkenin sütunu düşürülerek, veri sayısı küçültülmeden çalışılan modelin ROC eğrisi. Sağda ki modele ait skorlar daha yüksek değerler ile gözlemlendi. (Şekil18).








