In [12]:
import random
import pandas as pd
import time
from sklearn.decomposition import PCA
from sklearn.svm import SVC
from pprint import pprint
In [13]:
K = 10
In [14]:
def separate_parts(blob):
"""Splits data to index column, real data and class column"""
indexes = np.array(blob[:,0].astype('uint8'))
data = blob[:,1:-1]
classes = np.array(blob[:,-1].astype('uint8'))
return indexes, data, classes
def join_parts(indexes, data, classes):
"""Joins index verctor, data and classes vector into one table"""
return np.hstack((np.array([indexes]).T, data, np.array([classes]).T))
In [15]:
df = pd.read_csv('data.txt', header=None)
indexes, data, classes = separate_parts(df.as_matrix())
classes = classes <= 4
Po przyjrzeniu się danym można zauważyć, że niektóre parametry mają dużo większe wartości niż inne parametry. W związku z tym, parametry o średnio wyższych wartościach mają w sposób niejawny większą wagę. Aby tego uniknąć normalizujemy parametry odejmując wartość średnią. Parametry mają też różne wariancje i tu sytuacja jest podobna więc dzielimy wszystkie parametry przez ich odchylenie standardowe dla całęgo zbioru danych (dosyć standardowa normalizacja danych wejściowych). Przeprowadzałem eksperymenty z danymi znormalizowanymi i surowymi. Znormalizowane dane dają dużo lepsze wyniki.
In [16]:
norm_data = data - np.mean(data, 0)
norm_data /= np.std(data, 0)
norm_data = join_parts(indexes, norm_data, classes)
#norm_data = data
In [17]:
def kfold(data, k, step):
k_volume = len(data) / k
left = k_volume * step
right = k_volume * (step + 1)
return data[left:right], np.vstack((data[:left], data[right:]))
In [18]:
test, train = kfold(norm_data, 10, 1)
assert len(test) + len(train) == len(norm_data)
print len(test), len(train)
Aby sprawdzić czy nasze klasy są w miarę sensowne zrzutowałem wszystkie parametry na 2 wymiary używając PCA. Dla tych którzy nie znają to w skrócie PCA przekręca układ współrzędnych tak aby pierwsze współrzędne niosły jak najwięcej informacji. W tym przypadku pierwsze dwie współrzędne niosą 50.6 % informacji, więc można się spodziewać, że ogólnie jest x lepiej niż na obrazku :)
In [19]:
_, data, _ = separate_parts(norm_data)
pca = PCA()
pca_transformed = pca.fit_transform(data)
sum(pca.explained_variance_ratio_[0:2])
Out[19]:
In [20]:
plt.figure(figsize=(10,6), dpi=100)
plt.scatter(pca_transformed[classes,0], pca_transformed[classes,1], color="red")
plt.scatter(pca_transformed[classes==False,0], pca_transformed[classes==False,1], color="blue")
Out[20]:
Na dole są wyniki.
kernel - rodzaj jądra. Mamy linear, poly 2, poly 3 i rbf które kolejno odpowiadają jądrom liniowemu, kwadratowemu, trzeciego stopnia i wykładniczemu
parametr C - kara za niepoprawne klasyfikacje. 1.0 - to znaczy ze dopuszczamy niepoprawne klasyfikacje, 1000.0 to znaczy ze nie dopuszczamy
W drugiej linijce każdego wyjścia jest lista krotek. Jest ich zawsze 10 bo parametr K przy kfoldingu przyjąłem 10. Pierwsza wartość krotki to skuteczność klasyfikacji. 1.0 - to znacyz że wszystko zostało dobrze zaklasyfikowane. Druga wartość krotki to czas wykonania w sekundach.
In [28]:
def process(classifier, train_data, test_data):
train_indexes, train_data, train_classes = separate_parts(train_data)
test_indexes, test_data, test_classes = separate_parts(test_data)
classifier.fit(train_data, train_classes)
score = classifier.score(test_data, test_classes)
support = sum(classifier.n_support_)
return score, support
rets = {}
for c in (1.0, 1000.0):
for kernel, params in (('linear', {}), ('poly', {'degree':2}), ('poly', {'degree':3}), ('rbf', {})):
params.update({'kernel': kernel, 'C':c})
print '*' * 70
print params
ret = []
rets[(kernel, c)] = ret
for i in xrange(K):
test, train = kfold(norm_data, K, i)
start = time.time()
score, support = process(SVC(**params), train, test)
duration = time.time() - start
ret.append((score, duration, support))
for k, v in rets.iteritems():
name = "{0}.csv".format(str(k[0]) + str(int(k[1])))
print name
with open(name, 'w') as f:
for l in v:
f.write("{0}, {1}, {2}\n".format(*l))
pprint(rets)
Można zauważyć, że skuteczność klasyfikacji była bardzo mała dla jednego z k zestawów testowych. Może to wynikać z dosyć małego zestawu danych, gdzie prawdopodobieństwo wylosowania niereprezentatywnych danych jest stosunkowo duże. Może to również ozanaczać, że algorytm SVM jest wrażliwy na niekorzystne rozkłady danych uczących.
Należy również zauważyć, że wymuszając na algorytmie SVM poprawną klasyfikację wszystkich próbek również zmniejszamy jego skuteczność. Wynika to z faktu że w zestawie danych mogą znaleźć się nietypowe dane i jeśli jest ich mało to mogą bezpiecznie zostać zignorowane. Jeśli jednak wymusimy dostosowanie się również do anormalnych danych to klasyfikator gorzej może klasyfikować nieznane próbki.