PYTHON İLE MAKİNE ÖĞRENMESİNDE ÖZELLİK MÜHENDİSLİĞİ (III)

İSTATİSTİKSEL VARSAYIMLARA UYUMLULAŞTIRMA

VERİ DAĞILIMLARI

Bir makine öğrenmesi modeli oluşturmadan önce dikkat edilmesi gereken önemli bir nokta, temel verilerimizin dağılımının nasıl göründüğünü anlamaktır. Birçok algoritma, verilerinmizin nasıl dağıldığı veya farklı özelliklerin birbirleriyle nasıl etkileşime girdiği hakkında varsayımlarda bulunur. Örneğin, ağaç tabanlı modellerin yanı sıra hemen hemen tüm modeller, özelliklerimizin aynı ölçekte olmasını gerektirir. Özellik mühendisliği, verilerimizi dağılım varsayımlarına uyacak şekilde, veya en azından mümkün olduğunca yakın olacak şekilde, değiştirmek için kullanılabilir.

Ağaç tabanlı modellerin yanı sıra hemen hemen her model, verilerimizin normal olarak dağıldığını varsayar. Normal dağılımların şekli, aşağıda gösterildiği gibi, çan şekline benzer. Normal bir dağılımın temel özellikleri, verilerin yüzde 68'inin ortalamanın 1 standart sapması içine, yüzde 95'inin ortalamanın 2 standart sapması içine, % 99,7'sinin ortalamanın 3 standart sapması içine düşmesidir.

image.png

Kendi verilerimizin dağılım şeklini anlamak için sürekli değerlerden oluşan özelliklerin her birinin histogramlarını oluşturabiliriz:

In [1]:
# `pandas` paketinin oturuma yüklenmesi:
import pandas as pd
# `stackoverflow_survey.csv` verisinin oturuma yüklenmesi:
survey = pd.read_csv("http://veribilim.online/data/stackoverflow_survey.csv")
# `survey` veri setinin ilk beş satırının yazdırılması:
survey.head()
Out[1]:
SurveyDate FormalEducation ConvertedSalary Hobby Country StackOverflowJobsRecommend VersionControl Age Years Experience Gender RawSalary
0 2/28/18 20:20 Bachelor's degree (BA. BS. B.Eng.. etc.) NaN Yes South Africa NaN Git 21 13 Male NaN
1 6/28/18 13:26 Bachelor's degree (BA. BS. B.Eng.. etc.) 70841.0 Yes Sweeden 7.0 Git;Subversion 38 9 Male 70,841.00
2 6/6/18 3:37 Bachelor's degree (BA. BS. B.Eng.. etc.) NaN No Sweeden 8.0 Git 45 11 NaN NaN
3 5/9/18 1:06 Some college/university study without earning ... 21426.0 Yes Sweeden NaN Zip file back-ups 46 12 Male 21,426.00
4 4/12/18 22:41 Bachelor's degree (BA. BS. B.Eng.. etc.) 41671.0 Yes UK 8.0 Git 39 7 Male £41,671.00
In [2]:
# `matplotlib.pyplot` paktinin oturuma yüklenmesi:
import matplotlib.pyplot as plt
# `survey` veri setinde "Age", "ConvertedSalary" ve "Years Experience" sütunlarının histogramlarının çizilmesi:
fig = plt.figure(figsize = (10,10))
ax = fig.gca()
survey[["Age","ConvertedSalary","Years Experience"]].hist(ax=ax)
plt.show()
<ipython-input-2-fdc90c7421b8>:6: UserWarning: To output multiple subplots, the figure containing the passed axes is being cleared
  survey[["Age","ConvertedSalary","Years Experience"]].hist(ax=ax)
In [3]:
# `survey` veri setinde "Age", "Years Experience" sütunlarının kutu grafiklerinin (boxplot)
# çizilmesi:
fig = plt.figure(figsize = (10,10))
ax = fig.gca()
survey[["Age","Years Experience"]].boxplot(ax=ax)
plt.show()
In [4]:
# `survey` veri setinde "ConvertedSalary" sütununun kutu grafiğinin (boxplot)
# çizilmesi:
fig = plt.figure(figsize = (10,10))
ax = fig.gca()
survey[["ConvertedSalary"]].boxplot(ax=ax)
plt.show()

