Python Üzerinde Destek Vektör Makineleri Kullanarak Şarap Kalitesi Belirleme

Bu yazıda, sınıflandırma(classification) problemlerinde çok kullanılan ve en iyi yöntemler arasında sayılan bir makine öğrenmesi modelini Python üzerinde uygulayacağız. Bahsettiğimiz model sizin de muhtemelen aşina olduğunuz Destek Vektör Makineleri(Support Vector Machines) olacak. Bu modeli kullanarak kırmızı ve beyaz şarapların kalite sınıflandırmasını yapacağız. Bu kez konu da geçmiş konulara göre hayli ilginç olacak. Buna ek olarak, verideki her özelliğin işimize yarayıp yaramayacağı ile alakalı da bu kez farklı bir önlem alacağız. Daha önceki yazılarımızda “Feature Selection” yani değişken seçimini hem kendimiz el ile, hem de algoritmaya bırakarak onun yapmasını sağlayarak gerçekleştirmiştik. Fakat bu kez farklı bir işlem uygulayacak ve “Dimensionality Reduction” yani boyut düşürme gerçekleştireceğiz. Hem bu konunun, hem de SVM algoritmasının detaylarına yazıda yeri geldikçe değineceğim. Şimdi devam edelim. Öncelikle her zaman yaptığımız gibi veri setini tanıyalım, amacımızın ne olduğundan bahsedelim ve veri setinin kaynağının nerde bulunduğunu gösterelim.

Verinin linki aşağıdadır:

Wine Quality Data Set

Veri seti, Portekiz “Vinho Verde” şarabının kırmızı ve beyaz biçimlerinin incelemesini barındırıyor. Girdi verisi bazı testler sonucunda ortaya çıkan PH değeri, yoğunluk, sülfür dioksit miktarı, alkol miktarı gibi değerleri içeriyor. Çıktı yani bağımlı değişken ise şarap uzmanları tarafından yapılan 3 değerlendirmenin median değerini içeriyor. Şaraplar 0’dan 10′ a kadar en kötüden başlamak üzere en iyiye doğru sınıflandırılmış. Biz de özellik(feature) değerlerini kullanarak bu kalite sınıfını belirlemeye çalışacağız.

İşlemlerimize yine veri setini Python üzerine alarak başlayalım.

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

Her zamanki gibi çalışma klasörünü öğrendikten sonra veriyi oraya kopyaladık. Alternatif olarak tabii ki çalışma klasörünü de değiştirebilirdik. Çoğunlukla kullandığımız kütüphaneleri indirdik ve daha sonra “pd.read_csv” komutuyla veriyi içeri aldık. Veriyi bir “pandas” data frame nesnesine dönüştürdükten sonra satır ve sütun boyutlarını aynı zamanda da veri setinin giriş satırlarını inceledik. Sonuç:

1

Veri setinde 6497 satır ve 13 sütun var. Ya da bir başka değişle 13 değişkenin 6497 gözlemi var şeklinde de okuyabiliriz. Hatta bu şekilde okumak daha doğru olacaktır. Bu kez veri seti geçmiş yazılara göre daha ufak. Böyle bir veri seti seçmemin nedeni geçen seferki “Boosting” yazısında algoritma çalışmalarının oldukça uzun sürmesi ve bunun yarattığı sorunları çözmek. Bu sayede modelin daha kısa sürede oluşmasını ve efektif bir şekilde yazıyı tamamlamak istedim. Veri setinin ilk satırlarını da inceleyelim:

2

Evet “head” komutunun sonucu da bu şekilde. Görüldüğü üzere üstte bahsettiğimiz gibi değişkenleri ağırlıklı olarak şarabın kimyasal özellikleri oluşturuyor.

Devam edelim. Data preprocessing aşamasına geçelim ve neler yapmamız gerektiğine bakalım. Veri setindeki değişkenlerin tiplerine ve kayıp veri oranlarına bakalım. Daha sonra ise kategorik değişkenlerle ile ilgili gerekli nümerik kodlamaları ve kayıp verilere nasıl bir metod uygulayacağımızı belirleyelim.

Data_pd.dtypes

Sonuç:

3

Evet görüldüğü üzere bir kolon kategorik geriye kalanlar ise float ve integer. Bu durumda kodlamamız gereken tek değişken “type”. Birde kayıp veri oranlarını inceleyelim ve belli bir eşiğin üzerinde kayıp veri içeren satır veya sütunları çıkaralım. Bunu yapmamızın sebebi “imputation” veya diğer metodların yüksek kayıp veri içeren sütunlarda uygulanmasının uygun olmaması. Çünkü bu durumda kolona yüksek düzeyde “bias” katmış olabiliriz. Bu da kolonun doğal yapısının bozulmasına sebep olacaktır. Kayıp veri oranlarını inceleyelim:

Data_pd.isnull().sum(axis=0)
Data_pd.isnull().sum(axis=1)

Sonucu inceleyelim:

45

