In [12]:
import random
import pandas as pd
import time
from sklearn.decomposition import PCA
from sklearn.svm import SVC
from pprint import pprint

Stałe


In [13]:
K = 10

Funckje pomocnicze


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))

Wczytywanie danych


In [15]:
df = pd.read_csv('data.txt', header=None)
indexes, data, classes = separate_parts(df.as_matrix())
classes = classes <= 4

Normalizacja danych

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

K-fold


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)


21 193

Analiza składowych głównych (bonus)

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]:
0.50680399011126886

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]:
<matplotlib.collections.PathCollection at 0x4cadd90>

Klasyfikacja

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)


**********************************************************************
{'kernel': 'linear', 'C': 1.0}
**********************************************************************
{'kernel': 'poly', 'C': 1.0, 'degree': 2}
**********************************************************************
{'kernel': 'poly', 'C': 1.0, 'degree': 3}
**********************************************************************
{'kernel': 'rbf', 'C': 1.0}
**********************************************************************
{'kernel': 'linear', 'C': 1000.0}
**********************************************************************
{'kernel': 'poly', 'C': 1000.0, 'degree': 2}
**********************************************************************
{'kernel': 'poly', 'C': 1000.0, 'degree': 3}
**********************************************************************
{'kernel': 'rbf', 'C': 1000.0}
poly1.csv
rbf1.csv
linear1.csv
rbf1000.csv
poly1000.csv
linear1000.csv
{('linear', 1.0): [(1.0, 0.0019412040710449219, 33),
                   (1.0, 0.0013380050659179688, 33),
                   (1.0, 0.0013620853424072266, 33),
                   (1.0, 0.0015001296997070312, 33),
                   (0.95238095238095233, 0.0011980533599853516, 32),
                   (0.61904761904761907, 0.0012271404266357422, 27),
                   (0.90476190476190477, 0.001074075698852539, 29),
                   (0.8571428571428571, 0.0010390281677246094, 27),
                   (0.47619047619047616, 0.0009531974792480469, 14),
                   (1.0, 0.0011608600616455078, 33)],
 ('linear', 1000.0): [(1.0, 0.11731219291687012, 27),
                      (1.0, 0.11344504356384277, 27),
                      (1.0, 0.4630861282348633, 27),
                      (1.0, 0.18288803100585938, 26),
                      (0.95238095238095233, 0.0981740951538086, 26),
                      (0.66666666666666663, 0.0963890552520752, 18),
                      (0.8571428571428571, 0.0962991714477539, 22),
                      (0.8571428571428571, 0.1022639274597168, 21),
                      (0.38095238095238093, 0.002389192581176758, 8),
                      (1.0, 0.5910589694976807, 27)],
 ('poly', 1.0): [(1.0, 0.0011599063873291016, 49),
                 (1.0, 0.0010731220245361328, 48),
                 (1.0, 0.0011649131774902344, 48),
                 (1.0, 0.0011479854583740234, 49),
                 (1.0, 0.0012340545654296875, 49),
                 (0.7142857142857143, 0.0012249946594238281, 43),
                 (0.95238095238095233, 0.0011370182037353516, 48),
                 (0.8571428571428571, 0.0011560916900634766, 42),
                 (0.047619047619047616, 0.0007989406585693359, 21),
                 (0.95238095238095233, 0.0011210441589355469, 44)],
 ('poly', 1000.0): [(1.0, 0.0020139217376708984, 34),
                    (1.0, 0.00189208984375, 36),
                    (0.95238095238095233, 0.0016391277313232422, 36),
                    (1.0, 0.001750946044921875, 31),
                    (0.95238095238095233, 0.0015139579772949219, 30),
                    (0.7142857142857143, 0.0013310909271240234, 27),
                    (0.95238095238095233, 0.0012869834899902344, 31),
                    (0.8571428571428571, 0.002001047134399414, 36),
                    (0.38095238095238093, 0.0009379386901855469, 18),
                    (0.95238095238095233, 0.0020461082458496094, 35)],
 ('rbf', 1.0): [(1.0, 0.0013589859008789062, 58),
                (1.0, 0.0013709068298339844, 57),
                (1.0, 0.0014109611511230469, 59),
                (1.0, 0.0014040470123291016, 55),
                (1.0, 0.0014259815216064453, 54),
                (0.61904761904761907, 0.0011868476867675781, 46),
                (0.8571428571428571, 0.001313924789428711, 50),
                (0.8571428571428571, 0.0013740062713623047, 53),
                (0.19047619047619047, 0.0010039806365966797, 33),
                (1.0, 0.0013260841369628906, 52)],
 ('rbf', 1000.0): [(1.0, 0.0016829967498779297, 45),
                   (1.0, 0.001753091812133789, 44),
                   (0.95238095238095233, 0.0017740726470947266, 43),
                   (1.0, 0.0017061233520507812, 46),
                   (1.0, 0.0017199516296386719, 43),
                   (0.61904761904761907, 0.0014920234680175781, 33),
                   (0.80952380952380953, 0.00150299072265625, 42),
                   (1.0, 0.0015621185302734375, 41),
                   (0.19047619047619047, 0.0008959770202636719, 25),
                   (0.95238095238095233, 0.001477956771850586, 42)]}

Ocena pakietu

  • scikit-learn, http://scikit-learn.org/stable/index.html
  • autorzy: społeczność open-source, https://github.com/scikit-learn/scikit-learn/graphs/contributors. Projekt został zaczęty w 2007 jako część programu Google Summer of Code przez Davida Copernau. Później pracował nad nim Mathieu Brucher. W 2010 projekt został przejęty przez Fabiana Pedregosa, Gaela Varoquaux, Alexandra Gramfort i Vincenta Michel z INRIA( Francuski Instytut Rozwoju Informatyki i Automatyki)/
  • rok powstania 2007, ostatnia aktualizacja 5h temu (1.02.2014 15:02)
  • wymagania: intepreter języka Python, biblioteka NumPY.
  • istnieje kompletna i dosyć szczegółowa dokumentacja dostępna online
  • istnieje możliwość wsparcia na forum stackoverflow (tag #scikit-learn) i na liście dyskusyjnej projektu
  • wymagana średnia znajomość języka Python
  • moduł klasyfikatora posiada bardzo dużo możliwości konfiguracji w związku z czym oceniam zgodność sformułowania zadania na 100%
  • łatwość korzystania - 5. Dla osoby znającej język Python oraz bibliotekę NumPy jest bardzo naturalne i łatwe. Elementy tej biblioteki podzielone są na tematyczne moduły i posiadają jasne nazwy oraz przejrzystą dokumetnację.
  • klasyfikator SVM w bibliotece scikit-learn został oparty o znane i przetestowane implementacje (libsvm, liblinear)
  • implementacja rozwiązania przy pomocy biblioteki scikit-image i języka python zajęła około 40 linijek kodu i została napisana w czasie nie przekraczającym 3h. Program wykonał się w czasie poniżej 1 sekundy.
  • biblioteka scikit-image oparta jest o bibliotekę obliczeń numerycznych NumPy w związku z czym prezentacja wyników jest możliwa w oparciu o na przykład takie biblioteki jak "matplotlib" czy "ipython notebook". Biblioteki te dają możliwość wszechstronnej i kompleksowej prezentacji danych w łatwy i przejrzysty sposób.

Komentarz na temat wyników

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.