Yukarıda, tek tek sütunların dağılımına baktık. Bu iyi bir başlangıç olsa da, farklı özelliklerin birbirleriyle nasıl etkileşime girdiğine dair daha ayrıntılı bir görünüm yararlı olabilir. Çünkü bu, neyin nasıl dönüştürüleceğine dair kararımızı etkileyebilir:

In [5]:
# `seaborn` paketinin oturuma yüklenmesi:
import seaborn as sns 
# `survey` veri setinde "Age", "ConvertedSalary" ve Years Experience" ve sütunlarının 
# ikili ilişki grafiklerinin (pairplot) çizilmesi:
sns.pairplot(survey[["Age","ConvertedSalary","Years Experience"]])
plt.show()

survey veri setindeki "Age", "ConvertedSalary" ve "Years Experience" sütunlarına baktığımızda ikili ilişkilerde önemli bir etkileşim (korelasyon) desenine rastlamıyoruz.

In [6]:
# `survey` veri setindeki sütunların özet istatistiklerinin incelenmesi:
survey.describe().T
Out[6]:
count mean std min 25% 50% 75% max
ConvertedSalary 665.0 92565.169925 209134.425744 0.0 27550.0 55562.0 88238.0 2000000.0
StackOverflowJobsRecommend 487.0 7.061602 2.621591 0.0 5.0 7.0 10.0 10.0
Age 999.0 36.003003 13.255127 18.0 25.0 35.0 45.0 83.0
Years Experience 999.0 9.961962 4.878129 0.0 7.0 10.0 13.0 27.0

Bir sütunun özet istatistiklerini anlamak, hangi dönüşümlerin gerekli olduğuna karar verirken çok değerli olabilir. Örneğin, k-ortalamalar, doğrusal regresyon ve k-en yakın komşular gibi uzaklık ölçüleri içeren algoritmalar verilerin normalize edilmesini (aynı ölçeğe alınmasını) gerektirirken, karar ağaçlarında ayrımlar (dallanmalar) her bir özellik bazında ayrı ayrı gerçekleştirildiği için verilerin normalize edilmesi gerekmez.

ÖLÇEKLENDİRME VE DÖNÜŞTÜRME

Bir önceki kısımda belirttiğimiz gibi, çoğu makine öğrenimi algoritması, etkili olabilmeleri için verilerimizin aynı ölçekte olmasını gerektirir.

Örneğin, ConvertedSalary (maaş) sütunundaki değerleri Age (yaş) sütunundaki değerlerle karşılaştırmak zordur. Benzer ölçek varsayımı gerekli olsa da, gerçek dünya verilerinde bu nadiren bulunabilen bir durumdur. Bu nedenle, verilerimizin aynı ölçekte olmasını sağlamak için yeniden ölçeklendirmemiz gerekir. Bunu yapmak için birçok farklı yaklaşım vardır. Ancak burada en sık kullanılan iki yaklaşımı, yani Min-Max ölçeklendirmeyi (bazen normalleştirme olarak anılır) ve standardizasyonu tartışacağız.

Normalizasyon:

Normalizasyonda tüm sütunu 0 ile 1 arasında doğrusal olarak ölçeklendiriz. 0 sütundaki en düşük değere ve 1 en büyük değere karşılık gelir.

scikit-learn paketini kullanırken, normalleştirme uygulamak için MinMaxScaler()ı kullanabiliriz:

In [7]:
# `sklearn.preprocessing` modülünden `MinMaxScaler()`ın oturuma yüklenmesi:
from sklearn.preprocessing import MinMaxScaler
In [8]:
# bir `MinMaxScaler nesnesi oluşturulması:
olceklendirme = MinMaxScaler()
In [9]:
# `olceklendirme` nesnesinin `survey` veri setindeki `Age` sütununa uygulanması:
olceklendirme.fit(survey[['Age']])
Out[9]:
MinMaxScaler()
In [10]:
# `olceklendirme` nesnesini kullanarak sütunun dönüştürülmesi:
survey['Age_normalizasyon'] = olceklendirme.transform(survey[['Age']])
In [11]:
# orjinal ve dönüştürülmüş sütunların karşılaştırılması:
print(survey[['Age_normalizasyon', 'Age']].head())
   Age_normalizasyon  Age
0           0.046154   21
1           0.307692   38
2           0.415385   45
3           0.430769   46
4           0.323077   39
In [12]:
print(survey[['Age_normalizasyon', 'Age']].describe())
       Age_normalizasyon         Age
count         999.000000  999.000000
mean            0.276969   36.003003
std             0.203925   13.255127
min             0.000000   18.000000
25%             0.107692   25.000000
50%             0.261538   35.000000
75%             0.415385   45.000000
max             1.000000   83.000000

Yukarıdaki özet istatistiklerden gördüğümüz gibi, dönüştürülmüş olan Age_olcekli sütunundaki minimum değer "0", maksimum değer de "1"dir. Sütunda dağılan tüm değerler 0-1 arasına yerleştirilmiştir.

Standardizasyon

Normalleştirme, bir sütunu iki veri noktası arasında ölçeklendirmek için yararlı olsa da, bir veri noktası bile aykırı değerlerden aşırı etkilenmişse, iki ölçeklenmiş sütunu karşılaştırmak yine zor olabilir. Bunun için yaygın olarak kullanılan bir çözüm standardizasyon olarak adlandırılır. Burada katı bir şekilde belirlenmiş üst ve alt sınır koymak yerine, veriler ortalama etrafında yerleştirilir, ve her veri noktasının ortalamaya göre yeri "standart sapma" birim değeri esas alınarak belirlenir:

In [13]:
# `sklearn.preprocessing` modülünden `StandardScaler()`ın oturuma yüklenmesi:
from sklearn.preprocessing import StandardScaler
In [14]:
# `StandardScaler` oluşturulması
olceklendirme = StandardScaler()
In [15]:
# `olceklendirme` nesnesinin `survey` veri setindeki `Age` sütununa uygulanması:
olceklendirme.fit(survey[['Age']])
Out[15]:
StandardScaler()
In [16]:
# `olceklendirme` nesnesini kullanarak sütunun dönüştürülmesi:
survey['Age_standardizasyon'] = olceklendirme.transform(survey[['Age']])
In [17]:
# orjinal ve dönüştürülmüş sütunların karşılaştırılması:
print(survey[['Age_standardizasyon', 'Age']].head())
   Age_standardizasyon  Age
0            -1.132431   21
1             0.150734   38
2             0.679096   45
3             0.754576   46
4             0.226214   39
In [18]:
print(survey[['Age_standardizasyon', 'Age']].describe())
       Age_standardizasyon         Age
count         9.990000e+02  999.000000
mean          9.935329e-17   36.003003
std           1.000501e+00   13.255127
min          -1.358872e+00   18.000000
25%          -8.305099e-01   25.000000
50%          -7.570696e-02   35.000000
75%           6.790960e-01   45.000000
max           3.547347e+00   83.000000

Yukarıda görüldüğü gibi Age sütunundaki değerler ortalaması "0", standart sapması "1" olan değerlere dönüştürüldü. Minimum ve maksimum değerler de, sütundaki veri noktalarının ortalamadan "standart sapma" birimine göre ne kadar uzak oldukları baz alınarak belirlendi. Bizim örneğimizde maksimum değer ortalamadan 3.5 standart sapma yukarıda, minimum değer de ortalamadan 1.35 standart sapma aşağıdadır.

Log Dönüşümü

Normalizasyon ve standardizasyonda, veriler doğrusal olarak ölçeklendirilir. Bu dönüşümler, verilerin dağılım şeklini etkilemez ve veriler normal olarak dağılıyorsa (veya normal dağılıma yakınsa) iyi çalışır. Gerçek dünyadaki birçok değişken bu kalıbı takip etmez.

Örneğin survey veri setindeki ConvertedSalary sütununa baktığımızda değerlerin büyük bir kısmının düşük değerlerde toplandığını ancak bazı yüksek aykırı değerlerin bulunduğunu görebiliriz. Ayrıca herhangi bir negatif değer bulunmadığını da görebiliriz:

In [19]:
# `survey` veri setindeki 
survey[["ConvertedSalary"]].hist()
plt.show()

Bu şekildeki dağılımlara log-normal dağılım denir:

image.png

Log-normal dağılıma sahip değişkenler, log dönüşümü ile normal dağılıma dönüştürülebilirler:

In [20]:
# `sklearn.preprocessing` modülünden `PowerTransformer()`ın oturuma yüklenmesi:
from sklearn.preprocessing import PowerTransformer
In [21]:
# `PowerTransformer` dönüşüm nesnesinin oluşturulması:
donusum = PowerTransformer()
In [22]:
# dönüşüm nesnesinin veriye uygulanması:
donusum.fit(survey[['ConvertedSalary']])
Out[22]:
PowerTransformer()
In [23]:
# dönüşümün gerçekleştirilmesi:
survey['ConvertedSalary_LOG'] = donusum.transform(survey[['ConvertedSalary']])
In [24]:
# verinin dönüşüm öncesi ve dönüşüm sonrası halinin histogram grafiğinin çizilmesi:
fig = plt.figure(figsize = (14,7))
ax = fig.gca()
survey[['ConvertedSalary', 'ConvertedSalary_LOG']].hist(ax=ax)
plt.show()
<ipython-input-24-8d324e0e50b1>:4: UserWarning: To output multiple subplots, the figure containing the passed axes is being cleared
  survey[['ConvertedSalary', 'ConvertedSalary_LOG']].hist(ax=ax)

Log dönüşümden sonra verinin normal dağılıma yaklaştığını görüyoruz.

AYKIRI DEĞERLERİN TEMİZLENMESİ

Sıklıkla, yukarıdaki dönüşümleri gerçekleştirdikten sonra bile verilerimizin hala çok çarpık olduğunu görecebiliriz. Bu durum genellikle verilerimizde bulunan aykırı değerlerden kaynaklanabilir.

Aykırı değerler, verilerimizin çoğundan uzakta bulunan veri noktalarıdır. Bu durum, nadir olarak oluşan bazı doğal durumlardan ya da yanlış veri kaydından kaynaklanabilir. Her iki durumda da, modellerimizi olumsuz yönde etkileyebilecekleri için bu değerleri temizlemek isteyebiliriz. Olumsuz etkinin bir örneği olarak, bir aykırı değerin neredeyse tüm ölçeklendirilmiş verilerin alt sınıra sıkıştırılmasına neden olmasını verebiliriz.

Verinin küçük bir kısmının aşırı olumsuz bir etkiye sahip olmamasını sağlamanın bir yolu, sütundaki en büyük ve / veya en küçük değerlerin belirli bir yüzdesini ortadan kaldırmaktır:

In [25]:
# yüzde 95'lik dilimin bulunması:
yuzdelik = survey['ConvertedSalary'].quantile(0.95)
yuzdelik
Out[25]:
174268.59999999986
In [26]:
# aykırı değerlerin ortadan kaldırılması:
survey_aykiri_degersiz = survey[survey['ConvertedSalary'] < yuzdelik]
In [27]:
# orjinal histogram
survey[['ConvertedSalary']].hist()
plt.show()
In [28]:
# aykırı değerler kaldırılmış histogram:
survey_aykiri_degersiz[['ConvertedSalary']].hist()
plt.show()

Verilerimizin en yüksek %N'lik kısmını kaldırmak, çok sahte olabilecek veri noktalarının kaldırılmasını sağlamak için yararlı olsa da, veriler doğru olsa bile her zaman aynı oranda noktayı kaldırma dezavantajına sahiptir. Yaygın olarak kullanılan alternatif bir yaklaşım, ortalamadan üç standart sapmadan daha uzak olan verileri çıkarmaktır. Bunu, önce üst ve alt sınırları bulmak için ilgili sütunun ortalamasını ve standart sapmasını hesaplayarak ve bu sınırları veri setine bir maske olarak uygulayıp gerçekleştirebiliriz. Bu yöntem, yalnızca diğerlerinden gerçekten farklı olan verilerin kaldırılmasını sağlar ve veriler birbirine yakınsa daha az noktayı ortadan kaldırır.

In [29]:
# ortalama ve standart sapmanın bulunması:
standart_sapma = survey['ConvertedSalary'].std()
ortalama = survey['ConvertedSalary'].mean()
In [30]:
# kesme noktasının belirlenmesi:
kesme_noktasi = standart_sapma * 3
alt_sinir, ust_sinir = ortalama - kesme_noktasi, ortalama + kesme_noktasi
In [31]:
# aykırı değerlerin ortadan kaldırılması:
survey_aykiri_degersiz = survey[(survey['ConvertedSalary'] < ust_sinir) \
                           & (survey['ConvertedSalary'] > alt_sinir)]
In [32]:
# aykırı değersiz değişkenin histogramının çizilmesi:
survey_aykiri_degersiz[['ConvertedSalary']].hist()
plt.show()

YENİ VERİLERİN ÖLÇEKLENDİRİLMESİ VE DÖNÜŞTÜRÜLMESİ

Makine öğrenmesinin en önemli yönlerinden biri, oluşturduğumuz herhangi bir modelin "yeni" bir veri seti üzerinde gerçek uygulamasıdır. Geçmiş verilere dayalı bir model oluşturduysak, sonuçta, bu modeli tahminler yapmak için yeni verilere uygulamak isteyeriz.

Şimdiye kadar bir sütuna dayalı ölçekleyiciler oluşturduk ve ardından bu ölçekleyiciyi, üzerinde eğitildiği aynı verilere uyguladık. Makine öğrenimi modelleri oluştururken, modellerimizi genellikle geçmiş veriler (öğrenme veri seti) üzerine oluşturur ve oluşturulan bu modeli yeni verilere (test veri seti) uygularız. Bu durumlarda, hem öğrenme hem de test veri setlerine aynı ölçeklendirmenin uygulandığından emin olmamız gerekir.

Bunu pratikte yapmak için ölçekleyiciyi öğrenme veri setinde eğitiriz ve eğitimli ölçekleyiciyi test veri setine uygulanması için tutarız. Test veri setinde bir ölçekleyiciyi asla yeniden eğitmemeliyiz:

In [33]:
# `survey` veri setinin sayısal sütun alt kümesinin alınması:
survey_sayisal = survey[["ConvertedSalary","Age","Years Experience"]]
# X ve y değişkenlerinin oluşturulması:
X = survey_sayisal[["Age","Years Experience"]]
y = survey_sayisal[["ConvertedSalary"]]
In [34]:
# `survey_sayisal` veri setinin `ogrenme` ve `test` veri setlerine ayrılması:
from sklearn.model_selection import train_test_split
X_ogrenme,X_test,y_ogrenme,y_test = train_test_split(X,y,random_state=42)
In [35]:
# ogrenme ve test veri setlerinin X ve y değerlerinin birleştirilmesi:
veri_ogrenme = [X_ogrenme, y_ogrenme]
ogrenme = pd.concat(veri_ogrenme)
veri_test = [X_test, y_test]
test = pd.concat(veri_test)
In [36]:
# `sklearn.preprocessing` modülünden `StandardScaler`ın oturuma yüklenmesi:
from sklearn.preprocessing import StandardScaler
In [37]:
# `StandardScaler` nesnesinin oluşturulması:
olcekleyici = StandardScaler()
In [38]:
# ölçekleyicinin veriye uygulanması:
olcekleyici.fit(ogrenme[['Age']])
Out[38]:
StandardScaler()
In [39]:
# test veri setinin eğitilmiş ölçeklendirici kullanılarak dönüştürülmesi:
test['Age_standart'] = olcekleyici.transform(test[["Age"]])
In [40]:
print(test[['Age', 'Age_standart']].head())
      Age  Age_standart
453  29.0     -0.526719
793  25.0     -0.831362
209  46.0      0.768014
309  18.0     -1.364487
740  26.0     -0.755201

Aynı ölçeklendiriciyi hem öğrenme hem de test veri setlerimize uygulamaya benzer şekilde, öğrenme veri setinden aykırı değerleri çıkardıysak, muhtemelen aynı şeyi test setinde de yapmak isteriz. Bir kez daha, test setinden aykırı değerleri kaldırmak için öğrenme veri setinden hesaplanan eşikleri kullandığımızdan emin olmalıyız:

In [41]:
# öğrenme veri setinin ortalama ve standart sapmalarının hesaplanması:
ogrenme_std = ogrenme['ConvertedSalary'].std()
ogrenme_ortalama = ogrenme['ConvertedSalary'].mean()
In [42]:
# kesme noktasının hesaplanması:
kesme_noktasi= ogrenme_std * 3
# alt ve üst sınırların belirlenmesi:
ogrenme_alt_sinir, ogrenme_ust_sinir = ogrenme_ortalama - kesme_noktasi, ogrenme_ortalama + kesme_noktasi
# test veri setinin öğrenme veri setinde belirlenen üst ve alt sınırlara göre düzenlenmesi:
test_aykirisiz = test[(test['ConvertedSalary'] < ogrenme_ust_sinir) \
                             & (test['ConvertedSalary'] > ogrenme_alt_sinir)]
In [43]:
# orjinal histogram
test[['ConvertedSalary']].hist()
plt.show()
In [44]:
# aykırı değerler kaldırılmış histogram
test_aykirisiz[['ConvertedSalary']].hist()
plt.show()