Machine Learning #1 — Gözetimli Öğrenme , Keşifsel Veri Analizi, Çapraz Doğrulama

Göker Güner
10 min readDec 1, 2020

Bu yazı, öğrendiklerimi paylaşmak/pekiştirmek amacıyla kaleme aldığım Machine Learning yazı dizisinin ilkidir. Python programlama diline ve Jupyter-notebook ortamına aşinalığınız olduğunu varsayarak anlatıyorum. Projeler içerisinde kullanacağımız kütüphaneleri terminalinizden “ pip install kutuphaneadi” formatında yazarak yükleyebilirsiniz.

Eğer bu yazı dizisinin İngilizcesiyle ilgilenmiyorsanız, bu paragrafı pas geçebilirsiniz.

This post is the first of the Machine Learning article series that I have written to share / consolidate what I have learned. I assume that you are familiar with the Python programming language and the Jupyter-notebook environment. You can install the libraries that we will use in the projects by typing in the “pip install libraryname” format from your terminal.

For English version of the article : Link

Birleşik Devletler Kongre Oturumu’ndan bir görüntü.

Makine öğrenmesi(Makine Öğrenimi) nedir?

Basitçe, bilgisayarlara veriden çıkarım yaparak karar alma yeteneği kazandırmak olarak tanımlayabiliriz. Bunu yaparken, veriyi uygun yollarla biçimlendirip öğrenme işlemini yapacak olan modele/algoritmaya input(girdi) olarak veririz. Bu şekilde, birtakım karmaşık problemleri, elimizde yeteri kadar veri varsa, adım adım kodlamamıza gerek olmadan çözebiliriz.

Örneğin:

  • Bir mailin spam olup olmadığını anlama
  • Vikipedi içeriklerini konularına göre kategorileme

Birer makine öğrenimi problemi örneğidir. Makine öğrenimi temelde üçe ayrılır:

(Supervised Learning) Gözetimli(denetimli) öğrenme: Etiketlenmiş veriden çıkarım yapmayı öğrenir.

(Unsupervised Learning) Gözetimsiz(denetimsiz) öğrenme: Etiketlenmemiş veriden çıkarım yapmayı öğrenir.

(Reinforcement Learning) Takviyeli(pekiştirmeli) öğrenme: Davranışçılıktan esinlenen, öznelerin bir ortamda en yüksek ödül miktarına ulaşabilmesi için hangi eylemleri yapması gerektiğiyle ilgilenen bir makine öğrenmesi yaklaşımıdır. Her hareketin sonunda bir ödül veya ceza verilerek bir deneğin belli bir harekete ya da hareket dizisine koşullanabileceği fikrinden ortaya çıkar.

Bu yazımızın konusu olan gözetimli öğrenme ile devam edelim.

Gözetimli Öğrenme

Girdi olarak verilen birtakım özelliklerden çıkarım yaparak verilmeyen hedef bir özelliği tahmin etme mantığına dayanır. Sınıflandırma ve regresyon olarak iki alt türü vardır.

Sınıflandırma: Hedef değişken kategorilerden oluşur.

Regresyon: Hedef değişken, kategorize edilemeyen sürekli bir veri tipindedir. Örneğin ev fiyatı tahmin eden bir regresyon modeli geliştirdiğinizde, hedef değişken olan ev fiyatı girdi olarak verdiğiniz özelliklere göre dinamik olarak değişir, kategorilendirilemez.

Buraya kadarki terimleri farklı kaynaklarda farklı isimlerle görebilmeniz mümkündür. Aşina olmanız için kısaca bilgi verecek olursak,

Özellikler = tahmin değişkenleri = bağımsız değişkenler

Hedef değişken = bağımlı değişken = yanıt değişkeni

Bu tablo, makine öğrenimi alanında başlangıçta sıkça kullanılan Iris veri kümesine aittir.

Burada dört adet tahminleyici değişkeni(predictor variables) ve modelin bu değişkenleri öğrenerek tahmin ettiği bir adet hedef değişken(target variable) görmektesiniz. Veri kümesindeki çiçeklerin taç yaprak ve çanak yapraklarına ait uzunluk ve genişlik verilerine göre fotoğrafın hangi çiçeğe ait olduğunu sınıflandıran bir uygulama yapabilirsiniz. Bu veri kümesi, bizim de kullanacağımız oldukça verimli bir Python kütüphanesi olan scikit-learn kütüphanesinde yerleşik olarak bulunmaktadır. Çağırmak için, Jupyter Notebook ortamında

from sklearn import datasets
iris = datasets.load_iris()

komutlarını çalıştırmanız yeterlidir. Ara bir not olarak, scikit-learn kütüphanesinin dokümantasyonunu oldukça iyi buluyorum. Yerleşik olarak bulunan diğer veri kümelerini incelemek isterseniz scikit-learn veri setlerini inceleyebilirsiniz.

Bu yazıda yerleşik veri kümelerini kullanmayacağız, veri ön işlemesine(preprocessing) dair birkaç konuya değinmek için, harici bir kaynaktan aldığımız ancak yine kısmen hazır verilen başka bir veri kümesiyle yolumuza devam edeceğiz.

Veriyi temizlemek, işlemek ve görselleştirmek için gerekli kütüphanelerimizi dahil ederek başlayalım.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

Numpy: NumPy (Numerical Python) bilimsel hesaplamaları hızlı bir şekilde yapmamızı sağlayan bir matematik kütüphanesidir.

Pandas: Veri analitiği alanında, özellikle veri işleme ve analizi için sıklıkla kullanılan bir Python paketidir. Veriyi Excel’e benzer bir tablo yapısında işlemenize olanak tanır ve CSV, Excel vb. pek çok farklı formattaki veriyi işlemeniz mümkün olabilir.

Matplotlib: Veri görselleştirme için kullanacağımız temel Python kütüphanesidir.

Seaborn: Matplotlib tabanlı istatistiksel bir veri görselleştirme kütüphanesidir, veri görselleştirmenin faydalarını sıradaki başlıkta göreceğiz.

Buradaki kütüphanelerle neler yapılabileceğine de farklı bir yazı dizisinde daha detaylı değineceğiz. Şimdi veri kümemizi çalışma ortamına dahil edelim.

df = pd.read_csv('house-votes-84.data')

Veri kümesini buradan aldım. Bu küme, 1984 yılındaki bir Birleşik Devletler Kongre oturumundaki oylama kayıtlarını içerir. Parti üyelerinin, hangi başlıkta ‘evet’ veya ‘hayır’ oyu verdiklerine göre hangi partiye ait olduklarını tahminlemeye çalışıyoruz, ‘Democrat’(Demokrat) veya ‘Republican’(Cumhuriyetçi).

Bu bağlantıdaki aynı isimli veri setinde az sonra göreceğimiz başlıklar yok, ancak tanımlarda her bir sütunun tanımından bahsedilmiş, bir metin düzenleyicide ilk satıra bu tanımları ekledim, dosyanın düzenlenmiş haline de yazının sonunda paylaştığım GitHub reposundan ulaşabilirsiniz.

Pandas kütüphanesini pd kısaltmasıyla dahil etmiştik. Bunu bu şekilde yapmak zorunda değilsiniz, hiçbir kısaltma kullanmayabileceğiniz gibi, farklı bir kısaltma ile de dahil edebilirsiniz ancak, global kullanım şekillerine uymakta fayda var (Bu tip standartların yanı sıra kod yazarken dikkat etmeniz gereken bazı standartlar da vardır. Örneğin değişkenleri tanımlarken a=1 yerine a = 1 şeklinde, operatörün sağına ve soluna boşluk bırakmak gibi. Bu kurallar PEP-8 standartları olarak bilinir ve eğer bir IDE kullanıyorsanız IDE’niz size kodunuzu bu standartlara uygun yazmanız için yardımcı olur. Bununla ilgili de daha detaylı bir yazı kaleme alacağım. Konumuza devam edelim.)

Keşifsel Veri Analizi (EDA — Exploratory Data Analysis)

Görsel veya nümerik yöntemlerle, veriyi bir özet üzerinden yorumlama yaklaşımıdır. Veriyi ön işleme noktasında atılması gereken adımlardan çıkarım yapmak için kullanılacak metod ve modele kadar pek çok konuda fikir sahibi olmamızı sağlar.

Numerical EDA

Veriyi nümerik olarak özetleme yaklaşımıdır. Bunun için birkaç tane pandas metodu kullanıyoruz.

df.info()
df.info() komutunun çıktısı

df.info(), veriye dair genel bir bilgi edinmemizi sağlar. Burada özelliklerin isimlerini, kaç satır ve sütünluk bir veri kümemiz olduğunu görebiliriz. 435 satır ve 17 sütundan oluşan bir veri kümemiz var. Bu kümedeki 16 değişkeni tahminleyici olarak, 1 değişkeni(parti) ise hedef değişken olarak kullanacağız. 435x17 verinin tamamının non-null olduğunu görüyoruz ki bu önemli, çünkü numpy, pandas, scikit-learn gibi kütüphanelerin doğru çalışması için bütün verinin non-null ve aynı tipte olması gerekir.

