Sitemap
Türkiye Yayını

Herkesin Anlatacak Bir Hikâyesi Vardır. Hikâyelerimizde Buluşalım. | mediumturkiye.com |

Makine Öğrenmesi ile Sınıflandırma (Python) “Mushroom Dataset”

9 min readFeb 16, 2024

--

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.

Press enter or click to view image in full size
[yazar tarafından Canva’da hazırlandı]

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.

Tablo1. Veri seti

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()
Press enter or click to view image in full size
Şekil1. Veri setinin not defterinde görseli [yazarın not defterinden]

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%%')
Press enter or click to view image in full size
Şekil2. Solda ki grafik, eksik verilerin düşürülmediği hedef değişkenin sınıf dağılımı, sağdaki grafik ise düşürülmüş halinin sınıf dağılımı. e:edible, p:poisonous [yazarın not defterinden]

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)
Press enter or click to view image in full size
Şekil3. Soldaki grafik countbox ile şapka şekline ‘cap_shape’ ait değişkeninin, veri seti genelinde ki dağılımını, sağdaki grafik barplot ile şapka şekline ait değişkenin, hedef değişken ile olan ilişkisini göstermektedir. [yazarın not defterinden]

Ş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.

Press enter or click to view image in full size
Şekil4. Soldaki grafik countbox ile sap köküne ‘stalk_root’ ait değişkeninin, veri seti genelinde ki dağılımı; sağdaki ise barplot ile görselleştirilen hedef değişken ile olan ilişkisi. [yazarın not defterinden]
Press enter or click to view image in full size
Şekil5. ‘veil_type’ yani peçe tipi, u: evrensel ve p: kısmi olarak ayrılmıştır. Toplam 5643 adet mantarın tamamı kısmi peçe tipine ait, değişken sınıflandırmaya etkisi açısından anlamsızdır. [yazarın not defterinden]

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.

Press enter or click to view image in full size
Şekil6. Sınıflandırma modeli için seçilen değişkenlerin, popülasyon genelinde dağılımları. [yazarın not defterinden]
Press enter or click to view image in full size
Şekik7. Sınıflandırma modeli için seçilen değişkenlerin, hedef değişkene göre dağılımları. [yazarın not defterinden]

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)
Press enter or click to view image in full size
Şekil8. Kullanılan model (Logistic Regression) [yazarın not defterinden]

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)
Press enter or click to view image in full size
Şekil9. Kullanılan model (Random Forest) [yazarın not defterinden]

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))
Press enter or click to view image in full size
Şekil10. Logistic regression modeli için Confussion Matrix [yazarın not defterinden]
Şekil11. Logistic Regression kullanılan model metriklerinin değerleri [yazarın not defterinden]
y_probas = modelLogit.predict_proba(X_test)
skplt.metrics.plot_precision_recall(y_test, y_probas)
plt.show()
Şekil12. Logistic Regression kullanılan modelin Precision-Recall Curve grafiği [yazarın not defterinden]

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()
Şekil13. Random Forest kullanılan model metriklerinin değerleri [yazarın not defterinden]
Şekil14. Random Forest kullanılan modelin Precision-Recall Curve grafiği [yazarın not defterinden]

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.

Şekil15. Grid search kullanılan Random Forest model metriklerinin değerleri [yazarın not defterinden]
Şekil16. Değişkenlerin modele verdikleri katkıların gösterimi [yazarın not defterinden]

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)

Şekil17. Eksik veriler (missing values) düşürülürken (drop) veri sayısının korunduğu veri setinde ki hedef değişkenin (target) sınıf (class) dağılımı. [yazarın not defterinden]

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).

Press enter or click to view image in full size
Şekil18. Solda bulunan grafik eksik verilerin satırlardan düşürelerek çalışıldığı modelin ROC eğrisi; sağda bulunan grafik eksik verilerin ait olduğu değişkenin sütunu düşürülerek çalışılan modelin ROC eğrisi. [yazarın not defterinden]

--

--