Makine Öğrenmesinde Normalizasyon ve Standardizasyon ile Python Uygulaması

Yiğit Şener
6 min readFeb 12, 2021
Photo by Drew Perales on Unsplash

Makine öğrenmesi tanrıları mucizevi bir model oluşturmadan önce İskandinavlardan gelen girdi değişkenler üzerinde adeta bir duvar ustası gibi çalışarak modele en uygun veri seti formunu yakalamaya çalışırlar. Bu aşamalardan birisi de değişken/özellik ölçeklendirme (feature scalling) yöntemidir. Bu yöntem her zaman uygulanacak diye bir yasa yoktur ancak uygulandığında içerisinde birçok teknik yer alır.

Önce neden, nasıl, kim, kiminle diye bakalım sonra Python üzerinden konuyu resmetmeye çalışacağız.

Neden Değişken Ölçeklendirme Yapılır?

Neden olmasın :) Bazı gıcık makine öğrenmesi algoritmaları özellikle mesafe/uzaklık (distance) üzerine kurgulanmış olanları doğası gereği girdi değişkenlerinin birbirleri ile aynı aralıkta ve mümkünse küçük sayılardan oluşmasını tercih ediyorlar. Burada tercih derken algoritmanın matematiğinden kaynaklanan bir durum olmasından dolayı zorunlu bir tercih aslında.

Örneğin iki girdi değişken düşünelim. Birisi balık yaşı diğeri de balıktan çıkan yumurta sayısı (bir nevi gelir) olsun diyelim. Yaş belki [1,2,3…] diye devam eden bir array iken yumurta ise [500,800,950…] olarak devam edebilir. İşte bu regresyon probleminde aynı skala içinde yer almayan değişkenlerin bazı algoritmalar açısından cortlama durumu mevcut. Bazı sevimsiz algoritmalar açısından durumu bir inceleyelim.

Hangi Algoritmalar, Nasıl İhtiyaç Duyar? ya da Duymaz!

Gradient Descent optimizasyon yöntemini kullanan doğrusal/lojistik regresyon ya da sinir ağları değişkenlerin aynı skalada (range) olmasına hasrettirler. Bu hırslı algoritma düşük hatayı bulabilmek için sürekli bir deneme yanılma halinde çalışırken her deneme için atacağı adımda (step size) değişkenlerin aldığı değere göre farklı farklı sıçramalarda bulunabilir. Atlayan zıplayan bu algoritmadan daha hızlı ve verimli (learning rate) bir sonuç elde etmek için feature scalling temizlik bezleri kullanılmalıdır. Böylece sinsice değişkenlere sokulabilen algoritma (smoothly) aynı skalada yer alan değişkenler için en optimum minimumu yakalar. Daha fazla bilgi için aşağıdaki yazıma göz atabilirsiniz.

Mesafeye (Distance Based) Dayalı ucube bazı algoritmalar var oluş sebebi olarak feature scalling hunisine ihtiyaç duyarlar. Hangi algoritmalar abi?

  • KNN: En Yakın Kapı Komşusu
  • SVM: Destekli Atan Vektör Makinemsileri
  • K-Means: Kayda Değer Ortalamalar

İşte bu kıvılcımlı arkadaşlar temelde değerlerin birbirleri ile olan uzaklık yakınlık veya akrabalık durumlarına göre en iyi vektörleri ya da ayrımları keşfediyorlar. Dolayısı ile yukarıdaki balıkların yaşları ve yumurta sayıları arasındaki basamaklar bu algoritmaların beynini yakabiliyor. Ancak illa ki bulandıracak diye bir mevzuat yok.

Kararsız Ağaçlar algoritmaları ise delikanlıdır. Değişkenin kıyafetine bakmaz ayıverir dağılımına göre. Tek bir değişkenden türettiği dalları ve budakları ile diğer değişkenlerin ölçeklerinden bağımsız düşünür. Feature scalling yapmıyor diye bu algoritma ağadır paşadır diyemeyiz.

Gelelim tekniklere normalizasyon ve standardizasyon.

Normalizasyon

Değişken içinde yer alan sayıları genellikle 0 ve 1 arasına hapseden bir yöntemdir. Min-Max Scalling olarak da bilinir.

Xnorm = (X − Xmin) / (Xmax − Xmin)

Burada, Xmax ve Xmin sırasıyla değişkene ait değerlerin maksimum ve minimum sayılarını verir.

  • X değeri değişkendeki minimum değer olduğunda, pay 0 olacaktır ve dolayısıyla X = 0 olacaktır.
  • Öte yandan, X’in değeri değişkendeki maksimum değer olduğunda, pay paydaya eşittir olacağından X = 1 sonucu çıkacaktır.
  • Dolayısıyla X’in değeri minimum ve maksimum değer arasındaysa, X’in değeri 0 ile 1 arasında anlamına gelir.