Evet aslında bu noktada ilginç bir durumla karşı karşıyayız. Veri setinde kayıp veri içeren satır veya sütun bulunmuyor. Bu biraz iyimser bir durum. Aslına bakarsanız bayağı iyimser bir durum. Veriye dair genel bir bakış açısı kazanmak adına aşağıdaki kod bloğunu çalıştıralım.

Data_pd.describe(include='all')

Sonuç:

6

Evet aslında bu komut bir veri setini tanımak adına çok kullanışlı bir komut. Örneğin; “type” kategorik değişkenin 6497 gözlemi olduğunu, 2 seviyesi(kategorisi) olduğunu ve bunların 4898 tanesinin “white” yani beyaz şarap olduğunu görebiliyoruz. Ek olarak ortalama, standart sapma gibi nümerik değerler de “NaN” olarak gözüküyor ki bu gayet doğal. Çünkü bunları nümerik değişkenlerde hesaplayabiliyoruz. Nümerik değişkenlere bakarsak bu tarz değerler hesaplanmış fakat bu kez de kategorik değişkenlerde bulunan özellikler “NaN” olarak gözüküyor. Bu komut vasıtasıyla veri setine dair genel bir kanıya sahip olduk. Devam edelim.

Bu aşamada veride dengesiz sınıf problemi olup olmadığını incelemek de faydamıza olacaktır. Çünkü artık biliyorsunuz ki böyle bir problem ile karşı karşıya isek, bunun için alacağımız önlemler farklı olmak zorunda. Aşağıdaki kod bloğu ile inceleyelim:

Groups = (Data_pd.groupby('quality').size())/len(Data_pd)
print(Groups)

Sonucu inceleyelim:

7

Evet dağılım bu şekilde. Dağılımın dengesiz olduğunu net olarak görebiliyoruz. Örneğin 6 numaralı kalite grubu %43 oranında bulunurken, 4 numaralı grup %3, 3 numaralı grup %0.04 ve 9 numaralı grup ise %0.007 oranında bulunuyor. Bu değerlere göre bu problem aslında bir “outlier detection” problemine çok benzer. Bu değerler bize bir “outlier” oranını hatırlatıyor. Bu yüzden biz de önlemlerimizi buna göre alarak devam edeceğiz. Bunu görselleştirerek sınıf dengesizliğini daha net görelim. Bu kez yüzdeleri değil sayıları kullanalım:

Counts = pd.DataFrame(Data_pd.groupby('quality').size())
Counts

import matplotlib.pyplot as plt
Counts.plot(kind='bar')
plt.title('Quality Classification')
plt.xlabel('Quality')
plt.ylabel('Count')
plt.grid(which='major', linestyle='-', linewidth='0.5', color='green')
plt.grid(which='minor', linestyle=':', linewidth='0.5', color='black')
plt.show()

Üstteki kod bloğu ile “matplotlib” kütüphanesini indiriyoruz. Bu kütüphane veri görselleştirmede aktif olarak kullanılan ve başta gelen kütüphanedir. “Counts” adlı bir nesne oluşturup grup dağılımını o nesneye atıyoruz ve daha sonra “plot” komutunun içine “kind=’bar'” argümanını veriyoruz böylece bar plot istediğimizi belirtiyoruz. Sonrasında eksenlerin ve grafiğin ismini ayarladıktan sonra arka planı “.grid” komutu ile düzeltiyor ve grafiği gösteriyoruz:

8

Evet sınıf dengesizliği buradan da net olarak görülebiliyor. 5,6 ve 7 numaralı sınıflar domine ederken, 3 ve 9 neredeyse görülemeyecek kadar az. Bu da az önce yaptığımız çıkarımı güçlendiriyor.

Data preprocessing aşamasına geçerek devam edelim. Kayıp veri olmadığı için, o kısımla alakalı bir işlem yapmayacağız. Bu yüzden ikinci aşama olan kategorik değişkeni kodlama kısmına geçelim. Elimizdeki veri setinde hatırlarsınız ki sadece bir adet kategorik değişken vardı. Bu değişkeni kodlayarak bu kısmı da bitireceğiz. Aşağıdaki kod bloğuyla gerçekleştirelim:

X = Data_pd.iloc[:, 0:12].values
Y = Data_pd.iloc[:, 12].values

from sklearn.preprocessing import LabelEncoder, OneHotEncoder
LE_X = LabelEncoder()
X[:, 0] = LE_X.fit_transform(X[:, 0])
OHE = OneHotEncoder(categorical_features=[0])
X = OHE.fit_transform(X).toarray()

print(X)