df.describe()

df.describe() ise, info ile aldığımız genel bilgiye dair başka bir özet sunar. Her bir değişkenden kaç adet olduğu veya bu değişkenlerin değerlerinin kaça ayrıldığı gibi. Burada dikkatimizi çeken bir nokta, ‘party’ için beklediğimiz gibi 2 adet (Democrat, Republican) tekil(unique) değer varken, diğer başlıklarda 3 değer var.

Bunun anlamı, özellik değerlerinin içerisinde tipi ‘null’ veya ‘NaN’ olmasa bile ön işlemeye muhtaç verilerin olduğudur. Verilere daha yakından bakalım.

Çalışmanın orjinalinde sağa doğru kaydırarak, oy kullanılan tüm başlıkları görebilirsiniz. Yine ara bir bilgi notu olarak, GitHub’ın bir hizmeti olan gist’i de çok kullanışlı buluyorum. Bir Medium yazısına nasıl Jupyter Notebook ekleyebileceğinize dair bilgi için Ersin Yıldız’ın ‘Medium Yazılarına Jupyter Notebook Nasıl Gömülür?’ başlıklı yazısını inceleyebilirsiniz.

df.head() komutu ile, varsayılan olarak ilk 5 satırdaki verileri görebiliyoruz. Eğer bu verilerde aradığımız cevabı bulamayıp daha fazla veriye ihtiyaç duyarsak komutun içerisinde istediğimiz sayıyı yazarak daha fazla miktarda veriyi görebiliriz. Örneğin;

İlk 10 satırı bu şekilde görmüş olduk. Dikkatimizi çekmesi gereken nokta, bazı değerlerin ‘?’ şeklinde verildiğidir. Bir sonraki adımda bu analizi görsel olarak da yaptıktan sonra bu konuya eğileceğiz.

Visual EDA

Bir önceki adımda nümerik olarak gördüğümüz veri kümemize bu adımda da görsel olarak bakalım. Örneğin ‘immigration’ konusunda kullanılan oyların dağılımına bakalım.

Jupyter notebook ortamına aşina olmayanlar için bir not: import ve csv’den okuma işlemlerini her adımda yinelemek zorunda değilsiniz. Orjinal çalışma notebook’unda da görebileceğiniz gibi, dosyanın başlarındaki bir hücrede bu işlemleri yapmış olmanız yeterlidir. Ben, yalnızca bu tabloyu çizdirebilmek için hepsini tek bir hücrede çalıştırdım.

Bir önceki adımda elde ettiğimiz bilgiyi doğrular bir şekilde, ‘evet’ veya ‘hayır’ dışında üçüncü bir oy şekli daha görüyoruz. Bu da çözmemiz gereken ‘?’ şeklindeki verilerdir. Bu problemi çözebilmek için scikit-learn kütüphanesinin SimpleImputer metodundan yararlanıyoruz.

from sklearn.impute import SimpleImputer
imputer = SimpleImputer(missing_values='?', strategy='most_frequent')

SimpleImputer objesinden imputer ismini verdiğimiz bir değişken yarattık. missing_values parametresine, kayıp değer olarak kabul ettiğimiz ve değiştirmek istediğimiz ‘?’ string değerini verdik. Bu parametrenin varsayılanı “NaN”’dır(Not a Number). strategy parametresini de ‘most_frequent’ olarak belirledik. Böylelikle, ‘?’ ile belirtilmiş değerler o sütunda hangi değer en çoksa ona eşitlenecek(yani sıklığına göre eğer ‘hayır’ oyları en fazlaysa ’n’ veya ‘evet’ oyları en fazlaysa ‘y’).

Not: Kendi araştırmalarınızda, internette bazı kaynaklarda bu işlem için from sklearn.preprocessing import Imputer şeklinde bir çağırma yapıldığını görebilirsiniz. Ancak bu metod yeni versiyonda kaldırılmış ve yerini bizim kullandığımız sklearn.impute.SimpleImputer metoduna bırakmıştır.

Scikit-learn dokümantasyonundan alınmış bir ekran görüntüsü. missing_values ve strategy parametrelerinin hangi değerleri alabildiğini ve bu değerlerin ne anlama geldiğini burada görebilirsiniz. Metodun diğer parametreleri için; scikit-learn dokümantasyonunu okuyup örnekleri inceleyebilirsiniz.

Sırada modelimizi belirlemek ve eğitmek var. Bu bir sınıflandırma problemi olduğu için k-NN(k-Nearest Neighbours /k-En Yakın Komşuluk) algoritmasını bu problemin çözümü için kullanabiliriz. Bu algoritmanın çalışma prensiplerine değinecek olursak;

En yakın k noktanın sınıflarına bakar,

Bu k adet noktanın çoğunluğu hangi sınıfa aitse, incelediği noktanın sınıfını da o sınıf olarak belirler.

1. Adım

Elimizde kırmızı ve yeşil noktalardan oluşan bir veri kümesi olduğunu düşünelim. Ve hangi sınıfa ait olduğunu henüz bilmediğimiz de bir noktamız var. k parametremizin değerini 3 olarak belirlersek

2. Adım

Algoritmamız bu en yakın 3 noktaya bakar, çoğunluğun kırmızı olduğunu görür ve rengi atanmamış noktaya da kırmızı rengi değer olarak atar. Eğer k değerimizi 5 olarak seçersek

3. Adım

Bu kez çoğunluk yeşil renkte olduğu için algoritmamız aradığımız sınıfın yeşil olduğuna karar verecektir.

4.Adım

kNN algoritması eğitim kümesini inceler, tahmin yapmak için tüm veri kümesindeki en yakın komşuları arar. Şimdi algoritmamızı çalışma ortamımıza dahil edelim.

from sklearn.neighbors import KNeighborsClassifier
knn_model = KNeighborsClassifier(n_neighbors=2)

Yine scikit-learn kütüphanesi kullanarak kNN algoritmamızı çağırdık ve karar verirken kullanacağı komşuluk değerini gösteren n_neighbors parametresini 2 olarak belirleyerek knn_model adını verdiğimiz değişkeni tanımladık. Sırada adımlarımızı oluşturmak var.

steps = [('imputation', imputer),
('knn', knn_model)]

Bu şekilde, adımlarımızı Python tuple’larından oluşan bir liste şeklinde kümelemiş olduk. Tanımlamış olduğumuz steps değişkenini tahminleme sürecini adım adım kodlamak yerine bir pipeline oluşturmak için kullanacağız.

from sklearn.pipeline import Pipeline
pipeline = Pipeline(steps)

sklearn.pipeline metodunu kullanarak algoritmanızı, parametrelerinizi, veri önişleme adımlarınızı tek bir değişken (bizim steps adını verdiğimiz) içerisinde gruplayarak daha derli toplu kod yazabilirsiniz.

Gelelim elimizdeki veri kümesinden eğitim ve test kümeleri oluşturmaya. Bunun için train_test_split metodunu kullanacağız. Ayrıca ‘party’ özelliğini hedef, diğer özellikleri ise tahminleyici olarak belirleyeceğiz.

from sklearn.model_selection import train_test_split
y = df['party'].values
X = df.drop('party', axis=1).values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
df[df == 'n'] = 0
df[df == 'y'] = 1

Veri kümemizdeki her bir verinin ‘party’ değerlerini y değişkenine atadık. Ardından veri kümemizden bu değerleri düşürerek geri kalan değerleri X değişkenine atadık. Böylelikle tahminleyici ve hedef değişkenlerimizi oluşturmuş olduk.

Ardından train_test_split metodunu kullanarak verimizi train ve test olarak ikiye ayırdık. Ayırırken, test_size parametresiyle test kümemizin büyüklüğünü tüm verinin %30'u olarak belirledik. Geri kalanı da eğitim(train) verisi olarak ayırmış olduk.

random_state, basitçe veriyi bölmeden önce karıştırma(shuffle) için kullandığımız bir tam sayı parametresi. İncelediğiniz eğitim, doküman vb. materyallerde genellikle bu parametrenin 42'ye eşitlendiğini görürsünüz.

Son olarak bilmemiz gereken başka bir kritik nokta da, modelimizin/algoritmamızın string tipinde verileri işleyemeyeceğidir. kNN algortimasının görsel anlatımında da gördüğümüz gibi, tüm işleyiş vektör uzayındaki bir uzaklık hesabına(Öklid uzaklığı) dayanır. Veri kümemizdeki her bir noktayı matematiksel olarak modelleyebilmek için, ’n’ ve ‘y’ olarak verilmiş değerleri 0 ve 1'e değiştiriyoruz. Biraz daha popüler Python kütüphanelerine ve uygulamalara değinmek istediğim için matematiksel anlatımlara bu yazı serisinde çok fazla yer vermeyeceğim. Sırada algoritmanın veriyi öğrenmesi ve çıkarım yapması var.

from sklearn.metrics import accuracy_score
knn_pipeline = pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
accuracy_score(y_test, y_pred)

Verinin ön işlenmesi ve öğrenme aşamalarıyla bir pipeline oluşturmuştuk. Bu noktada pipeline kullanmadan çalışmak isterseniz knn_model.fit() metoduyla direkt modelin veriden çıkarım yapmasını da sağlayabilirsiniz. Bu durumda önceki aşamaları adım adım kodlamanız gerekeceğini unutmayın.

Ardından predict metoduna test için ayırdığımız girdi kümesini vererek tahminleme yapmasını sağlıyoruz. Son olarak yine scikit-learn kütüphanesinden dahil ettiğimiz accuracy_score metodunu kullanarak başarım oranımızın yaklaşık %92.37 olduğunu görebiliriz.

Peki, bu oranı daha da yükseltebilmek için neler yapabiliriz? Başarımımızın olabileceğinden daha düşük olmasına ne sebep olmuş olabilir?

Dikkat etmemiz gereken ilk nokta, n_neighbors parametresinin 2 olarak seçilmiş olması. Bu parametrenin daha yüksek olması, belki de daha yüksek bir skor elde etmemizi sağlayabilirdi.

İkinci nokta ise, train/test olarak ayrılan veri. Biz, verinin %30'unu test verisi olarak ayırmıştık, ancak ayrılan verinin rastgele olarak seçildiğini unutmayın. Belki de, farklı %30'u test verisi olarak belirlemiş olsaydık, yani daha farklı bir %70 ile algoritmamızı eğitseydik sonuç daha farklı olacaktı. Bu iki problemi nasıl çözebiliriz?

Başarımımızı yükseltmek için kullandığımız Cross-Validation metodu için örnek bir şema.

İlk çözümümüz cross-validation(çapraz doğrulama). Bu teknik, her bir adımda farklı bir train/test ayrımının yapılmasını ve en iyi öğrenmenin seçilmesine dayanır. Örnek olarak, train/test verisinin 80/20 oranında ayrıldığı bir gösterimi temel aldım. her ayrımda(split) train/test kümelerini farklı farklı seçerek bir ölçüm(metric) alıyoruz. Bu ayrımlardan hangisi en yüksek skorlu ise, ayrımımızı o olarak seçiyoruz.

Bu tekniğimizi birleştireceğimiz asıl teknik ise grid search cross-validation. Veri kümemizi gridlere ayırarak her bir adımda sadece train/test kümesini farklı seçmekle kalmıyor, aynı zamanda farklı bir n_neighbor parametresiyle deniyoruz. Bu tekniğin Python implementasyonu için sklearn.model_selection.GridSearchCV kullanıyoruz.

from sklearn.model_selection import GridSearchCV
parameters = {'knn__n_neighbors': np.arange(1, 50)}
cv = GridSearchCV(pipeline, param_grid=parameters, cv=5)

knn algoritmasına ait n_neighbors parametresine, numpy kütüphanesinin yardımıyla 1'den 50'ye kadar değer vereceğiz. Farklı bir algoritmanın farklı bir parametresini kullanmak isterseniz ‘modelname__parameter’ şeklinde yazmalısınız. GridSearchCV metoduna önce pipeline’ımızı, daha sonra da param_grid parametresine değer olarak Python sözlüğü(dictionary) tipinde oluşturduğumuz parameters değişkenimizi atadık. cv parametresine train/test ayrımımızı kaç farklı şekilde yapmak istediğimizi de girerek, tekrar fit aşamasına geri dönüyoruz.

cv.fit(X_train, y_train)
print("Tuned KNN Parameters: {}".format(cv.best_params_))
print("Best score is {}".format(cv.best_score_))

Burada algoritmamız her bir n_neighbor değeri için veriyi 5 farklı şekilde işleyerek çalıştı, en iyi parametreyi ve skoru belirledi. cv.best_params_ metoduyla en iyi n_neighbor değerinin 6 olduğunu, cv.best_score_ ile de en iyi skorumuzun yaklaşık %93.76 olduğunu gördük.

Machine Learning yazı dizisinin sonraki konularında farklı problemlere farklı ML yaklaşımlarını, başarımı ölçmek için kullanılabilecek farklı metodları inceleyeceğiz. Faydalı olması dileklerimle.

Kaynaklar

https://archive.ics.uci.edu/ml/datasets/

--

--