Özellik İşlemek (Feature Engineering)
Veri madenciliğinde "veriden veri yaratma" tekniği çok kullanılıyor; mesela bir sipariş veri satırında o siparişin hangi zamanda (timestamp) olduğunu belirten bir kolon varsa (ki çoğu zaman vardır), bu kolonu "parçalayarak" ek, daha genel, özetsel bilgi kolonları yaratılabilir. Ya da kategoriksel verileri pek çok farklı şekilde sayısal hale çevirebiliriz, mesela 1-hot kodlama ile N kategori N kolon haline gelir, eldeki kategoriye tekabül eden öğe 1 diğerleri sıfır yapılır.
Özellik işlemenin önemi yapay öğrenme açısından önemi var, mesela bir SVM sınıflayıcısını en basit haliyle siyah/beyaz görüntüden sayı tanıma probleminde kullandık, ve diyelim yüzde 70 başarı elde ettik. Şimdi çok basit bir yeni özellik yaratalım, görüntüyü dikey ikiye bölelim, ve üstteki ve alttaki siyah noktaları toplayarak iki yeni kolon olarak görüntü matrisine ekleyelim. Bu yeni özellikleri kullanınca basit sınıflayıcının yüzde 20 kadar tanıma başarısında ilerleme kaydettiğini göreceğiz!
Not: Derin yapay sınır ağları teknikleri ile özellik işlemeye artık gerek olmadığı söylenir, bu büyük ölçüde doğru. Bir DYSA farklı seviyelerdeki pek çok farklı nöronları üzerinden aslında üstte tarif edilen türden yeni özellikleri otomatik olarak yaratır, ögrenir. Fakat yine de yeni özellikleri elle yaratma tekniklerini bilmek iyi.
Şimdi farklı yöntemlere bakalım.
Zaman Kolonlarını Zenginleştirmek
Zaman kolonları çoğu zaman saniyeye kadar kaydedilir, bu bilgiyi alıp mesela ay, mevsim, haftanın günü, saat, iş saati mi (9-5 arası), akşam mı, sabah mı, öğlen mi, vs. gibi ek bilgiler çıkartılabilir. Tüm kolonlar veri madenciliği algoritmasına verilir, ve algoritma belki öğlen saati ile sipariş verilmiş olması arasında genel bir bağlantı bulacaktır.
Python + Pandas ile bir zaman kolonu şöyle parçalanabilir, örnek veri üzerinde görelim, sadece iki kolon var, müşteri no, ve sipariş zamanı,
import pandas as pd
from StringIO import StringIO
s = """customer_id;order_date
299;2012-07-20 19:44:55.661000+01:00
421;2012-02-17 21:54:15.013000+01:00
437;2012-02-20 22:18:12.021000+01:00
463;2012-02-20 23:46:21.587000+01:00
482;2012-05-21 09:50:02.739000+01:00
607;2012-02-21 11:57:12.462000+01:00
641;2012-02-21 13:40:28.088000+01:00
674;2012-08-21 14:53:15.851000+01:00
780;2012-02-23 10:31:05.571000+01:00
"""
df = pd.read_csv(StringIO(s),sep=';', parse_dates=True)
def f(x):
tmp = pd.to_datetime(x['order_date'])
tpl = tmp.timetuple(); yymm = int(tmp.strftime('%m%d'))
spring = int(yymm >= 321 and yymm < 621)
summer = int(yymm >= 621 and yymm < 921)
fall = int(yymm >= 921 and yymm < 1221)
winter = int( spring==0 and summer==0 and fall==0 )
warm_season = float(tpl.tm_mon >= 4 and tpl.tm_mon <= 9)
work_hours = float(tpl.tm_hour > 9 and tpl.tm_hour < 17)
morning = float(tpl.tm_hour >= 7 and tpl.tm_hour <= 11)
noon = float(tpl.tm_hour >= 12 and tpl.tm_hour <= 14)
afternoon = float(tpl.tm_hour >= 15 and tpl.tm_hour <= 19)
night = int (morning==0 and noon==0 and afternoon==0)
return pd.Series([tpl.tm_hour, tpl.tm_mon,
tpl.tm_wday, warm_season,
work_hours, morning, noon, afternoon, night,
spring, summer, fall, winter])
cols = ['ts_hour','ts_mon','ts_wday','ts_warm_season',\
'ts_work_hours','ts_morning','ts_noon','ts_afternoon',\
'ts_night', 'ts_spring', 'ts_summer', 'ts_fall', 'ts_winter']
df[cols] = df.apply(f, axis=1)
print df[cols]
ts_hour ts_mon ts_wday ts_warm_season ts_work_hours ts_morning \
0 18.0 7.0 4.0 1.0 0.0 0.0
1 20.0 2.0 4.0 0.0 0.0 0.0
2 21.0 2.0 0.0 0.0 0.0 0.0
3 22.0 2.0 0.0 0.0 0.0 0.0
4 8.0 5.0 0.0 1.0 0.0 1.0
5 10.0 2.0 1.0 0.0 1.0 1.0
6 12.0 2.0 1.0 0.0 1.0 0.0
7 13.0 8.0 1.0 1.0 1.0 0.0
8 9.0 2.0 3.0 0.0 0.0 1.0
ts_noon ts_afternoon ts_night ts_spring ts_summer ts_fall ts_winter
0 0.0 1.0 0.0 0.0 1.0 0.0 0.0
1 0.0 0.0 1.0 0.0 0.0 0.0 1.0
2 0.0 0.0 1.0 0.0 0.0 0.0 1.0
3 0.0 0.0 1.0 0.0 0.0 0.0 1.0
4 0.0 0.0 0.0 1.0 0.0 0.0 0.0
5 0.0 0.0 0.0 0.0 0.0 0.0 1.0
6 1.0 0.0 0.0 0.0 0.0 0.0 1.0
7 1.0 0.0 0.0 0.0 1.0 0.0 0.0
8 0.0 0.0 0.0 0.0 0.0 0.0 1.0
Sıcak mevsim (warm season) Mart-Eylül aylarını kapsar, bu ikisel bir değişken hale getirildi. Belki siparişin, ya da diğer başka bir verinin bununla bir alakası vardır. Genel 4 sezon tek başına yeterli değil midir? Olabilir, fakat bazı kalıplar / örüntüler (patterns) belki sıcak / soğuk mevsim bilgisiyle daha çok bağlantılıdır.
Aynı şekilde saat 1-24 arasında bir sayı olarak var, fakat "iş saatini" ayrı bir ikisel değişken olarak kodlamak yine bir "kalıp yakalama" şansımızı arttırabilir. Bu kolonun ayrı bir şekilde kodlanmış olması veri tasarımı açısından ona önem verildiğini gösterir, ve madencilik algoritmaları bu kolonu, eğer ona bağlı bir kalıp var ise, yakalayabilirler.
Not: Burada ufak bir pürüz sabah, öğlen, akşamüstü gibi zamanları kodlarken çıktı. Gece 19'dan sonra ve 7'den önce bir sayı olacaktı, fakat bu durumda $x>19$ ve $x<7$ hiçbir sonuç getirmeyecekti. Burada saatlerin 24 sonrası başa dönmesi durumu problem çıkartıyordu, tabii ki karşılaştırma ifadelerini çetrefilleştirerek bu iş çözülebilir, ama o zaman kod temiz olmaz (mesela ($x>19$ ve $x<24$) ya da ($x>0$ ve $x<7$) yapabilirdik). Temiz kod için gece haricinde diğer tüm seçenekleri kontrol ediyoruz, ve gece "sabah, öğlen, akşamüstü olmayan şey" haline geliyor. Aynı durum mevsimler için de geçerli. Onun için
night = int (morning==0 and noon==0 and afternoon==0)
kullanıldı.
Kategorileri İkileştirme
Yapay öğrenim algoritmalarının çoğu zaman hem kategorik hem sayısal değerleri aynı anda bulunduran verilerle iş yapması gerekebiliyor. Ayrıca literatüre bakılınca görülür ki çoğunlukla bir algoritma ya biri, ya diğeri ile çalışır, ikisi ile aynı anda çalışmaz (çalışanlar var tabii, mesela karar ağaçları -decision tree-). Bu gibi durumlarda iki seçenek var, ya numerik veri kategoriselleştirilir (ayrıksallaştırılır), ya da kategorik veri numerik hale getirilir.
Bu durumda, kategorik bir kolon eyalet için, eyaletin Ohio olup olmaması başlı başına ayrı bir kolon olarak gösteriliyor. Aynı şekilde Nevada. Bu kodlamaya literatürde 1-hot kodlaması adı veriliyor. KMeans, lojistik regresyon gibi metotlara girdi vermek için bu transformasyon kullanılabilir.
import numpy as np
import pandas as pd, os
import scipy.sparse as sps
from sklearn.feature_extraction import DictVectorizer
def one_hot_dataframe(data, cols, replace=False):
vec = DictVectorizer()
mkdict = lambda row: dict((col, row[col]) for col in cols)
vecData = pd.DataFrame(vec.fit_transform(data[cols].apply(mkdict, axis=1)).toarray())
vecData.columns = vec.get_feature_names()
vecData.index = data.index
if replace is True:
data = data.drop(cols, axis=1)
data = data.join(vecData)
return (data, vecData, vec)
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9]}
df = pd.DataFrame(data)
df2, _, _ = one_hot_dataframe(df, ['state'], replace=True)
print df2
pop year state=Nevada state=Ohio
0 1.5 2000 0.0 1.0
1 1.7 2001 0.0 1.0
2 3.6 2002 0.0 1.0
3 2.4 2001 1.0 0.0
4 2.9 2002 1.0 0.0
Unutmayalım, kategorik değerler bazen binleri bulabilir (hatta sayfa
tıklama tahmini durumunda mesela milyonlar, hatta milyarlar), bu da
binlerce yeni kolon demektir. Yani 1/0 kodlaması, yani 1-hot işleminden ele
geçen yeni blok içinde aslında oldukca çok sayıda sıfır değeri olacak
(sonuçta her satırda binlerce 'şey' içinde sadece bir tanesi 1 oluyor),
yani bu bloğun bir seyrek matris olması iyi olurdu. O zaman matrisin
tamamını sps.csr_matrix
ya da sps.lil_matrix
ile gerçekten
seyrek formata çevirebiliriz, ve mesela scikit-learn paketi, numpy, scipy
işlemleri seyrek matrisler ile hesap yapabilme yeteneğine
sahip. Seyrekselleştirince ne elde ediyoruz? Sıfırları depolamadığımız
için sadece sıfır olmayan değerler ile işlem yapıyoruz, o ölçüde kod
hızlanıyor, daha az yer tutuyor.
Dikkat etmek gerekir ki yeni kolonları üretince değerlerin yerleri sabitlenmiş olur. Her satır bazında bazen state=Ohio, state=Nevada, bazen sadece state=Ohio üretiyor olamayız. Üstteki örnekte her zaman 4 tane kolon elde edilmelidir.
Not: 1-hot yerine bir diğer seçenek kategoriyi bir indise çevirmek (tüm kategorileri sıralayıp kaçıncı olduğuna bakarak mesela) sonra bu sayıyı ikisel sistemde belirtmek, eğer 'a' sayısı 30 indisine tekabül ediyorsa, 30 ikisel sistemde 11110, bu değer kullanılır (aslında bu son tarif edilen sistemin 1-hot sistemden daha iyi işlediği rapor ediliyor).
Anahtarlama Numarası (1-Hot Encoding, Hashing Trick)
Fakat bir problem var, dokümanı temsil eden ve içinde 1 ya da 0 hücreli özellik vektörünü (feature vector) oluşturmak için tüm kelimelerin ne olduğunu bilmeliyiz. Yani veriyi bir kere baştan sonra tarayarak bir sözlük oluşturmalıyız (ki öyle yapmaya mecbur kaldık) ve ancak ondan sonra her doküman için hangi kelimenin olup olmadığını saptamaya ve onu kodlamaya başlayabiliriz. Halbuki belgelere bakar bakmaz, teker teker giderken bile hemen bir özellik vektörü oluşturabilseydik daha iyi olmaz mıydı?
Bunu başarmak için anahtarlama numarasını kullanmamız lazım. Bilindiği gibi temel yazılım bilime göre bir kelimeyi temsil eden bir anahtar (hash) üretebiliriz, ki bu hash değeri bir sayıdır. Bu sayının en fazla kaç olabileceğinden hareketle (hatta bu sayıya bir limit koyarak) özellik vektörümüzün boyutunu önceden saptamış oluruz. Sonra kelimeye bakarız, hash üretiriz, sonuç mesela 230 geldi, o zaman özellik vektöründeki 230'uncu kolonun değerini 1 yaparız.
d_input = dict()
def add_word(word):
hashed_token = hash(word) % 127
d_input[hashed_token] = d_input.setdefault(hashed_token, 0) + 1
add_word("obama")
print d_input
{48: 1}
add_word("politics")
print d_input
{48: 1, 91: 1}
Üstteki kodda bunun örneğini görüyoruz. Hash sonrası mod uyguladık (yüzde işareti ile) ve hash sonucunu en fazla 127 olacak şekilde sınırladık. Potansiyel problemler ne olabilir? Hashing mükemmel değildir, çarpışma (collision) olması mümkündür yani nadiren farklı kelimelerin aynı numaraya eşlenebilmesi durumu. Bu problemleri iyi bir anahtarlama algoritması kullanarak, mod edilen sayıyı büyük tutarak çözmek mümkündür, ya da bu tür nadir çarpışmalar "kabul edilir hata" olarak addedilebilir.
Pandas kullanarak bir Dataframe'i otomatik olarak anahtarlamak istersek,
import pandas as pd
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9]}
data = pd.DataFrame(data)
print data
pop state year
0 1.5 Ohio 2000
1 1.7 Ohio 2001
2 3.6 Ohio 2002
3 2.4 Nevada 2001
4 2.9 Nevada 2002
Şimdi bu veri üzerinde sadece eyalet (state) için bir anahtarlama numarası yapalım
def hash_col(df,col,N):
for i in range(N): df[col + '_' + str(i)] = 0.0
df[col + '_hash'] = df.apply(lambda x: hash(x[col]) % N,axis=1)
for i in range(N):
idx = df[df[col + '_hash'] == i].index
df.ix[idx,'%s_%d' % (col,i)] = 1.0
df = df.drop([col, col + '_hash'], axis=1)
return df
print hash_col(data,'state',4)
pop year state_0 state_1 state_2 state_3
0 1.5 2000 0.0 0.0 0.0 0.0
1 1.7 2001 0.0 0.0 0.0 0.0
2 3.6 2002 0.0 0.0 0.0 0.0
3 2.4 2001 0.0 0.0 0.0 1.0
4 2.9 2002 0.0 0.0 0.0 1.0
Baştan Seyrek Matris ile Çalışmak
Büyük Veri ortamında, eğer kategorik değerler milyonları buluyorsa, o zaman üstteki gibi normal Numpy matrisinden seyreğe geçiş yapmak bile külfetli olabilir. Bu durumlarda daha en baştan seyrek matris üretiyor olmalıyız. Mevcut tüm değerleri önceden bildiğimizi farz edersek,
import numpy as np
import pandas as pd, os
import scipy.sparse as sps
import itertools
def one_hot_column(df, cols, vocabs):
mats = []; df2 = df.drop(cols,axis=1)
mats.append(sps.lil_matrix(np.array(df2)))
for i,col in enumerate(cols):
mat = sps.lil_matrix((len(df), len(vocabs[i])))
for j,val in enumerate(np.array(df[col])):
mat[j,vocabs[i][val]] = 1.
mats.append(mat)
res = sps.hstack(mats)
return res
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
'year': ['2000', '2001', '2002', '2001', '2002'],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9]}
df = pd.DataFrame(data)
print df
vocabs = []
vals = ['Ohio','Nevada']
vocabs.append(dict(itertools.izip(vals,range(len(vals)))))
vals = ['2000','2001','2002']
vocabs.append(dict(itertools.izip(vals,range(len(vals)))))
print vocabs
print one_hot_column(df, ['state','year'], vocabs).todense()
pop state year
0 1.5 Ohio 2000
1 1.7 Ohio 2001
2 3.6 Ohio 2002
3 2.4 Nevada 2001
4 2.9 Nevada 2002
[{'Ohio': 0, 'Nevada': 1}, {'2002': 2, '2000': 0, '2001': 1}]
[[ 1.5 1. 0. 1. 0. 0. ]
[ 1.7 1. 0. 0. 1. 0. ]
[ 3.6 1. 0. 0. 0. 1. ]
[ 2.4 0. 1. 0. 1. 0. ]
[ 2.9 0. 1. 0. 0. 1. ]]
one_hot_column
çağrısına bir "sözlükler listesi" verdik, sözlük her
kolon için o kolonlardaki mümkün tüm değerleri bir sıra sayısı ile
eşliyor. Sözlük listesinin sırası kolon sırasına uyuyor olmalı.
Niye sözlük verdik? Bunun sebebi eğer azar azar (incremental) ortamda iş yapıyorsak, ki Büyük Veri (Big Data) ortamında her zaman azar azar yapay öğrenim yapmaya mecburuz, o zaman bir kategorik kolonun mevcut tüm değerlerine azar azar ulaşamazdık (verinin başında isek, en sonundaki bir kategorik değeri nasıl görelim ki?). Fakat önceden bu listeyi başka yollarla elde etmişsek, o zaman her öne-hot işlemine onu parametre olarak geçiyoruz.
Sözlük niye one_hot_dataframe
çağrısı dışında yaratıldı? Bu çağrı
düz bir liste alıp oradaki değerleri sırayla bir sayıyla eşleyerek her
seferinde bir sözlük yaratabilirdi. Bunu yapmadık, çünkü sözlük yaratımının
sadece bir kere, one_hot_dataframe
dışında olmasını istiyoruz. Yine
Büyük Veri ortamını düşünenelim, eşleme (map) için mesela bir script
yazdık, bu script içinde (basında) hemen sözlükler yaratılırdı. Daha sonra
verinin tamamı için, azar azar sürekli one_hot_dataframe
çağrısı
yapılacaktır. O zaman arka arkaya sürekli aynı veriyi (sözlükleri) sıfırdan
tekrar yaratmamız gerekirdi. Bu gereksiz performans kaybı demek
olacaktı. Unutmayalım, Büyük Veri ortamında tek bir kategorik kolonun
milyonlarca değişik değeri olabilir!
Azar Azar İşlemek (Incremental, Minibatch Processing)
Çoğu zaman onlarca kategori, birkaç milyonluk satır içeren bir veriye bakmamız gerekiyor; biliyoruz ki bu kadar veri için Büyük Veri teknolojilerine (mesela Spark, Hadoop gibi) geçmek gereğinden fazla külfet getirecek, elimizdeki dizüstü, masaüstü bilgisayarı bu işlemler için yeterli olmalı, fakat çoğu kütüphane tek makinada azar azar işlem yapmak için yazılmamış. Mesela üstte görülen anahtarlama yöntemi anahtarlama başlamadan önce tüm verinin hafızaya alınmasını gerektiriyor.
Bu durumda kendimiz çok basit Python kavramlarını, iyi bir anahtarlama kodunu, ve lineer cebir hesaplarında seyreklik (sparsity) tekniklerini kullanarak ufak veri parçaları işleyen bir ortamı yaratabiliriz.
Örnek veri olarak [4] yazısında görülen oy kalıpları verisini biraz değiştirerek yeni bir analiz için kullanalım. Veri oy verenlerin ırk, cinsiyet, meslek, hangi partiye oy verdikleri ve kazançlarını kaydetmiş, biz analizimizde bahsedilen kategorilerin bu kişilerin kazancıyla bağlantılı olup olmadığına bakacağız. Veriyi oluşturalım,
import pandas as pd
df = pd.read_csv('../stat_logit/nes.dat',sep=r'\s+')
df = df[['presvote','year','gender','income','race','occup1']]
df = df.dropna()
df.to_csv('nes2.csv',index=None)
Önce kategorilerden ne kadar var, sayalım. Basit toplam yani,
import pandas as pd
df = pd.read_csv('nes2.csv')
print u'tüm veri', len(df)
print 'cinsiyet', np.array(df['gender'].value_counts())
print u'ırk', np.array(df['race'].value_counts())
print 'parti', np.array(df['presvote'].value_counts())
print u'kazanç', df['income'].mean()
tüm veri 13804
cinsiyet [7461 6343]
ırk [12075 1148 299 180 85 17]
parti [6998 6535 271]
kazanç 3.07649956534
Mesela son sonuçtaki her hücre belli bir partiye verilen oyların sayısı; veriye göre üç farklı kategori varmış demek ki, veri ABD için olduğuna göre bunlardan ilk ikisi bilinen iki büyük parti, üçüncü hücre de herhalde bağımsız adaylar.
Kazanç 1 ile 5 arasında tam sayılar (1 az, 5 çok) bu sayıları kategorik olarak kabul edip aslında çok çıktılı bir sınıflayıcı eğitmeyi de seçebilirdik, fakat bu örnek için bu sayıları reel hedef olarak aldık: test verisinde tahminleri bakılırsa 2.5'lük kazanç tahminleri görülebilir, bu yüzden.
Kategorik verileri ikileştirmeye gelelim. Burada üç nokta önemli, veriyi azar azar işleyeceğiz demiştik, ve veriyi seyrek matris olarak almak istiyoruz, ve hangi kategorik değerin hangi kolona eşleneceğini elle tanımlamak istemiyoruz (eşleme otomatik olmalı). Seyreklik önemli çünkü eğer 1000 farklı kategorik değere sahip olan 10 tane kolon varsa, bu 10000 tane yeni kolon yaratılması demektir - her farklı kategori için o değere tekabül eden kolon 1 olacak gerisi 0 olacak. Bu rakamlar orta ölçekte bile rahatlıkla milyonlara ulaşabilir. Eğer ikileştirme için seyrek matris kullanırsak çoğu sıfır olan değerler hafızada bile tutulmaz. Eşleme otomatik olmalı, zaten onun için anahtarlama yapacağız.
Anahtarlama icin sklearn.feature_extraction.text.HashingVectorizer
var,
from sklearn.feature_extraction.text import HashingVectorizer
import numpy as np
vect = HashingVectorizer(n_features=20)
a = ['aa','bb','cc']
res = vect.transform(a)
print res
(0, 5) 1.0
(1, 19) 1.0
(2, 18) -1.0
Sonuçlar seyrek matris olarak, ve üç değer için üç ayrı satır olarak geldi. Anahtarlama niye bazen -1 bazen +1 veriyor? Aslında bu bizim için çok faydalı, çünkü birazdan PCA işleteceğiz, ve PCA her veri kolonunun sıfırda ortalanmış olmasını ister. Üstteki teknikte anahtar üreten fonksiyon -1,+1 arasında rasgele seçim yapıyor gibi duruyor, bize göre bu üretilen anahtar kolonlarında -1, +1 değerlerinin doğal olarak dengelenmesi için yapılmış, böylece otomatik olarak ortalamaları sıfıra inecektir. Akıllıca bir teknik.
Devam edelim, sonucu tek satır olacak şekilde kendimiz tekrar
düzenleyebiliriz. O zaman Python yield
kavramını [3] kullanarak
(azar azar satır okumak için), anahtarlama, ve seyrek matrisler ile şu
şekilde bir kod olabilir,
from sklearn.feature_extraction.text import HashingVectorizer
import numpy as np
import pandas as pd, csv
import scipy.sparse as sps
HASH = 30
vect = HashingVectorizer(decode_error='ignore',n_features=HASH)
def get_row(cols):
with open("nes2.csv", 'r') as csvfile:
rd = csv.reader(csvfile)
headers = {k: v for v, k in enumerate(next(rd))}
for row in rd:
label = float(row[headers['income']])
rrow = [x + str(row[headers[x]]) for x in headers if x in cols]
X_train = vect.transform(rrow)
yield X_train.tocoo(), label
def get_minibatch(row_getter,size=10):
X_train = sps.lil_matrix((size,HASH))
y_train = []
for i in range(size):
cx,y = row_getter.next()
for dummy,j,val in zip(cx.row, cx.col, cx.data): X_train[i,j] = val
y_train.append(y)
return X_train, y_train
# tek bir satir goster
cols = ['gender','income','race','occup1']
row_getter = get_row(cols)
X,y = get_minibatch(row_getter,size=1)
print y, X.todense()
[4.0] [[ 0. 0. 0. -1. 0. 0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0. 1. 0. 0. 1. 0.]]
İlgilendiğimiz kolon listesini get_row
'a verip bir gezici fonksiyon
yarattık. Bu geziciyi get_minibatch
'e verdik, kaç tane satır
istediğimizi ona söylüyoruz, o bize istenen kadar satırı arka planda
geziciye sorarak seyrek matris olarak veriyor. 10 tane daha isteyelim,
X,y = get_minibatch(row_getter,size=10)
print len(y), X.shape, type(X)
10 (10, 30) <class 'scipy.sparse.lil.lil_matrix'>
PCA
Lineer Cebir'in temel bileşen analizi (PCA) tekniğini kullanarak boyut azaltması yapabiliriz. Veriyi yine satır satır işleyerek PCA hesabı yapan teknikler var, kod veriyi seyrek formatta da alabiliyor. Bu kod lineer cebir PCA yazısında işlendi.
import sys; sys.path.append('../stat_170_pca')
import ccipca
cols = ['gender','income','race','occup1']
row_getter = get_row(cols)
pca = ccipca.CCIPCA(n_components=10,n_features=30)
for i in range(10000):
X,y = get_minibatch(row_getter,size=1)
pca.partial_fit(X)
pca.post_process()
print 'varyans orani'
print pca.explained_variance_ratio_
varyans orani
[ 0.36086926 0.16186391 0.13377998 0.09440711 0.0702763 0.05113956
0.04768294 0.0343724 0.02336052 0.02224802]
Her bileşenin verideki varyansın ne kadarını açıkladığı görülüyor.
Peki 30 kolonu 10 kolona indirdik, acaba veri temsilinde, tahmin etmek amacında ilerleme elde ettik mi? Veriyi PCA'nın bulduğu uzaya yansıtıp bu boyutu azaltılmış veriyi regresyonda kullansak ne olur acaba? Yansıtma ve regresyon,
from sklearn.linear_model import SGDRegressor
clf = SGDRegressor(random_state=1, n_iter=1)
row_getter = get_row(cols)
P = pca.components_.T
for i in range(10000):
X_train, y_train = get_minibatch(row_getter,1)
Xp = np.dot((X_train-pca.mean_),P)
clf.partial_fit(Xp, y_train)
Şimdi sonraki 1000 satırı test için kullanalım,
y_predict = []
y_real = []
for i in range(1000):
X_test,y_test = get_minibatch(row_getter,1)
Xp = np.dot((X_test-pca.mean_),P)
y_predict.append(clf.predict(Xp)[0])
y_real.append(y_test[0])
y_predict = np.array(y_predict)
y_real = np.array(y_real)
err = np.sqrt(((y_predict-y_real)**2).sum()) / len(y_predict)
print 'ortalama tahmin hatasi', err
print 'maksimum deger', np.max(y_real)
ortalama tahmin hatasi 0.0105872845541
maksimum deger 5.0
1 ile 5 arasında gidip gelen değerlerin tahmininde 0.01 civarı ortalama hata var. Fena değil. Peki verinin kendisini olduğu gibi alıp regresyonda kullansaydık? Hedef verisi kazanç, kaynak kolonları geri kalan kategoriler. Üstte olduğu gibi veri parçaları 1000'er satır, 10 parça olarak alacağız, yani 10,000 satır modeli eğitmek için kullanılacak. Geri kalanlar test verisi olacak.
sklearn.linear_model.SGDRegressor
ufak seyrek matris parçaları ile
eğitilebiliyor,
from sklearn.linear_model import SGDRegressor
clf = SGDRegressor(random_state=1, n_iter=1)
row_getter = get_row(cols)
y_predict = []; y_real = []
for i in range(10):
X_train, y_train = get_minibatch(row_getter,1000)
clf.partial_fit(X_train, y_train)
X_test,y_test = get_minibatch(row_getter,1000)
y_predict = clf.predict(X_test)
err = np.sqrt(((y_predict-y_test)**2).sum()) / len(y_predict)
print 'ortalama tahmin hatasi',
ortalama tahmin hatasi 0.0208096951078
Bu sonuç ta hiç fena değil. Sonuç olarak veri içinde bazı kalıplar olduğunu gördük, tahmin yapabiliyoruz. Hangi kolonların daha önemli olduğunu bulmak için her kolonu teker teker atıp hatanın yukarı mı aşağı mı indiğine bakabilirdik.
Tekrar vurgulamak gerekirse: üstteki analizde aslında çok fazla kategorik
veri yok, yani statsmodels.formula.api
üzerinden güzel formüllerle,
regresyon çıktısında her kategorik değerin güzelce listelendiği türden bir
kullanıma da gidebilirdik. Bu yazıda göstermeye çalıştığımız çok fazla
veri, çok fazla kolon / kategori olduğunda ve tek makina ortamında takip
edilebilecek çözümler.
Zaman Karşılaştırmak
Eğer 23:50 ile sabah 00:10 zamanını karşılaştırmak istersek ne yaparız? Eğer saat ve dakika farkını direk hesaplasak bu iki zamanın çok uzak olduğunu düşünebilirdik. Fakat aslında aralarında 20 dakika var, zaman dönüp başa gelen bir kavram.
Bu hesabı yapmak için bir yöntem çember, açılar kullanmak. Gün içindeki zamanı 0 ile 1 arasında kodlarız, sonra bu büyüklüğü $2\pi$ ile çarparız, bu bize çember üzerindeki bir noktayı verir, yani zamanı açıya çevirmiş oluruz. Sonra açının $\sin$, $\cos$ değerini hesaplayıp iki rakam elde ederiz, bu iki sayı bize gün içindeki zamanı temsil eden bir büyüklük verir.
Bu büyüklükleri birbirleri ile karşılaştırmak daha kolay, üstteki şekilde $\theta_2$ ve $\theta_3$ birbirine yakın, karşılaştırma yaparken $\sin$ bize dikey eksendeki izdüşümü, $\cos$ yatay eksendeki izdüşümünü verir, $\theta_2,\theta_3$ için y eksenindeki yansıma birbirine çok yakın. Eksen x üzerindeki yansıma farklı biri eksi biri artı yönde fakat yine de mutlak değer bağlamında birbirlerine çok yakınlar. İstediğimiz de bu zaten.
import scipy.linalg as lin
t1 = 0.12 * 2*np.pi
t2 = 0.97 * 2*np.pi
t3 = 0.03 * 2*np.pi
d1 = (np.cos(t1), np.sin(t1))
d2 = (np.cos(t2), np.sin(t2))
d3 = (np.cos(t3), np.sin(t3))
print ("%f %f" % d1)
print ("%f %f" % d2)
print ("%f %f" % d3)
print u'uzaklık 1-2 =', lin.norm(np.array(d1)-np.array(d2))
print u'uzaklık 2-3 =', lin.norm(np.array(d2)-np.array(d3))
0.728969 0.684547
0.982287 -0.187381
0.982287 0.187381
uzaklık 1-2 = 0.907980999479
uzaklık 2-3 = 0.374762629171
Kaynaklar
[1] Teetor, R Cookbook
[2] Scikit-Learn Documentation, 4.2. Feature extraction, http://scikit-learn.org/dev/modules/feature_extraction.html
[3] Bayramlı, Fonksiyon Gezmek ve Yield, https://burakbayramli.github.io/dersblog/sk/2011/02/fonksiyon-gezmek-ve-yield.html
[4] Bayramlı, Istatistik, Lineer Regresyon
Yukarı