Evet üstteki kod bloğunu açıklayalım. İlk bölümde veri setindeki hangi değişkenlerin bağımlı ve hangi değişkenlerin bağımsız olduğunu Python’a tanıtıyoruz. Burada “.iloc” komutu ile beraber bu kolonların indekslerini vererek “.values” ile de bunları istediğimiz formata çevirdik. Verisetinde 13 değişken olduğundan ve Python’da indeksler 0’dan başladığından dolayı indeks numaralarını da bu şekilde verdik. Burada neden bağımsız değişkenlerde “0:12” aralığını verdiğimi soracak olursanız, bu indeks dizisinin başlangıcı kapalı, sonu açık aralık yani son indeks numarasını değil bir öncekini seriye katıyor. İkinci bölümde, “sklearn” kütüphanesinin “preprocessing” modülünden gerekli kodlayıcıları çağırdık ve bunların nesnelerini tanımladık. Kategorik değişkenin indeksi 0 olduğu için öncelikle “LabelEncoder” nesnesine “.fit_transform” metoduyla bu değişkeni kodlamasını söyledik. Bu kodlama unutmayın ki “Red” ve “White” kategorilerini 1 ve 2 olarak kodluyor. Fakat bizim bu kategorik değişkenimizde kategoriler arasında bir seviye farkı bulunmadığından dolayı, bu 1 ve 2 sayılarının işleme girdiğinde normal sayılar olarak algılanmasına neden olacak böylece bir seviye farkı varmış gibi işleme girecekler. İşte bu durumun önüne geçmek adına “One Hot Encoding” metodunu kullandık. Bu metoddan bahsetmiştik. Bu metod “type” değişkenini iki değişkene ayıracak ve bir tanesi “Red”, bir tanesi de “White” için olacak. Eğer şarap kırmızı ise “Red” kolonunda 1 “White” kolonunda 0, beyaz ise tam tersi olacak. Bu metodun kodlanmasında bir önceki metoda göre farklı olduğu nokta ise kategorik değişkenin indeksinin nesne tanımlanırken içeride veriliyor olması. Son olarak “.toarray” komutu da bu verileri bir “numpy array” türüne dönüştürüyor. Sonuca bakacak olursak:

9

Evet ilk iki kolon az önce bahsettiğimiz “dummy” kolonlar. Böylece kategorik değişkenleri kodlama işini çözdük. Devam edelim.

Şimdi yine daha önceki yazılarımızda uyguladığımız “Feature Scaling” aşamasını gerçekleştireceğiz. SVM algoritmasında işimize yarayacağı için bunu gerçekleştiriyoruz. Aşağıdaki kod bloğu ile uygulayalım:

from sklearn.preprocessing import StandardScaler
sc = StandardScaler()
X = sc.fit_transform(X)

print(X)

Evet burada da yine gerekli kütüphaneden modülü indirdik ve bu modülün bir nesnesini oluşturduk. Daha sonra da “fit_transform” metodu ile veriyi bu şekle dönüştürdük ve işlemi tamamladık. Sonuca yine bakalım:

10

Data preprocessing aşamasında böylece son bir adım kaldı. Onu da aşağı yukarı tahmin ediyorsunuzdur. Veriyi train ve test set olmak üzere ikiye bölmek. Burada yine dikkat çekmek istiyorum ki validation set oluşturmuyorum. Çünkü elimizde zaten 6000 küsür gibi az miktarda bir veri var. Bu böyle bir veri miktarına sahipsek bizim için en uygun metod cross-validation olacaktır. Devam edelim:

from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, random_state=0)
len(X_train)
len(X_test)

Burada da verinin %80’ini train sete, geriye kalan %20’sini ise test sete ayırdık. Fonksiyon içerisindeki “random_state” argümanı ise her çalıştığında aynı sonucun üretilmesini sağlayan bir sabitleyici. Veri kümelerinin büyüklüklerine bakarak doğru çalışıp çalışmadığını inceleyelim:

11

Evet gördüğünüz gibi train setin büyüklüğü 5197 iken test setin büyüklüğü 1300 civarı. Bu da oranlamanın doğru olduğunu gösteriyor. Dikkat edelim ki yine ana veride olduğu gibi train ve test setlerde de bağımlı ve bağımsız değişkenleri ayırdık. R üzerinde çalışırken böyle bir şey yapmıyoruz fakat Python üzerinde bu şekilde çalışıyoruz.

Hatırlayalım ki veri setinde sınıf dengesizliği vardı. Bu yüzden daha önceki yazılarımızda olduğu gibi SMOTE metoduyla veri setini dengeleyip her sınıfa yeteri kadar gözlem sağlayalım:

from collections import Counter
from imblearn.over_sampling import SMOTE
dict = {3: 1000,
        4: 1000,
        5: 2300,
        6: 2300,
        7: 1500,
        8: 1000,
        9: 1000}
smote = SMOTE(ratio=dict,
              k_neighbors=3,
              random_state=0,
              kind='regular')
X_train, Y_train = smote.fit_sample(X_train, Y_train)
print('Yeni grup sayıları: {}'.format(Counter(Y_train)))

Evet kod bloğunu açıklayalım. İlk aşamada yine gerekli modülleri indirdikten sonra bir “dictionary” tanımladık ve her sınıf için istediğimiz gözlem miktarını bu nesne içinde belirttik. Sonrasında “SMOTE” metodu ile “ratio” argümanına bu nesneyi verdikten sonra 3-NN algoritması ile yeni gözlemlerin oluşmasını istedik. Son olarak yine “random_state” ile her zaman aynı sonuçlar doğmasını sağladıktan sonra “kind” argümanı ile de normal versiyonun uygulanmasını istedik. Bu metodu “fit_sample” ile training sete uyguladıktan sonra yeni grup sayılarını yazdırdık:

25

Gördüğünüz gibi verideki sınıfların sayılarını bu şekilde değiştirdik. Veri setini bölme işlemini de tamamladık ve SMOTE metodunu uyguladık. Artık “Data Preprocessing” aşamasının sonuna gelmiş bulunuyoruz. Şimdi artık veride bulunan bu özelliklerden hangilerinin işimize yarayıp yaramayacağına karar verme zamanı geldi. Üstte de bahsettiğimiz gibi burada boyut düşürme işlemi uygulayacağız. Öncelikle neden boyut düşürmeye ihtiyaç duyduğumuzdan bahsedelim, sonrasında ise hangi metodu kullanarak boyut düşürme uygulayacağımızı anlatalım.

Makine öğrenmesi literatüründe çok bilinen bir terim vardır: “Curse of Dimensionality”. Bu terim aslında bizi şunu anlatır; verideki boyut(değişken/özellik) sayısı büyüdükçe ortaya bazı problemler çıkmaya başlar. Bunlardan ilki hesaplama yüküdür. Doğal olarak ne kadar fazla kolonunuz varsa hesaplama yükünüz de o kadar artacaktır. Büyük makine öğrenimi projeleri düşünülürse buralarda zaten kullanılan kolon sayısı fazladır ve bu da büyük bir hesaplama yükü getirir. Bu yüzden bu kolon sayısını düşürmek adına çarelere başvurulur. Bir diğer sakınca ise boyut miktarı arttıkça veride çok daha fazla oranda gözleme ihtiyaç duyulmasıdır. Çünkü çok boyutlu bir ortamda artık elinizdeki gözlem sayısı yetersiz kalıyorsa veriniz o ortam için çok seyrek bir duruma düşer. Yani elinizdeki veri setinin tüm boyutlarını temsil edecek ve hepsindeki paternleri tespit etmeye yetecek bir veri miktarına sahip olmanız gerekir. Aksi takdirde herhangi bir algoritma bu konuda yetersiz kalacaktır. Ne demek istediğimizi bir görsel ile anlatayım:

12

Üstteki görseli inceleyelim. İlk görselde tek boyut var ve gördüğünüz gibi veri sayısı bu boyut için gayet sık ve yeterli durumda. Bu şartlar altında makine öğrenimi algoritmaları bu verinin bu boyuttaki ya da değişkendeki paternlerini rahatça tespit edebilir. İkinci görselde boyut sayısı ikiye çıktığında veri iki boyutlu düzleme göre biraz daha seyrekleşmeye başlıyor. Yani iki boyut için yeterli gözlem sayısı olup olmadığı tartışmaya açık bir hale geliyor. Algoritmaların başarısı burada düşmeye başlıyor. Son görselde ise boyut sayısı üçe çıkıyor ve gördüğünüz gibi çoğu yer boş kalıyor. Veri seyrek ve yetersiz. Bu da bu üç boyut için algoritmaların yeterli veri görememesi ve patern tespiti yapamamasına neden oluyor. Yani veri setindeki tüm boyutları temsil edecek ve algoritma için bu boyutların hepsine dair örnekler sağlayacak veri grupları artık bulunamıyor. Daha fazla değişken sayısı veriye girdiğinde artık görselleştirmek bizler için imkansız olacağından burada bırakalım. Zaten bu üç görsel bahsettiğimiz durumu açıklamak için yeterli.

İşte bu üstte saydığımız sebeplerden dolayı verideki boyut sayısı düşürülmek istenir. Bunu da yapmanın iki yolu vardır:

  1. Değişken Seçimi (Feature Selection)
  2. Değişken Çıkarımı (Feature Extraction)

Değişken seçimi için bazı yazılarımızda kendimiz algoritmalar çalıştırmış, bazılarında ise algoritmanın otomatik olarak yapması için kendisine bırakmıştık. Şimdi ise ikinci seçeneği yani değişken çıkarımını uygulayacağız. Bu işlemi de PCA(Principal Component Analysis) ya da Asal Bileşenler Analizi vasıtasıyla yapacağız. Şimdi biraz da bu yöntemden ve nasıl çalıştığından bahsedelim. Bu yöntemi çok fazla terminolojiye değinmeden açıklamaya çalışacağım.

PCA metodunun genel amacı verideki değişkenliği en yüksek düzeyde açıklayan asal bileşenleri tespit etmek ve böylece bunların dışındakileri atarak veri boyutunu düşürmektir. Çünkü değişkenliği ne kadar iyi açıklarsanız tahminleriniz de o denli güçlü olacaktır. PCA öncelikle boyut düşürmek için eldeki veriyi sıfıra merkezleyerek aynı lineer regresyon işleminde olduğu gibi veriye oturan en iyi çizgiyi tespit eder. Bu çizgiyi tespit ederken kullandığı optimizasyon yöntemi ise veri noktalarının bu doğruya izdüşümlerinin orijine olan toplam uzaklıklarının karelerinin toplamını maksimize etmektir. Tespit edilen ilk çizgiye Principal Component 1 ya da PC1 denilir. Bu çizginin oluşumunda hangi değişkenin ne kadar payı olduğu da bu çizginin eğimine tüm değişkenlerin ne kadar katkıda bulunduğu hesaplanarak elde edilir. Daha sonra bu çizgiye dik olmak ve orijinden geçmek üzere PC2, PC3 ve diğer asal bileşenler oluşturulur. Aynı şekilde bunların oluşumuna da hangi değişkenin ne kadar katkısı olduğu eğimlerine tüm değişkenlerin katkıları ölçülerek hesaplanır. Daha sonra bu asal bileşenlerin verideki değişkenliği ne kadar hesapladığı belirlenir. Bu belirleme işleminde de üstte bahsettiğimiz veri noktalarının bu doğruya izdüşümlerinin orijine olan toplam uzaklıklarının karelerinin toplamı, toplam gözlem sayısının bir eksiğine bölünür. Böylece her asal bileşenin değişkenlik açıklama oranı tespit edilmiş olur. Son aşamada ise en çok değişkenlik açıklama oranına sahip olan asal bileşenler seçilir ve geriye kalanlar kullanılmaz. Böylece veri boyutu düşürülmüş olur. Terminolojide Eigenvector, SVD(Singular Value Decomposition), Variation gibi bir çok terim geçiyor. Ben burada bunlara pek fazla girmeden, özet bilgi vermeye çalıştım. Örneğin SVD ifadesi, üstte bahsettiğimiz izdüşümlerin orijine olan mesafelerinin kareleri toplamının kökünü ayrıştırma işlemine denmektedir. Bu sayının kökü bize “Singular Value” değerini verir. Bunu ayrıştırma işlemine de “Singular Value Decomposition” ya da kısaca SVD denmektedir. Bunlara dair internette zaten birçok bilgi bulunuyor bu yüzden detay vermeye gerek görmedim.

Vakit kaybetmeden uygulamaya geçelim ve nasıl çalıştığını görelim:

from sklearn.decomposition import PCA
PCA = PCA(n_components=None,
          random_state=0)
X_train = PCA.fit_transform(X_train)
X_test = PCA.transform(X_test)
Exp_Var = PCA.explained_variance_ratio_

np.set_printoptions(suppress=True)
print(Exp_Var)

Evet üstteki kod bloğunu açıklayalım. İlk satırda her zamanki gibi gerekli modülü indirdik. Daha sonra “PCA” fonksiyonunu kullanarak asal bileşen analizini uygulamaya geçtik. Burada “n_components” argümanı kaç tane asal bileşenin seçileceğini belirliyor. Buraya “None” girmemin sebebi ise, bunu henüz bilmiyor olmamız. Yani kaç asal bileşenin verideki değişkenliğin anlamlı bir miktarını açıklamaya yeteceğini henüz bilmiyoruz. Bu yüzden buna “None” dedik. “.fit_transform” metodu ile train set üzerinde PCA algoritmasını uyguladık ve bu uygulanmış algoritmayı kullanarak test seti de dönüştürdük. Son satırda yaptığımız ise tam olarak üstte sorduğumuz soruya cevap vermek. Yani, kaç tane asal bileşene ihtiyacamız olacağını bilmediğimiz için, “Exp_Var” adlı bir nesne oluşturduk ve her asal bileşenin açıkladığı değişkenlik oranını oraya attık. En son bu listeye bakacak ve verideki değişkenliğin anlamlı bir miktarını açıklamaya kaç tane asal bileşenin yettiğini göreceğiz. Daha sonra da bu algoritmayı bu kez belirlediğimiz asal bileşen sayısını seçmesini isteyerek çalıştıracağız. Böylece hem veri setindeki boyut adedini düşürmüş olacak hem de değişkenliğin büyük bir kısmını hala açıklayabiliyor olacağız. “print” komutundan önceki satır sonuçların bilimsel gösterimde gelmemesini sağlıyor. Sonucu görelim:

26

Evet sonucu inceleyelim. İlk asal bileşen değişkenliğin %37’sini, ikinci %20’sini açıklıyor. Diğerlerinin de açıklama oranlarını görebiliyoruz. İlk 6 asal bileşene bakarsak, bunlar toplamda verideki değişkenliğin %88 civarını açıklıyorlar. Bu yüzden sadece bunları kullanmak bizim için anlamlı olacaktır. Sadece bunları kullanarak veri setini de 13 boyuttan 6 boyuta düşürmüş olacağız. Şimdi “n_components” argümanını 6 yaparak algoritmayı tekrar çalıştıralım. Çünkü üstteki kod bloğu yine 13 adet asal bileşen hesapladı ve veri hala 13 boyutlu. Biz ise 6 boyuta düşmesini istiyoruz bu yüzden tekrar çalıştırmamız gerek. Çalıştırmadan önce konsolu sıfırlayın veya yazı boyunca çalıştırdığımız tüm kodları yeniden çalıştırın böylece train ve test setlerimizin ilk haline geri dönmelerini sağlayalım. Çünkü algoritmayı bu ilk haller üzerinde çalıştırmamız gerekiyor. Sıfırlama işlemini yaptığınızı varsayarak devam ediyorum. Aşağıdaki kod bloğu ile işlemi gerçekleştirelim:

from sklearn.decomposition import PCA
PCA = PCA(n_components=6,
          random_state=0)
X_train = PCA.fit_transform(X_train)
X_test = PCA.transform(X_test)
Exp_Var = PCA.explained_variance_ratio_

np.set_printoptions(suppress=True)
print(Exp_Var)

print(X_train)

Sonuç:

27

Tam istediğimiz gibi. Az önceki ilk 6 asal bileşen hesaplandı. Artık verinin yeni halini görelim:

28

Gördüğünüz gibi veride artık 13 değil 6 kolon var. Yani boyut sayısını 13’ten 6’ya düşürdük. Tabii ki test setin de aynı şekilde boyutu düşürüldü fakat göstermeye gerek görmedim.

Evet artık her şey hazır. SVM algoritmasını kullanarak tahmin yapma işlemine geçebiliriz. Öncelikle SVM algoritmasının teorik altyapısına değinelim. SVM’de temel amaç, veri sınıflarını birbirlerinden büyük bir boşluk ile ayırmaktır. Başka bir deyişle, iki boyutlu veri düşünürsek, SVM veri sınıflarının arasına öyle bir çizgi çekmek ister ki bu sınıflar birbirinden büyük bir boşlukla ayrılmış olsun. Yani ortadaki bu çizgiye en yakın olan veri noktalarının dahi bu çizgiye uzaklığı maksimum düzeyde olsun. Bir görsel ile destekleyecek olursak;

20

Evet ne demek istediğimiz üstteki görsel ile daha iyi anlaşılabilir. Farkettiyseniz ortada bir çizgi var(yeşil tırtıklı çizgi) ve bu çizgi veriyi mümkün olan en büyük boşluk ile ayırmış durumda. En büyük boşluk hedefine ulaşmak istememizin nedeni ise veriyi en büyük boşluk ile ayırmayı başardığımızda, yeni veri noktalarının tahmininden yüksek düzeyde emin olacak olmamız. Yanlardaki iki siyah çizgi ise her sınıfın ortadaki çizgiye en yakın gözlemlerinin üzerinden geçiyor. Ortadaki çizgiye “Karar Sınırı” diyoruz. Siyah çizgiler üzerindeki noktaların karar sınırına olan uzaklıkları ise “Margin” yani kenarlar olarak adlandırılıyor. SVM algoritmasının amacı da bu kenarları maksimize etmek. Siyah çizgiler üzerinde karar sınırına en yakın olan bu veri noktalarına “Support Vectors” yani destek vektörleri denmektedir. Karar sınırını destekleyen noktalar bunlar olduğu için bu ad verilmiştir. SVM algoritması için optimizasyon problemi artık destek vektör noktalarının kenarlarının en uzak olacağı bir karar sınırı belirlemektir. Tabii ki üstteki görsel iki boyutlu bir veri için geçerli. Boyut sayısı arttığında karar sınırı bir düz çizgi olmaktan çok, artık bir “Hyperplane” yani uzayda bir ayırım düzlemi halini almaya başlar. Fakat görsel amaçlar için iki boyutlu veri üzerinde inceleme yapmak daha uygun olduğu için buradan anlattım. Bu süreçlerin arkasında tabii ki detaylı matematiksel işlemler mevcut. Örneğin “margin” yani kenarlar “functional” ve “geometric” kenarlar olmak üzere ikiye ayrılıyor. Optimizasyon probleminin çözülmesinde de “Lagrange Duality” gibi üst düzey matematiksel yöntemler kullanılıyor. Bunların detayları internette makalelerde bulunabilir. SVM algoritmasının diğer algoritmalardan bariz bir farkı da bulunuyor ve bu fark onun başarısında önemli bir rol oynuyor. Yukarıdaki görseli incelediğimizde SVM algoritmasının karar çizgisini çizerken destek vektörlerden yani sınıfların en ucundaki ve boşluğun sınırında olan veri noktalarından yararlandığını görebiliyoruz. Bu da aslında SVM’in veri sınıflarındaki diğer sınıflara en yakın yani en benzer olan gözlemlere bakmasını sağlıyor. Birbirine en benzer ama farklı sınıflara ait olan gözlemleri birbirinden ayırdığından dolayı da SVM algoritması bu çizgiyi bulabilirse başarılı bir karar sınırı çizmiş oluyor.

