Monday, July 28, 2014

Seyrek 1-Hot Kodlamasi

Bu konu tekrar tekrar ortaya cikiyor. Amac kategorik verilerin hizli sekilde "her ayri kolon+kategori ayri bir kolonda olacak sekilde" bir matris ortamina gecirilmesi ve numerik olarak temsil edilebilmesi. Mesela

pop;state;year
1.5;Ohio;2000
1.7;Ohio;2001
3.6;Ohio;2002
2.4;Nevada;2001
2.9;Nevada;2002


verisinde state=Ohio icin ayri bir kolon olacak ve bu kolon '1' degerine sahip olacak. Tabii bu kombinasyonu icermeyen diger satirlar bu noktada '0' degeri tasiyacak, ki  seyrek matris ortaminda o deger bellege bile alinmayacaktir.

Bu isi Pandas uzerinden yapmak orta derece buyuk veride bile muthis yavastir. Alttaki kod parcasi bu isi CSV'yi acip satir satir islerken cok daha hizli yapiyor, hic Pandas'i kullanmadan. Girdi CSV, cikti seyrek matris;

from scipy.io import mmwrite
import csv, pandas as pd, os
import scipy.sparse as sps
import numpy as np

mat_keys = {}
cat_cols = ['state']

N  = num_lines = sum(1 for line in open('in.csv')) - 1
D = 10 # tahmin, buyukce bir sayi

A = sps.lil_matrix((N,D))

with open('in.csv', "rb" ) as theFile:
    reader = csv.DictReader(theFile, delimiter=';')
    for i,line in enumerate(reader):
        for col in cat_cols:
            new_col = 'cat_%s_%s' % (col,line[col])
            if new_col not in mat_keys: mat_keys[new_col] = len(mat_keys)
            A[i, mat_keys[new_col]] = 1.0


Kodun yaptigi her kombinasyon icin bir kolon indis degeri hesaplamak, ve o matris noktasina 1 degeri vermek. Nihai matriste bu "yeni kolonlarin" hangi kolon indisine tekabul ettigini hatirlamamiz iyi olur, o sebeple o eslemeyi tasiyan sozluk bir yere mesela pickle olarak kaydedilebilir.

Kod islemi tamamlaninca A icinde kategorik kolonlarin numerik kodlanmis hali olacak. Matris sps.lil_matrix((N,D)) ile yaratildi, D sayisi buyukce bir sayi olarak yaklasik tahmin edildi, nasil olsa seyrek ortamda hangi deger verirseniz verin bellekte o olcude yer "onceden ayirilmaz"; seyrek matrislerin onemli bir avantaji budur zaten.

Indis degeri vermek icin yeni bir kombinasyonun mat_keys icinde olup olmadigina bakiyoruz, yoksa yeni indis degeri sozlukte olan degerlerin sayisi olacak, 10 tane deger var ise, yeni indis 10 (dikkat 10+1 degil cunku 0-baslangicli indis erisimi kullaniyoruz, C dilinde oldugu gibi), mat_keys[new_col] = len(mat_keys) bu isi yapiyor. Bu ucuz bir sekilde tekil (unique) deger kodlamanin bir yolu.

Her kolon+kategori eslemesinden ne kadar yaratildigini gormek icin

res = []
for x in mat_keys: res.append([x, A[:, mat_keys[x]].sum()])
df = pd.DataFrame(res)
df.sort_index( by=[1], inplace = True, ascending = False)
print df


Sonuc

0  cat_state_ohio  3
1  cat_state_nevada  2


Yogun (dense) matris olarak gostermek gerekirse (ki bu gercek dunya sartlarinda hicbir zaman yapilmaz)

print A.todense()

[[ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  1.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 0.  1.  0.  0.  0.  0.  0.  0.  0.  0.]]


Kodlanmasi istenen her kategorik kolonu cat_cols icine eklemek yeterli! Mesela year icin bunu yapalim,

[[ 1.  1.  0.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  1.  0.  0.  0.  0.  0.  0.  0.]
 [ 1.  0.  0.  1.  0.  0.  0.  0.  0.  0.]
 [ 0.  0.  1.  0.  1.  0.  0.  0.  0.  0.]
 [ 0.  0.  0.  1.  1.  0.  0.  0.  0.  0.]]

                 
cat_state_Ohio  3
cat_year_2001  2
cat_state_Nevada  2
cat_year_2002  2
cat_year_2000  1

Sozlugu gosterelim

{'cat_year_2000': 1, 'cat_year_2001': 2, 'cat_state_Ohio': 0, 'cat_state_Nevada': 4, 'cat_year_2002': 3}

Kodlanmis matrisin kategorik olmayan diger kolonlardan ayri olmasi dert degil, bu matris degerleri seyrek hstack ile basit bir sekilde kodlanmis matrise yapistirilabilir.

from scipy.io import mmwrite
import csv, pandas as pd, os
import scipy.sparse as sps
import numpy as np

mat_keys = {}
cat_cols = ['state','year']

N  = num_lines = sum(1 for line in open('in.csv')) - 1
D = 10 # tahmin, buyukce bir sayi