Standardizasyon

Bu teknik çok acımazsız. Önce değişkeni alır yerden yere vurarak ortalamasını sıfıra indirir ardından etrafındaki değerleri bir birim standart sapma sınırında halay çekmesini sağlar. Bu yeni değişkenin eski değişkenin değerlerinin ortalamaya göre standart sapmasını verir. Normalizasyon gibi 0 ile 1 arasında kalmaz eksilerde gezip dolaşabilir. Aşağıda çok havalı olamyan formülü yer almaktadır.

Acaba Hangi Yöntemi Kullansam?

Aslında cevabı inanılmaz basit;

Üşenmeyin her ikisini de model de deneyin :)

Bu konuda biraz pragmatist bakmak gerekiyor sanırım. Zaten bir tanesini deneyeceksiniz diğerini denememeniz için bir sebep yok. Şu algoritma da bu işlemde şu kullanılır gibi bir kural şimdilik literatürde net değil. Fekat tecrübeniz ile bunu öngörebilirsiniz.

Ayrıca aşağıdaki gibi bir kaç uyarım olabilir;

  • Her iki yöntemde sıra dışı (outlier) durumlarında modeli yanıltabilir performansı düşürebilir.
  • İki yöntemde özellikle normal (Gauessian) dağılımlarına aşıktır. Bu yüzden değişken değerlerinin nasıl dağıldığına dikkat edelim.

Python Üzerinden Basit Bir Uygulama

Öncelikle gerekli gereksiz kütüphaneleri ortama tanıtıp datamızı GITHUB üzerinden çağıralım. Eğer Pandas ile linke bağlanmakta sorun yaşıyorsanız veya canınız link üzerinden indirip ortama akmasını isterseniz buradaki adresten indirebilirsiniz.

Aşağıdaki kodu çalıştırıp tabloya kuş bakışı bakalım.

import numpy as np
import pandas as pd
import sklearn.metrics as mt
from sklearn.model_selection import KFold, train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC
from sklearn import preprocessing

# veri setinin alınması
url = "https://github.com/yigitsener/machine-learning/blob/master/data/purchased.csv"
df = pd.read_csv(url + "?raw=true", error_bad_lines=False)

print(df.shape)
# ÇIKTI:
# (10, 4)

# dataya bir bakak
df.head()

Veri seti gayet açık bu müşteriler alır mı almaz mı? (purchased) onu tahmin etmeye çalışıyor. Temel dursun fıkralarında yer alan ülkelerimiz de mevcut.

Veri sayısı yukarıdaki kod içinde gördüğünüz gibi çok çok az ileride model kurulacağı için her ne kadar denemede yapsak modelin sapıtmamasını istiyoruz. Bu yüzden veri sayısını 16 kat artıralım. Yaşasın FOR döngüsü.

# veri setinin çoğaltılması
for i in range(4):
df = pd.concat([df,df])

df = df.reset_index().drop(labels="index", axis = 1)

# satır ve sutun sayısı
print(df.shape)
# ÇIKTI:
# (160, 4)

Veri sayımıza darbe yaparak 160'a çıkardık. Önümüzde Matrix dünyasındaki makinelerin kabul etmeyeceği kategorik değişkenler var bunları dönüştürelim. Dummy variable time!!!

# one hot encoder ile coategatik verilerin dönüşümü
df = pd.get_dummies(df, drop_first = True)

# yeniden bakış
df.head()

Pandas’ın get_dummies() fonksiyonu gerçekten efsanevi. Bu arada tabloda bir ülke eksik aaa Fransa yok. Sebebini aşağıdaki yazımdan öğrenebilirsiniz.

Şimdi işin kolayına kaçarak Sklearn üzerinden yaş (Age) ve maaş (Salary, bu da balık ismi gibi) değişkenlerinin normalize ve standardize mutasyonlarına bir göz atalım.

Önce normalizasyon

# orjinal veriyi tutualım
df_normalize = df.copy()

# seçilen kolonlar için normalizasyon
df_normalize[['Age', 'Salary']] = preprocessing.minmax_scale(df[['Age', 'Salary']])

# sonuçları array olarak bakalım
print(df_normalize[['Age', 'Salary']].values)
# ÇIKTI
# [[0.73913043 0.68571429]
# [0. 0. ]
# [0.13043478 0.17142857]
# [0.47826087 0.37142857]
# [0.56521739 0.08571429]
# [0.34782609 0.28571429]
# [0.60869565 0.11428571]
# [0.91304348 0.88571429]
# [1. 1. ]]

Dizilerde görüldüğü üzere yaş ve maaş ayrı ayrı ama bir ve sıfır arasında dağılarak aynı yatağa baş koymuşlar.

Sıra standardizasyon (ne gereksiz uzun bir kelime)