Evet buraya kadar anlattıklarımız arasında dikkat çekmemiz gereken bir nokta var. Bu nokta verinin “Linearly Separable” yani doğrusal olarak ayrılabilen bir veri olduğu. Fakat gerçek senaryolarda durum her zaman bu şekilde olmayacaktır. Veri eğer doğrusal ayrılabilir değilse bir çizgi ile karar sınırı çekmek imkansız olacaktır ve bu durumda başka çözümlere gidilecektir. Bu duruma sunulan bir çözüm, veri setini bir üst boyuta bir “mapping” fonksiyonu vasıtasıyla taşımak ve burada doğrusal olarak ayırdıktan sonra eski boyutuna bu sonucu yansıtarak karar sınırını çizmektir. Fakat bu işlemin hesaplama maliyeti tahmin edebileceğiniz gibi büyük olacaktır. Bu yüzden bu duruma çözüm olarak makine öğrenimi literatüründe çok bilinen bir yol geliştirilmiştir: “The Kernel Trick”. Bir kernel vasıtasıyla doğrusal olarak ayrılamayan bir veri aynı bir üst boyuta çıkarılıyormuş gibi ayrılabilir ve karar sınırı çizilebilir. Fakat aslında tüm işlemler verinin kendi boyutunda gerçekleşmektedir. Kernel ile kastettiğimiz ise aslında bir fonksiyondur. Çok bilinen kernellar arasında Gaussian(ya da Radial Basis), polynomial ve sigmoid kernelları sayabiliriz.

21

Örneğin bu görsel Gaussian Kernel görseli. Normal dağılıma aşina olanlar için yabancı bir görsel değil. Bu kernel veriye yansıtıldığında aynı aşağıdaki gibi bize veriyi ayırma olanağı sunacaktır:

22

Solda verinin kendisi, sağda ise kernel uygulanmış hali bulunuyor. Görüleceği üzere kernel veriyi bir hyperplane ile ayırmamıza müsade ediyor. Her makine öğrenimi algoritmasında olduğu gibi tabii ki kernelda da ayarlanmak üzere hiperparametreler bulunuyor. Örneğin bizim de kullanacağımız kernel olan gaussian kernel için “sigma” hiperparametresi, kernel fonksiyonunun genişliğini tayin eder ve optimize edilmesi gerekir. Daha fazla teorik detaya girmeden uygulamaya geçelim.

Biz uygulama aşamasında hem lineer hem de radial kernel kullanarak SVM algoritmasını deneyeceğiz. İkisinin sonuçlarının nasıl değişkenlik gösterdiğini de böylece incelemiş oluruz. Vakit kaybetmeden başlayalım:

from sklearn.svm import SVC
from sklearn.model_selection import GridSearchCV
parameters = [
    {'C': [0.1, 0.5, 1, 10, 50, 75, 100, 500, 1000, 3000], 'kernel': ['linear']},
    {'C': [0.1, 0.5, 1, 10, 50, 75, 100, 500, 1000, 3000], 'gamma': [0.1, 0.3, 0.5, 0.8, 1, 2, 5, 7, 10, 20, 50, 100], 'kernel': ['rbf']}
]
GridSearch_SVM = GridSearchCV(estimator=SVC(),
                              param_grid=parameters,
                              scoring='accuracy',
                              cv=5,
                              n_jobs=-1)
GridSearch_SVM = GridSearch_SVM.fit(X_train, Y_train)
GridSearch_SVM_Model.best_score_
GridSearch_SVM_Model.best_params_

Evet yine kod bloğunu açıklayalım. Öncelikle her zamanki gibi gerekli modülleri indirdik. Daha sonra test edeceğimiz hiperparametre uzayını “dictionary” tipinde Python’a nesne olarak verdik. Lineer SVM için test edilecek tek hiperparametre “C” yani maliyet parametresi. Bu parametre aynı önceki yazılarda yaptığımız gibi overfitting’i önlemek adına modelin loss fonksiyonuna uygulanan bir ekstra maliyet diyebiliriz. Radial SVM için ise yine aynı C hiperparametresi ile beraber bir de fonksiyonun genişliğini ayarlayan “gamma” hiperparametresi için bir uzay tanıttık. Sonrasında sınıflandırıcılarımızı “SVC” fonksiyonu ile gerçekleştirip “kernel” argümanına kernel tiplerine verdikten sonra “random_state” argümanı ile de sabit sonuçlar talep ettik. Grid Search Cross-validation işlemi, belirlediğimiz hiperparametre uzayının her ikili kombinasyonu için “accuracy” değerlerini “cv” argümanında belirttiğimiz gibi 5-fold cross-validtion yaparak belirledi. Daha sonra bu belirlediği metodu da “.fit” işlemini kullanarak training sete uyguladık ve en son aşamada da “.best_params_” ile hangi iki hiperparametrenin en iyi sonuçları verdiğini öğrendik. GridSearchCV algoritmasının içindeki “n_jobs” argümanı ise paralel çalışmaya izin veriyor. Evet sonuçları inceleyelim:

33