A = sps.lil_matrix((N,D))

with open('in.csv', "rb" ) as theFile:
    reader = csv.DictReader(theFile, delimiter=';')
    for i,line in enumerate(reader):
        for col in cat_cols:
            new_col = 'cat_%s_%s' % (col,line[col])
            if new_col not in mat_keys: mat_keys[new_col] = len(mat_keys)
            A[i, mat_keys[new_col]] = 1.0
           
df = pd.read_csv('in.csv',sep=';',usecols=['pop'])

A = sps.hstack((A,sps.lil_matrix(df)))

print A.todense()


Sonuc

[[ 1.   1.   0.   0.   0.   0.   0.   0.   0.   0.   1.5]
 [ 1.   0.   1.   0.   0.   0.   0.   0.   0.   0.   1.7]
 [ 1.   0.   0.   1.   0.   0.   0.   0.   0.   0.   3.6]
 [ 0.   0.   1.   0.   1.   0.   0.   0.   0.   0.   2.4]
 [ 0.   0.   0.   1.   1.   0.   0.   0.   0.   0.   2.9]]


Ya da iki ustteki kodu genisleterek tum CSV degerlerinin seyrek matris aktarilmasini saglayabiliriz, eger kategorik kolon ise 1-hot kodlama yapilir, degilse oldugu gibi aktarilir,

from scipy.io import mmwrite
import csv, pandas as pd, os
import scipy.sparse as sps
import numpy as np

mat_keys = {}
cat_cols = ['state','year']

N  = num_lines = sum(1 for line in open('in.csv')) - 1
D = 10 # tahmin, buyukce bir sayi

A = sps.lil_matrix((N,D))

with open('in.csv', "rb" ) as theFile:
    reader = csv.DictReader(theFile, delimiter=';')
    for i,line in enumerate(reader):
        for col in line.keys():
            col_name = col; val = line[col]
            if col in cat_cols:
                col_name = 'cat_%s_%s' % (col,line[col])
                val = 1.0
            if col_name not in mat_keys: mat_keys[col_name] = len(mat_keys)
            A[i, mat_keys[col_name]] = val

print A.todense()       
print mat_keys


Sonuc

[[ 1.   1.5  1.   0.   0.   0.   0.   0.   0.   0. ]
 [ 1.   1.7  0.   1.   0.   0.   0.   0.   0.   0. ]
 [ 1.   3.6  0.   0.   1.   0.   0.   0.   0.   0. ]
 [ 0.   2.4  0.   1.   0.   1.   0.   0.   0.   0. ]
 [ 0.   2.9  0.   0.   1.   1.   0.   0.   0.   0. ]]


{'cat_state_Nevada': 5, 'pop': 1, 'cat_year_2000': 2, 'cat_year_2001': 3, 'cat_state_Ohio': 0, 'cat_year_2002': 4}

Hangi kodun kullanilacagi ihtiyaca gore sekillenir; bizim ihtiyacimiz yuzlerce kolon iceren bir veri icinden yapay ogrenim amacli olarak sadece kategorik kolonlari cekip cikartmakti, bu yuzden ilk kodu gelistirdik. Her ihtiyac icin degisik bir yaklasim secilebilir. Mesela ustteki kod kategorik olmayan her seyi numerik kabul ediyor, belki kirli bir veri ortaminda bu secim de iyi degildir, o zaman numerik kolonlar icin ayri bir liste gerekir, vs.

Pandas Problemleri

1-hot kodlamasini seyrek matris olmadan, yine CSV okuyup ama Pandas Dataframe'ini genisletmeye cabalayan bir sekilde de yazmistik ilk basta, fakat dinamik olarak yeni kolon eklemenin hizli islemedigini gorduk. Pandas mevcut bir veriyi almak ve onu manipule etmek, gerekirse birkac yeni kolon eklemek baglaminda kullanilirsa hizlidir. Araclarimizin sinirlarini bilmemiz iyi olur.

Eger tum kolonlari onceden bilsek (bunu bulmaya ugrassak) belki Pandas versiyonu da hizli sekilde kodlanabilirdi, ama kirli veri ortaminda kategorik verilerin kendi icinde bile ek kategorileri oldugunu dusunun! (bizzat gorduk, virgul ile ayrilmis listeler vardi) bu durumda tum kategorileri onceden bulmaya ugrasmak ayni isi iki kere yapmak olurdu, kod bakimini zorlastirirdi.

Ayrica genel olarak, hem de Buyuk Veri dunyasina hazir olmak baglaminda, verinin satir satir islenmesine alismak lazim, bakilan o satir o kolon icin hesap yapilmaya ugrasilmasi daha temiz bir koda sebebiyet veriyor. Ustteki orneklerde kac indis, hangi kategoriler oldugunu onceden bilmiyoruz, bilmeye ugrasmiyoruz onlari anlik olarak hesapliyoruz. Daha ileri ornekler mesela asiri buyuk kategorik secenekleri hashing ve mod hesaplari uzerinden daha az bir pencereye indirgeme yontemini bile kullanabilir, bu hesap ta anlik ve cok hizli yapilabilecek bir hesaptir.

No comments: