Evet yazılara bir süre ara verdikten sonra tekrar bir yazı ile karşınızdayım. Bu kez Python üzerinde yine çok bilinen ve kullanılan makine öğrenimi algoritmalarından biri olan Naive Bayes’i uygulayacak ve elimizdeki otel verisi vasıtasıyla değerlendirme yapacağız. Yazının işleyişi her zamanki gibi veri setinin tanıtımıyla başlayacak, daha sonra veri setini keşfederek devam edecek, veri ön işleme ve Naive Bayes algoritmalarının kullanımıyla da son bulacak. En son aşamada da tahminleme işlemini gerçekleştireceğiz. Yeri geldikçe algoritmanın teorik kısımlarına da değineceğiz. O zaman vakit kaybetmeden başlayalım.
Kullanacağımız veri seti Japonya’da bulunan 300’den fazla otelin bilgisini içeriyor ve değerlendirmesini yapıyor. Veride otelin adı, içerisinde bulunduğu şehir, fiyat, merkeze uzaklık, imkanların puanlaması, temizliğin puanlaması, lokasyonun puanlaması gibi değişkenler bulunuyor. Bu değişkenlerden yola çıkılarak da oteller için bir “rating” belirlenmiş ve bunlar da superb, fabulous, very good, good gibi kategorilerden oluşuyor. Tabii ki bunların kendi içerisinde bir sıralaması var. Biz de bu veri setinde otelin özelliklerinden yola çıkarak hangi rating kategorisine dahil olabileceğini tahminlemeye çalışacağız. Bu işlemin detaylarına sırası geldiğinde değineceğiz. İşlemlere başlamadan veriye dair bilgileri bulabileceğiniz ve veriyi indirebileceğiniz linki vereyim:
Evet şimdi yavaş yavaş işlemlere başlayalım ve “csv” formatındaki veriyi Python içerisine alalım. Aşağıdaki kod bloğu ile her zamanki gibi veriyi alıyoruz:
import os os.getcwd() import pandas as pd import numpy as np Data = pd.read_csv('Hostel.csv', sep = ',') Data_pd = pd.DataFrame(data = Data) Data_pd = Data_pd.drop(Data_pd.columns[0], axis = 1) len(Data_pd) len(Data_pd.columns) Data_pd.head()
Evet “os” kütüphanesinden üzerinde çalıştığımız klasörün hangisi olduğunu öğrendik ve daha sonra elimizdeki veriyi ya bu klasöre atarak ya da direkt olarak o klasörü verinin içinde bulunduğu klasör ile değiştirerek içeri aldık. Bunu yaparken “pandas” kütüphanesindeki “read_csv” komutunu kullandık. Bu kez açık bir şekilde verilerin virgül ile ayrıldığını da “sep” argümanını kullanarak belirttik. Sonrasında bu veriyi bir “pandas data frame” nesnesine dönüştürdük. Sonraki satırda veri setinin ilk sütununu çıkardık. Bunu yapmamızın sebebi ise o sütunun verinin satır numaralarını belirtmesiydi. Böyle bir sütuna ihtiyacımız olmadığından veriden çıkarttık. Bu işlemi “.drop” komutu ile gerçekleştirdik ve indeksler 0’dan başladığı için 0 indeksini kullandık. Son 3 satırda da veri hakkında genel bir bilgi sahibi olduk diyebiliriz:
Veri seti 342 satır(gözlem) ve 15 sütundan(değişken) oluşuyor. Son derece küçük bir veri seti diyebiliriz. Fakat sonuçta otel verilerini inceliyoruz ve elimizde 342 adet otele dair veri olması gayet normal. Ayrıca yazının amacı düşünüldüğünde de bu veri seti bizim için hem teorik hem de pratik anlamda yeterli olacaktır. Bir de veri setinin ilk birkaç satırına bakacak olursak:
Evet elimizdeki veri bu şekilde. Aradaki bazı sütunlar konsolda “…” ile atlanmış ve gösterilmiyor. Bu ayarı tabii ki değiştirebilirsiniz. Şimdi yavaş yavaş veriyi incelemeye, tanımaya ve düzeltmeye başlayalım.
Veri ile alakalı bazı temel değişiklikler yaparak başlayalım. Örneğin veriden bazı kolonları analize başlamadan önce çıkarabiliriz. En sondaki iki kolon olan “lon” ve “lat” kolonları boylam ve enlem bilgilerini veriyor. Bu tarz coğrafi verilere bambaşka bir yazıyı ayırabiliriz. Fakat bu yazıda coğrafi veriler ile uğraşmayacağımızdan bunları veri setinden çıkaracağız. Bu kolonları aşağıdaki kod bloğu ile veriden atalım:
Data_pd = Data_pd.drop(['lon', 'lat'], axis = 1)
Evet düşürecek olduğumuz kolonların adlarını “[]” ifadesi içinde belirttikten sonra “.drop” komutu ile düşürdük. Bunu yaparken eksen bilgisini de “axis = 1” ifadesi ile verdik ki sütunlarda işlem yapılsın. Veri setinin değişken isimleri de kötü görünüyor bunu da düzeltmek adına isimleri daha güzel bir biçime dönüştürelim:
Data_pd = Data_pd.rename(columns = {'hostel.name': 'HostelName', 'price.from': 'PriceFrom', 'summary.score': 'SummaryScore', 'rating.band': 'RatingBand', 'atmosphere': 'Atmosphere', 'cleanliness': 'Cleanliness', 'facilities': 'Facilities', 'location.y': 'LocationY', 'security': 'Security', 'staff': 'Staff', 'valueformoney': 'ValueForMoney'})
Evet böylece kolon adlarını da düzeltmiş olduk. Veri setinin görünümü artık daha iyi. Veriye baktığımızda hala göze çarpan birkaç nokta bulunuyor. Örneğin bağımlı değişkeni her zamanki gibi veri setinin en sonuna alabiliriz. Bu da analizlerimizi alışılmış biçimde yapmamızı sağlayacaktır. Ek olarak otel adı kolonunu da veri setinden çıkarmak mümkün. Çünkü otelin adının şu an için bir değeri yok. Fakat otellerin ratinglerinin verilmesinde isimlerinin de etkisi olabilir. Örneğin otel adında belirli bir kelimenin geçip geçmediğine ya da birden fazla kelime olup olmadığına bakılabilir ve bunun da etkisi ölçülmeye çalışılabilirdi. Fakat biz bu yazıda bunu yapmayacağımız için otel adını da veriden çıkaracağız. Son olarak, “Distance” kolonu şehir merkezine uzaklığı veriyor fakat veriler “şehir merkezine X km uzak” şeklinde yazılmış. Bu veri aslında bizim için nümerik bir veri iken, şu an kategorik gibi gözüküyor. Bu yüzden bu değişkenden de sadece kilometre değerini alıp geri kalanını atacağız:
Data_pd = Data_pd.drop(Data_pd.columns[0], axis = 1) Data_rb = Data_pd.pop('RatingBand') Data_pd['RatingBand'] = Data_rb Data_pd['Distance'] = pd.DataFrame(Data_pd['Distance'].str.split('km').tolist(), columns = ['km', 'string'])['km'] Data_pd['Distance']
Evet kod bloğunu inceleyelim ve sonrasında sonuca bakalım. İlk satırda, bahsettiğimiz gibi otel adı kolonunu veriden çıkardık. Sonrasında “RatingBand” kolonunu geçici bir data frame nesnesine atadıktan sonra, asıl veri setinin sonuna ekledik. Dikkat edelim ki “.pop” komutu bahsi geçen kolonu veri setinden düşürerek tek başına ele alıyor. Bu yüzden tekrardan kolonu düşürmeye gerek kalmadı ve sona direkt olarak ekleyebildik. Son aşamada ise, “Distance” kolonunu “.str.split” komutu ile “km” üzerinden ikiye böldük ve bunu bir listeye çevirerek iki kolondan oluşmasını sağladık. Bu kolonlara da “columns” argümanını kullanarak “km” ve “string” isimlerini verdik. Bunların tamamını kolonları rahatça alabilmek için bir pandas data frame nesnesine dönüştürdük. Zaten dönüştürmeseydik iki farklı kolondan oluşmasını sağlayamazdık. En son aşamada da tekrar [‘km’] komutu ile bu iki kolondan sadece “km” kolonunu istediğimizi söyledik ve bunu da asıl veri setimizdeki “Distance” kolonu ile değiştirdik. Sonuca baktığımızda:
Evet “Distance” kolonu artık tam istediğimiz şekilde sadece kilometre değerlerini içeriyor. Veri düzenlemesini bitirmek için son bir aşama daha kaldı fakat öncesinde bahsetmek istediğim 1-2 nokta var.
Data_pd['PriceFrom'].max() Data_pd['RatingBand'].unique()
Üstteki kod bloğu ile “PriceFrom” kolonundaki maksimum değeri ve “RatingBand” kolonundaki de tüm kategorileri görmek istedim. Sonucu incelersek:
Evet gördüğünüz gibi fiyatlardaki maksimum değer 1003200 görünüyor ki genel fiyatları inceleyecek olursanız böyle bir rakam imkansız. Bu veri 2 kez girilmiş ve bunun bir kullanıcı hatası olduğunu düşünebiliriz. Çünkü bir outlier değer olmak için bile çok büyük bir sayı ve hata olduğu aşikar. Öte yandan, bağımlı değişkenimizin kategorilerine bakacak olursak önceden saydığımız kategoriler dışında bir de “Rating” diye bir kategori görüyoruz ki bu da anlamsız görünüyor. Bu iki problem için de bir şeyler yapacağız. Bahsettiğimiz maksimum fiyat değerini içeren iki satırı veri setinden çıkarabiliriz. Fakat Rating kategorisini içeren satır sayısına bakacak olursak:
(Data_pd['RatingBand'] == 'Rating').sum()
Bu kategoriyi içeren 8 satır bulunuyor. Zaten az satır bulunan bir veri setinden 8 satırı çıkarmak istemediğimizden, bunları “NA” yani kayıp veriye çevirip daha sonra en sık görülen kategoriye çevirerek icabına bakabiliriz. Öncelikle ilk bahsettiğimiz problemi, o satırları çıkararak çözelim ve daha sonra ikinci problemdeki satırların bağımlı değişken değerlerini de kayıp veriye çevirelim:
np.where(Data_pd['PriceFrom'] == 1003200)
Numpy kütüphanesinden “.where” komutu ile bahsettiğimiz değeri içeren satır numaralarını aşağıdaki gibi aldık:
Ardından 289 ve 316 numaralı satırları veri setinden çıkarıyoruz:
Data_pd = Data_pd.drop(Data_pd.index[[289, 316]])
“.drop” ve “.index” komutlarını kullanarak istediğimiz indeksteki satırları veri setinden çıkardık. Son olarak “Rating” kategorisini içeren satırların bağımsız değişkenlerini de kayıp veriye çevirelim:
Data_pd = Data_pd.replace('Rating', np.nan)
Evet direkt olarak “Rating” değerini içeren neresi varsa “np.nan” ile kayıp veriye çevirdik. Bu kayıp verileri daha sonra zaten ilgili bölümde halledeceğiz.
Şimdi veri setindeki kolonlar için doğru veri tiplerini belirleyerek devam edebiliriz. Bir örnek vermek gerekirse, “Distance” kolonunu bu şekilde ayırdık fakat hala istediğimiz biçimde bir veri tipine yani nümerik tipe dönmedi. Bunun gibi başka durumlar diğer kolonlarda da var mı kontrol etmek ve varsa düzeltmek adına veri setindeki kolon tiplerini inceleyelim:
Data_pd.dtypes
Sonuç:
Görüleceği üzere “Distance” kolonu hala “object” tipinde duruyor ve nümerik olması gerek. Aynı şekilde “City” ve “RatingBand” değişkenlerini de kategorik tipe çevirelim ki üzerlerinde işlemlerimizi rahatça gerçekleştirebilelim.
for col in Data_pd.iloc[:, [0, 11]]: Data_pd[col] = Data_pd[col].astype('category') for col in Data_pd.iloc[:, [2]]: Data_pd[col] = Data_pd[col].astype('float64') Data_pd.dtypes
Üstteki for döngüleri ile 0 ve 11 indekslerindeki kolonları yani “City” ve “RatingBand” kolonlarını “category” tipine, 2 indeksli “Distance” kolonunu da yine “float64” tipine çevirdik. Kontrol edecek olursak:
Evet veri tipleri istediğimiz hale gelmiş durumda. Artık veriyi keşfetmeye başlayabiliriz. Burada bize en çok yardım edecek araçlar korelasyon analizi, histogramlar, kutu grafikler gibi görsel araçlar olacaktır. Örneğin otel rating bandlarına göre fiyatları inceleyelim:
import matplotlib.pyplot as plt Data_pd.boxplot(column = ['PriceFrom'], by = ['RatingBand'], grid = False) plt.ylim((5000, 800))
matplotlib kütüphanesinden pyplot modülünü indirdikten sonra “.boxplot” komutunu kullanarak “PriceFrom” kolonunun “RatingBand” kolonuna göre bölünmesini istedik ve kutu grafiği oluşturduk. “.ylim” komutunu ise y ekseninin ölçeğini ayarlamak için kullandık:
Evet grafiğe bakacak olursak; beklenildiği gibi en yüksek fiyat medyan değerine sahip olan kategori “superb” kategorisi. Ayrıca bu kategorinin dağılımı son derece simetrik gözüküyor. “Good” kategorisine baktığımızda ise dağılım son derece sola çarpık. “Good” kategorisi dışında diğer kategorilerin medyan değerlerine baktığımızda aslında arada çok da büyük farklar olmadığını görüyoruz. Bu da bize fiyat dışındaki etmenlerin otel ratinginin belirlenmesinde daha büyük etkileri olabilceğine dair bir ipucu veriyor. Aynı kutu grafiği bir de imkanlara bakarak çizelim:
import matplotlib.pyplot as plt Data_pd.boxplot(column = ['Facilities'], by = ['RatingBand'], grid = False) plt.ylim((5000, 800))
Sonuç:
Bu kez medyanlar arası farklar çok fazla ki “Superb” kategorisindeki otellerin imkan puanlarının yüksekliği rahatça görülebiliyor. Çizdiğimiz kutu grafikler bize aslında rating belirleme konusunda otelin sunduğu imkanların fiyattan daha önemli olabileceğini gösteriyor.
Biraz da güvenlik ve personele verilen puanların dağılımı nasıl bunu görelim. Bunu görmek için de histogramlardan faydalanacağız:
Data_pd.hist(column = ['Security'], grid = False) Data_pd.hist(column = ['Staff'], grid = False)
Evet “.hist” komutu içerisine istediğimiz kolonların adlarını vererek histogramları çizdirdik:
Histogramlara bakacak olursak aslında genel olarak hem güvenlik hem de personel puanlamaları yüksek bu yüzden de net olarak sola çarpık bir dağılım var. Son derece düşük puan alan otel sayısı çok az görünüyor. İki değişken de aslında 9-10 değerleri arasında puan içeren çok fazla gözlem barındırıyor.
Bir de verideki değişkenler arası korelasyonu inceleyelim. Aşağıdaki kod bloğu ile genel korelasyon haritasını görebiliriz:
Data_pd.corr(method = 'pearson')
“.corr” komutunu kullanarak “pearson” korelasyon katsayılarını hesapladık ve korelasyon matrisini aşağıdaki gibi oluşturduk:
Değerlere baktığımızda birbirleri ile yüksek korelasyonlu olan kolonlar az çok göze çarpıyor. Örneğin Para-değer ilişkisini içeren kolon ile imkanlar arasında 0.77 civarı bir korelasyon var ki bu gayet normal. Son olarak, veri setinin tanımlayıcı istatistiklerine de bakarak bu bölümü kapatalım:
Data_pd.describe(include = 'all')
Sonuç:
Tanımlayıcı istatistiklerde de örneğin fiyatlarda ortalama değerin 2536 civarı olduğunu, maksimum değerin ise 7600 olduğunu görüyoruz. Standart sapma ise 866 civarında ki bu da fiyatların gayet değişken olduğuna işaret ediyor. Kategorik değişkenlere de bakacak olursak bağımlı değişkendeki en sık kategorinin “Superb” olduğu görülüyor. Aynı şekilde 4 farklı kategori olduğu da “unique” satırından anlaşılıyor. Bu şekilde tabloyu genişleterek tüm kolonlar hakkında bilgi sahibi olabilirsiniz. Biz veri keşfetme aşamasını burada bitirerek, “pre-processing” yani ön işleme aşamasına geçeceğiz.
İlk yapacağımız işlem kayıp verileri tespit edip buna bir çözüm aramak olacak. Daha önce de yaptığımız gibi bu işleme hem satır hem de sütun bazında bakacağız. Öncellikle kaç sütun ve satırın kayıp veri içerdiğine bakalım.
len(Data_pd[Data_pd.isnull().any(axis=1)]) for col in Data_pd.columns: print(col , sum(pd.isnull(Data_pd[col])))
Kodun ilk satırında “.isnull” komutuna “.any” ifadesini ekleyerek herhangi bir kayıp veri içeren satır sayısının kaç olduğunu öğrendik. İkinci satırda ise bir for döngüsü yardımıyla her kolon için “.isnull” komutunu kullandık ve böylece her kolonda haç kayıp veri olduğunu gördük. Sonuçlara bakalım:
Evet kayıp veri miktarları bu şekilde. 23 satır kayıp veri içerirken kolonların da ilk üçü hariç tamamında kayıp veri bulunuyor. Satır ve sütun bazında bunlar çok da büyük oranlar olmadığından dolayı “imputation” metodunu kullanarak kayıp veri problemini çözelim:
from sklearn.preprocessing import Imputer imputer_numeric = Imputer(missing_values = 'NaN', strategy = 'mean', axis = 0) imputer_numeric = imputer_numeric.fit(Data_pd.iloc[:, 1:10]) Data_pd.iloc[:, 1:10] = imputer_numeric.transform(Data_pd.iloc[:, 1:10]) Data_pd = Data_pd.apply(lambda x:x.fillna(x.value_counts().index[0]))
Imputation için kullandığımız üstteki kod bloğunu açıklamak gerekirse; “sklearn” kütüphanesinden “preprocessing” modülünü indirdik ve “Imputer” sınıfını çağırdık. Buradan aynı isimli fonksiyonu kullanarak kayıp verilerin ortalama ile değiştirilmesini “strategy = ‘mean'” argümanı ile belirttik. Sonrasında nümerik kolonların indekslerini yazarak “.fit” metodu ile “fitting” işlemini yaptıktan sonra “.transform” metodu ile aynı kolonlara “imputation” metodunu uyguladık. Bu işlem nümerik kolonlar içindi. Kategorik kolonlar için de “.apply” metodunu kullanarak o kolonlardaki kayıp verilerin, ilgili kolonun en sık görülen kategorisi ile (yani en baştakiyle: index[0]) değiştirilmesini söyledik. Sonuç:
Görüleceği gibi kayıp veriler satır ve sütun bazında artık sıfır.
İkinci aşamada kategorik verilerin kodlanma işlemini tamamlayalım. Elimizde iki tane kategorik değişken var. Bunlardan biri “City”, diğeri ise “RatingBand”. “City” değişkenini one-hot olarak kodlayacağız çünkü kategorileri arasında herhangi bir üstünlük veya sıralama söz konusu değil.
X = Data_pd.iloc[:, 0:10].values Y = Data_pd.iloc[:, 10].values from sklearn.preprocessing import LabelEncoder, OneHotEncoder LE = LabelEncoder() X[:, 0] = LE.fit_transform(X[:, 0]) OHE = OneHotEncoder(categorical_features = [0]) X = OHE.fit_transform(X).toarray() LE_Y = LabelEncoder() Y = LE_Y.fit_transform(Y) print(X)
Kod bloğunu açıklayalım. Öncelikle bağımlı ve bağımsız değişkenleri ayrı ayrı “numpy array” nesnelerine dönüştürdük ve Python’a tanıttık. Sonrasında yine gerekli kütüphaneden gerekli modülleri indirdik ve bunları kendi adlarını kullanarak nesneler biçiminde tanımladık. Öncelikle “LabelEncoder” nesnesini tanımladık ve “City” değişkeni üzerinde “.fit_transform” metodu ile beraber kullandık. Sonrasında artık kodlanmış olan “City” değişkeni üzerinde bu kez one-hot kodlama gerçekleştirdik. Burada LabelEncoder’dan farklı olarak OneHotEncoder fonksiyonu içerisinde kategorik değişkenin indeksini belirttik böylece aşağıdaki bir kez daha belirtmemize gerek kalmadı. Ayrıca bağımlı değişken için de LabelEncoder kullanarak onu da kodlamış olduk. Sonucu da yine bir “array” tipine dönüştürdük. Son duruma bakacak olursak:
Evet ilk kolonlarda görülebileceği gibi “City” kolonu artık “one-hot” bir biçimde kodlanmış durumda. Bu işlemden sonra geçelim son aşamaya.
Ön-işleme aşamasının son adımı olan “scaling” ya da standardize etme işlemini de gerçekleştirelim:
from sklearn.preprocessing import StandardScaler sc = StandardScaler() X = sc.fit_transform(X) print(X)
Her zamanki gibi gerekli kütüphaneden gerekli modülü indirdikten sonra tanımladık ve “.fit_transform” metoduyla da uyguladık. Sonucu görelim:
Evet standartlaştırma işlemini de tamamladığımıza göre artık veriyi train ve test set olarak ikiye ayıralım ve artık makine öğrenimine başlayalım:
from sklearn.model_selection import train_test_split X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size = 0.15, random_state = 0) len(X_train) len(X_test)
Scikit-learn kütüphanesi içerisinden gerekli modülü indirdikten sonra “train_test_split” fonksiyonunu kullanarak veriyi ikiye ayırdık. Bu kez test setini %15 verdik çünkü veri setinin boyutu zaten küçük olduğundan training setini daha fazla azaltmak istemedik. Sonucu görelim:
Görüleceği gibi train set içerisinde 289, test set içerisinde ise 51 gözlem var. Veri ön-işleme aşamasını böylece bitirdik. Fakat hatırlarsınız ki ön-işleme aşamasını bitirdikten sonra makine öğrenimine geçmeden önce, veride sınıf dengesizliği olup olmadığını kontrol ediyorduk. Eğer dengesizlik var ise başka yöntemlere başvuracağız. Aşağıdaki kod ile bakalım:
Counts = pd.DataFrame(Data_pd.groupby('quality').size()) Counts
Bu kontrolü, training sete bakarak da yapabilirdik. Fakat zaten oranlar bakımından aynı oldukları için, bir fark olmayacaktır. Ama dikkat edilmesi gereken nokta, SMOTE metodunu training set üzerinde uygulayacağımızdır.
Sonuç:
Görüleceği gibi yine sınıf dengesizliği bulunuyor ve biz de bu durumda her zaman uyguladığımız SMOTE tekniğini uygulayarak çözmeye çalışacağız:
from collections import Counter from imblearn.over_sampling import SMOTE dict = {0:300, 1:250, 2:400, 3:270} smote = SMOTE(ratio=dict, k_neighbors=5, 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)))
Üstteki kod bloğunda yine gerekli modülleri gerekli kütüphanelerden indirdik ve sonrasında bir “dictionary” içerisinde her kategorinin kaç gözleme sahip olmasını istediğimizi belirttik. Bir sonraki adımde “SMOTE” fonksiyonu içinde 5-NN algoritmasını kullanarak yapay gözlemleri tahmin etmesini istedik ve bu metodu da “.fit_sample” ifadesi ile training set üzerine uyguladık. Sonuçta gözlem sayıları aşağıdaki gibi oldu:
Evet sınıf dengesizliği problemini bir nebze giderdik. Şimdi makine öğrenimi tarafına geçmeden önce kullanacağımız algoritmanın nasıl çalıştığına bir bakalım.
Bu konuyu öğrenmek için tabii ki Bayes Teoremi’ni bilmek gerekir. Çoğunuzun zaten bildiğini düşünsemde kısaca bahsedeyim. Bayes teoremi aslında koşullu olasılığın belirlenmesinde kullanılan bir formüldür. Bu formül, elde bulunan tahminlerin, yeni kanıtlar geldikçe güncellenmesini ve revize edilmesini sağlar. Bayes teoreminin formülü aşağıdaki gibidir:
Muhtemelen hepimizin aşina olduğu bu formülün aslında anlatmak istediği, bir olayın olma olasılığının, elde edilen yeni bilgiler ışığında nasıl etkilendiğidir. Tabi bunu yaparken bu yeni bilgilerin de doğru olduğunu varsayarak yapar. Üstteki formüle baktığımızda, elimizde B bilgisi var iken, A’nın oluşma olasılığının ne olacağının formüle edildiğini görüyoruz. Bu da aslında A’nın olma olasılığı ile, elde A’nın bilgisi var iken B’nin olma olasılığının çarpımının B’nin olasılığına bölünmesi ile elde edilmiştir.
Peki Naive Bayes algoritması bunun neresindedir? Bu algoritma da aslında oluşturduğu sınıflandırıcıyı(classifier), Bayes Teoremi’ne dayanarak oluşturur. Elimizde bir veri seti olduğunu düşünelim ve bağımsız değişkenlerin oluşturduğu seti X ile, bağımlı değişkeni Y ile gösterelim. Bu durumda aslında bizim modellemek istediğimiz olasılık, üstteki Bayes formülünde de olduğu gibi P(A|B), yani bizim senaryomuzda P(Y|X) olacaktır. Bu olasılığı da aşağıdaki formül ile temsil edebiliriz:
Öncelikle X’in nasıl bir vektör oldunu göstermek adına üstteki resmi koydum. X, tüm bağımsız değişkenleri yani özellikleri içinde bulunduran bir vektör ve n özellikten oluşuyor. Bu durumda P(Y|X), üstteki formül ile modellenebilir. Formülün detayına bakarsak, aslında Bayes Teoremi’nin bir uygulaması olduğunu görebiliriz. Çok karışık gözükse de, şimdi en basit haliyle açıklamaya çalışacağım. Formülün aslında bize söylediği; elimize yeni bir gözlem yani X değeri geldiğinde(ki bu artık elimizde X’in bilgisinin olduğunu belirtiyor) Y’nin herhangi bir değerinin ortaya çıkma olasılığının ne olacağı. Y’nin her değerinin ortaya çıkma olasılığını hesaplayabilirsek, bunlardan en büyüğünü seçerek aslında istediğimiz sonucu da bulmuş olacağız. Bu da şu şekilde hesaplanıyor; (Y’nin değeri i olarak kabul edildiğinde, elimize yeni gelen X’in ortaya çıkma olasılığı) * (Y’nin değerinin i olma olasılığı) / (Y’nin her bir değeri için, yeni elde ettiğimiz X değerinin ortaya çıkma olasılıklarının, Y’nin o değerinin ortaya çıkma olasılığı ile çarpımlarının toplamı). Payda aslında paydaki ifadenin her Y değeri için yazılmış ve bunların toplanmış hali. Bu uzunca yazdığım açıklama aslında üstte bahsettiğimiz Bayes Teoremi’nin alt tarafa uygulanmasından başka bir şey değil. Dikkatli okuyacak olursanız üsttekinin sadece uzun versiyonu.
Naive Bayes algoritması ile ilgili bir önemli nokta da, “koşullu bağımsızlık” varsayımı yapmasıdır. Bu ifadeyi de açıklarsak; X,Y ve Z olmak üzere 3 tane rastgele değişkenimiz olsun(bunlar rastgele değişken kümeleri de olabilir). Elde Z’nin değeri var iken, X’in olasılık dağılımı Y’nin değerinden bağımsız ise, X, Y’ye göre koşullu bağımsızdır denir. Bu varsayım Naive Bayes algoritmasının, P(Y|X) (istediğimiz olasılık) olasılığını modelleyebilmesi için ihtiyaç duyduğu gözlem sayısını ciddi derecede azaltır fakat gerçek makine öğrenimi senaryolarında çoğunlukla sağlanamamaktadır. Fakat buna rağmen, Naive Bayes algoritması başarılı sonuçlar vermektedir. Daha fazla teoride boğulmadan, Naive Bayes algoritmasının yeni bir gözlem değeri geldiğinde bağımlı değişkeni tahmin ettiği son formülasyonun bilgisini verelim:
Bu da aslında üstte bahsettiklerimizden farksız bir formül. Baktığımızda aslında yeni bir gözlem değeri geldiğinde Y’nin her olası değeri için olasılıkları hesaplıyor ve aralarından maksimum olanını seçiyor. Bu da zaten bizim istediğimiz sonucu veriyor. Burada dikkat ederseniz payda kısmı da aslında Yk’dan bağımsız durumda ve bizim formülümüz bu olasılığı maksimum yapacak Yk değerini arıyor. Bu yüzden paydayı da tamamen formülden çıkarabiliz ve Naive Bayes algoritmasının formülü tamamen aşağıdaki basit formüle döndürülebilir:
Evet biraz fazla teorik oldu fakat Naive Bayes algoritmasının genel çalışma yapısı bu şekilde. Aslına bakarsanız elimize yeni bir gözlem geldiğinde, Naive Bayes o gözlemin özellik değerlerini alıyor ve bu formülden geçiriyor. Bu formül de bağımlı değişkenin her bir kategorisinin bu şartlar altında yani elimize gelen yeni gözleme göre oluşma olasılığını hesaplıyor. Bunlardan en yüksek olasılığa sahip olanını da sonuç olarak seçiyor. Artık uygulamasına geçelim.
Uygulayacağımız sınıflandırıcı, Gaussian Naive Bayes olacak. Detayına değinecek olursak, aslında Naive Bayes algoritmasının farklı versiyonları sadece P(X|Y)’nin dağılımı hakkında farklı varsayımlar yapar. Gaussian Naive Bayes’in formülü de aşağıdaki gibi:
Sizin de farkedeceğiniz gibi normal dağılım formülüyle yakından alakalı bir formül. Uygulamasına geçersek:
from sklearn.naive_bayes import GaussianNB GaussianClassifier = GaussianNB() GaussianClassifier.fit(X_train, Y_train)
Yine Scikit-learn içerisinden modülümüzü indirdik ve “GaussianNB” fonksiyonunu kullanarak sınıflandırıcı nesnesini oluşturduk. Sonrasında ise “.fit” metoduyla training set üzerinde eğittik. Şimdi tahminleme aşamasına geçelim:
y_pred = GaussianClassifier.predict(X_test) from sklearn.metrics import confusion_matrix ConfMatrix = confusion_matrix(Y_test, y_pred) print(ConfMatrix) from sklearn.metrics import accuracy_score accuracy_score(Y_test, y_pred)
Kod bloğu içerisinde “.predict” metodu ile tahminleri gerçekleştirdikten sonra “confusion_matrix” modülünü indirerek doğru ve yanlış tahminleri karşılaştıracağımız matriksi oluşturduk. Sonrasında ise “accuracy_score” metodu ile de accuracy oranını hesapladık. Sonuçları inceleyelim:
Confusion Matrix’in köşegeni üzerindeki veriler doğru tahmin ettiklerimiz. Gördüğünüz gibi Gaussian Naive Bayes algoritması çoğunu doğru tahmin etmiş. Accuracy oranı da %76 görünüyor ki bu kadar az training verisine ve algoritmanın ayarlanacak hiperparametresi olmamasına rağmen güzel bir sonuç. Naive Bayes algoritmasının en çok uygulandığı alanlar metin sınıflandırması ve maillerin spam filtrelerinin oluşturulmasıdır. Bu algoritma buralarda kaydadeğer başarılar sağlamıştır. Aynı zamanda hızlı eğitildiğinden dolayı da çok tercih edilmiştir.
NOTLAR
- SMOTE metodunun kullandığı algoritma ve ürettiği gözlem sayıları ile oynanarak değişik başarı oranları elde edilmeye çalışılabilir.
- Naive Bayes algoritmasının hiperparametreleri bulunmadığı için Hiperparametre Ayarlama(Hyperparameter Tuning) aşamasını gerçekleştirmedik.
- Elimizdeki değişken sayısı zaten az olduğu için, herhangi bir boyut indirgeme(Dimensionality Reduction) tekniği uygulamaya gerek görmedik.
REFERANSLAR