Algoritma Radial SVM için 15 maliyet değeri ile beraber 5 gamma değerini seçmiş. Şimdi bu belirlediğimiz hiperparametreler ile kuracağımız modellerin doğruluğunu ölçmek adına tekrar cross-validation yapalım. Biliyorsunuz ki normalde modelin test set üzerindeki performansına bakarak tahminlemedeki başarsını ölçüyoruz. Fakat bu yöntemin şöyle bir sakıncası var; sadece bir veri seti üzerinde performans ölçmek, varyans yani değişkenlikten yana sıkıntı çekmemize sebep olabilir. Hatırlayalım ki varyanstan kastımız oluşturduğumuz modelin farklı veri setler üzerinde yaptığı tahminlerin birbirlerine ne kadar yakın veya uzak olduğuydu. Eğer modelin performansı veri setinden veri setine çok fazla değişiyorsa bu bizim için iyi bir şey değildir ve model tutarsızdır demektir. İşte sadece bir veri setine bakarak performans ölçtüğümüzde varyansa dair herhangi bir çıkarım yapamıyoruz çünkü model sadece tek bir veri seti üzerinde performans göstermiş oluyor. Bu yüzden cross-validation metoduyla training set üzerinde 5 farklı ufak veri setinde performans hesaplayacak ve ortalama alacağız. Sonuçta da varyansı da işin içine katmış olacağız. Aşağıdaki gibi gerçekleştirelim:

from sklearn.model_selection import cross_val_score
SVMModel = SVC(C=15,
               gamma=5,
               kernel='rbf',
               random_state=0)
SVMModel_CV = cross_val_score(SVMModel,
                              X_train,
                              Y_train,
                              cv=5,
                              scoring='accuracy')

SVMModel_CV.mean()

Burada da “cross_val_score” modülünü indirdikten sonra modelleri üstte belirlediğimiz hiperparametreleri kullanarak oluşturduk ve daha sonra “cross_val_score” fonksiyonu ile 5-fold CV yöntemini uyguladık. Son olarak da oluşan “accuracy” değerlerinin ortalamasını aldık:

29

Evet baktığımız zaman gaussian kernel modelin cv’de aldığı ortalama accuracy değeri %83 civarı. Aslında bu değer üstte “Grid Search” ile uyguladığımız metodta en iyi parametrelerin verdiği sonuçla aynı. Yani modelin training set üzerindeki ortalama cross-validation skoru %83. Şimdi en iyi parametreler ile olan modeli training sete fit edip test üzerinde deneyelim.

SVMModel_Final = SVC(C=15,
                     gamma=5,
                     kernel='rbf',
                     random_state=0)

SVMModel_Final.fit(X_train, Y_train)

y_pred = SVMModel_Final.predict(X_test)

Evet “Grid Search” algoritmasının seçtiği en iyi hiperparametre değerleri ile bir Gaussian SVM modeli kurup bunu training sete fit ediyoruz. Daha sonra da test set verilerini de tahmin ediyoruz. Modeli aşağıdaki kod bloğuyla değerlendirelim:

from sklearn.metrics import accuracy_score, cohen_kappa_score, confusion_matrix
cm = confusion_matrix(Y_test, y_pred)
acc = accuracy_score(Y_test, y_pred)
kappa = cohen_kappa_score(Y_test, y_pred)

Gerekli modülleri indirdikten sonra Confusion Matrix, accuracy ve geçen yazımızda bahsettiğimiz Cohen’s Kappa skorunu üstteki fonksiyonlar aracılığıyla hesaplıyoruz. Sonuçları görelim:

3132

Evet confusion matrix’te köşegen üzerindeki değerler doğru tahminleri, geriye kalanlar kaçanları gösteriyor. Kaçan bir çok veri olduğunu görebiliyoruz. Accuracy değeri ise %63 yani çok da iyi bir değer değil. Kappa değeri de gerçek sınıflarla tahmin edilen sınıfların orta düzeyde örtüştüğünü gösteren 0.43 civarında bir değer.

Bu sonuçları genel olarak incelediğimizde geniş bir hiperparametre uzayı taramamıza rağmen SVM algoritması en iyi hiperparametreler ile dahi çok iyi bir sonuç elde edemedi. Bu da bu veri setinde bize başka algoritmalar kullanmamızı tavsiye ediyor aslında.

 

NOTLAR

  1. Değerlendirilen hiperparametre uzayı ve SMOTE metodunun ürettiği yapay gözlem sayısı ile oynanarak, modelin accuracy değeri yükseltilebilir. Fakat yine de çok fazla değişmeyecektir.
  2. Modelin maliyet fonksiyonu ile oynanarak, veride daha az bulunan sınıfları doğru tahmin edememesi durumunda daha çok maliyete maruz kalması ayarlanabilir. Bu da performansa etkide bulunacaktır.
  3. PCA analizi sonrasında oluşan asal bileşenlerden daha fazla alınarak accuracy skoru yükseltilmeye çalışılabilir. Fakat geriye kalan asal bileşenler değişkenliğin az bir miktarını açıkladığından, bunların getireceği hesaplama yükü artıracakları accuracy değerinden daha yüksek olacaktır.

 

REFERANSLAR

cs229-notes3.pdf erişimi için tıklayın

http://scikit-learn.org/stable/supervised_learning.html#supervised-learning

1106.1813.pdf erişimi için tıklayın

Bir Cevap Yazın

Aşağıya bilgilerinizi girin veya oturum açmak için bir simgeye tıklayın:

WordPress.com Logosu

WordPress.com hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Google fotoğrafı

Google hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Twitter resmi

Twitter hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Facebook fotoğrafı

Facebook hesabınızı kullanarak yorum yapıyorsunuz. Çıkış  Yap /  Değiştir )

Connecting to %s