# yukarıdakinin mantığında
df_standardize = df.copy()
stdandard_scale = preprocessing.StandardScaler()
df_standardize[['Age', 'Salary']] = stdandard_scale.fit_transform(df[['Age', 'Salary']])
print(df_standardize[['Age', 'Salary']].values)
# ÇIKTI
# [[ 0.72319607 0.81747845]
# [-1.73567056 -1.24773026]
# [-1.30175292 -0.73142808]
# [-0.14463921 -0.12907554]
# [ 0.14463921 -0.98957917]
# [-0.57855685 -0.38722663]
# [ 0.28927843 -0.90352881]
# [ 1.30175292 1.41983099]
# [ 1.59103135 1.76403244]
# [-0.28927843 0.38722663]]

Gördüğünüz üzere standartlaşmada eksili değerlere kadar uzanan bir ölçeklendirme söz konusu. Salkım saçak bir durum ile karşı karşıyayız.

Peki modeldeki etkisi nedir?

Modelleri bir daha, bir daha çalıştıracağımız için bir fonksiyon olarak tanımlayalım. Sonra orijinal, normalize ve standardize değerler ile doğruluk oranlarına bakarken on saat yeniden kod yazmamıza gerek kalmaz.

# veri setinde önce girdi ve çıktıyı ayarlıyor
# sonra test ve train olarak ayırıyor.
def defineTargetSaperateTest(df, target_name, test_size_float = 0.20):
"""
:param df: dataframe object
:param target_name: define target name
:param test_size: how many saperate test data
:return: x_train,x_test,y_train,y_test
"""
target = df[target_name]
predictor = df.drop(labels=target_name, axis=1)
return train_test_split(predictor, target, test_size = test_size_float, random_state = 0)


def modelResult(x_train,x_test,y_train,y_test):
lgr = LogisticRegression()
lgr.fit(x_train, y_train)
y_pred = lgr.predict(x_test)
print(mt.accuracy_score(y_test, y_pred))


def svmResult(x_train,x_test,y_train,y_test):
sv = SVC()
sv.fit(x_train, y_train)
y_pred = sv.predict(x_test)
print(mt.accuracy_score(y_test, y_pred))

Görüldüğü üzere üç tane cillop gibi fonksiyonumuz oldu. Şimdi biz bunlara ne verirsek yerler :))

Önce orjinalden başlayalım.

# veri hazırlığı fonksiyonu
x_train,x_test,y_train,y_test = defineTargetSaperateTest(df=df, target_name="Purchased_Yes")

# sonuçlar
lgr_acc = logisticResult(x_train,x_test,y_train,y_test)
svm_acc = svmResult(x_train,x_test,y_train,y_test)
print(f"Lojistik sonuç: {lgr_acc}")
print(f"SVM sonuç: {svm_acc}")
# ÇIKTI

# Lojistik sonuç: 0.53125
# SVM sonuç: 0.4375

Orijinal veri setini kullandığımızda model başarım oranları yüzde 50 civarında.

Hazırsanız normalize edilmiş veriler ile bakalım.

# veri hazırlığı fonksiyonu
x_train,x_test,y_train,y_test = defineTargetSaperateTest(df=df_normalize, target_name="Purchased_Yes")

# sonuçlar
lgr_acc = logisticResult(x_train,x_test,y_train,y_test)
svm_acc = svmResult(x_train,x_test,y_train,y_test)
print(f"Lojistik sonuç: {lgr_acc}")
print(f"SVM sonuç: {svm_acc}")
# ÇIKTI

# Lojistik sonuç: 0.6875
# SVM sonuç: 0.9375

Ben şok, bakın SVM bir anda yüzde 93'lere varan bir performans gösteriyor. Lojistik regresyonda göstermelik olarak artırmış kendini.

Gelin bir de standardizasyonelizm yapalım.

# veri hazırlığı fonksiyonu
x_train,x_test,y_train,y_test = defineTargetSaperateTest(df=df_standardize, target_name="Purchased_Yes")

# sonuçlar
lgr_acc = logisticResult(x_train,x_test,y_train,y_test)
svm_acc = svmResult(x_train,x_test,y_train,y_test)
print(f"Lojistik sonuç: {lgr_acc}")
print(f"SVM sonuç: {svm_acc}")
# ÇIKTI

# Lojistik sonuç: 0.6875
# SVM sonuç: 1.0

Böyle bir şey olamaz standardizasyon SVM’i yüzde yüze çıkardı. Ama zavallı lojistik az da olsa arttı. Olsun her ikisi de bir artış gösterdi.

Demek ki boşuna değilmiş bunca transformasyon işlemleri.

Sonuç

Her sakallı gördüğünüzü dedeniz sanmayın. Yani her girdi değişken için bu işlemler uygulanacak diye bir kural yok. Model başarısız gidiyorsa ya da daha başarılı olmasını istiyorsanız deneyebilirsiniz.

--

--