-
-
Save xehivs/0def0261090abcae9ec2d88304bc7fe4 to your computer and use it in GitHub Desktop.
MVP eksperymentu klasyfikacji
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
MVP eksperymentu klasyfikacji | |
""" | |
# Importujemy numpy do transformacji macierzowych | |
import numpy as np | |
# Z modułu `sklearn.dataset`` importujemy funkcję `make_classification` pozwalającą na syntetyzację tzw. zbioru Madelon. | |
# http://clopinet.com/isabelle/Projects/NIPS2003/Slides/NIPS2003-Datasets.pdf | |
from sklearn.datasets import make_classification | |
# Z submodułu `model_selection` możemy zaimportować obiekty umożliwiające nam bezboleśne wykonanie walidacji krzyżowej. | |
# Tutaj wykorzystujemy najprostszą, zaimplementowaną w klasie KFold. | |
from sklearn.model_selection import KFold | |
# Submoduł `metrics` zawiera metryki oceny jakości rozpoznawania. | |
# Znów, skorzystamy z najprostszej, czyli dokładności (`accuracy_score`) | |
from sklearn.metrics import accuracy_score | |
# Aby przeprowadzić eksperymenty, potrzebujemy modeli, które będziemy oceniać. | |
# Wykorzystamy naiwny klasyfikator bayesowski dla zbiorów ilościowych (GNB) i standardową sieć neuronową (MLP). | |
# W "dialekcie" uczenia głębokiego sieć MLP nazywamy *warstwą w pełni połączoną*. | |
from sklearn.naive_bayes import GaussianNB | |
from sklearn.neural_network import MLPClassifier | |
# Do analizy statystycznej niezbędna będzie nam funkcja testu statystycznego. | |
# Testów nie ma w `scikit-learn`, ale z pomocą przychodzi nam biblioteka ogólnego zastosowania naukowego `scientific-python`. | |
from scipy.stats import ttest_rel | |
# Na koniec, dla kultury eksperymentu, zaimportujemy funkcję pomocniczą clone. | |
from sklearn.base import clone | |
""" | |
Przygotowanie eksperymentu | |
""" | |
# Korzystając z metody Madelon, generujemy tysiąc dwuwymiarowych obiektów. | |
X, y = make_classification(n_samples=1000, n_features=2, n_redundant=0, random_state=1410) # zmienic na tysiac | |
# Budujemy słownik zawierający dwa klasyfikatory bazowe w ich domyślnej konfiguracji | |
clfs = { | |
'GNB': GaussianNB(), | |
'MLP': MLPClassifier() | |
} | |
# Do zmiennych odkładamy (a) metrykę porównawczą, (b) wybrany test statystyczny i (c) wartość alpha testowania hipotezy zerowej. | |
metric = accuracy_score | |
stest = ttest_rel | |
alpha = .05 | |
# Inicjalizujemy obiekt walidacji krzyżowej | |
kf = KFold(n_splits=5) | |
# Przygotowujemy macierz, w której będziemy składować wyniki. | |
# Dla prostego eksperymentu, w którym nie przeglądamy żadnych dodatkowych *hiperparametrów* wystarczy nam macierz 5x2. | |
# (pięć foldów, dwa klasyfikatory) | |
results = np.zeros((5, len(clfs))) | |
""" | |
Realizacja eksperymentu | |
""" | |
# Iterujemy funkcję `split()` obiektu klasy KFold. | |
# Funkcja ta przyjmuje X i y (cały zbiór). | |
# *Ubieramy* ją w funkcję wbudowaną `enumerate()`, aby w każdym powtórzeniu mieć też wygodny dostęp do identyfikatora *foldu*. | |
for fold, (train, test) in enumerate(kf.split(X, y)): | |
# Iterujemy klasyfikatory bazowe ze słownika. | |
# I tutaj korzystamy z funkcji wbudowanej `enumerate()`, tym razem dla wygodnego identyfikatora modelu bazowego. | |
for clf_idx, clfn in enumerate(clfs): | |
# Pamiętajmy, że iterowanie słownika w każdej iteracji pobiera *klucz* a nie *wartość*. | |
# W związku z tym, aby *wyłuskać* model bazowy, musimy tym kluczem zaadresować słownik – `clfs[clfn]`. | |
# Co ważne, nie korzystamy z klasyfikatora bazowego, a zawsze *klonujemy go* na potrzeby pętli eksperymentalnej. | |
# Służy do tego funkcja `clone()`. | |
clf = clone(clfs[clfn]) | |
# Budujemy model klasyfikacji. W nomenklaturze `scikit-learn` nazywamy to *dopasowaniem modelu*. | |
# Stąd, wykorzystywana jest funkcja `fit()` obiektu klasyfikatora. | |
# Do funkcji `fit()` przekazujemy *zbiór uczący*, a więc tylko te wiersze X i elementy y, | |
# na które wskazują nam wektory `train` i `test` uzyskane z obiektu walidacji krzyżowej. | |
clf.fit(X[train], y[train]) | |
# Po zbudowaniu modelu, uzyskujemy od niego *predykcje*. Służy do tego metoda `predict()`. | |
# Do metody tej przekazujemy wyłącznie *zbiór testowy* (`X[test]`). | |
# Co ważne, nie wykorzystujemy tutaj etykiet (`y[test]`), które będą dla nas referencją dla wyliczenia metryki. | |
y_pred = clf.predict(X[test]) | |
# Wywołujemy funkcję metryczną (`metric`), aby ocenić jakość klasyfikatora w wybranym foldzie. | |
# Najpierw przekazujemy etykiety *stronniczości*, a w drugiej kolejności – *predykcje*. | |
score = metric(y[test], y_pred) | |
# Dzięki temu, że wcześniej pomyśleliśmy o użyciu funkcji `enumerate()`, mamy łatwy dostęp do *adresu wyniku* w macierzy *results*. | |
results[fold, clf_idx] = score | |
""" | |
Analiza statystyczna | |
""" | |
# Wyniki możemy wydrukować na ekranie. | |
print(results) | |
""" | |
[[0.885 0.935] | |
[0.89 0.92 ] | |
[0.92 0.965] | |
[0.85 0.935] | |
[0.855 0.94 ]] | |
""" | |
# Z racji na to, że drugi wymiar rezultatów powiązany był z klasyfikatorami, | |
# to kolumny macierzy `results` prezentują nam wyniki poszczególnych metod. | |
# *Odłóżmy je* do zmiennych `a` i `b`. | |
# Będą to pięcioelementowe wektory. | |
a = results[:,0] | |
b = results[:,1] | |
# Wydrukujmy je na ekranie. | |
print('a', a) | |
print('b', b) | |
""" | |
a [0.885 0.89 0.92 0.85 0.855] | |
b [0.935 0.915 0.965 0.94 0.94 ] | |
""" | |
# Raportując rezultaty eksperymentu będziemy informować odbiorcę o trzech sprawach. | |
# Pierwszą jest *wartość oczekiwana miary*, która estymowana jest przez średnią jakość rozpoznawania. | |
# [O to, do czego służy atrybut `axis` warto zapytać prowadzącą laboratorium!] | |
mean_results = np.mean(results, axis=0) | |
print(mean_results) | |
""" | |
[0.88 0.938] | |
""" | |
# Widzimy, że drugi klasyfikator (MLP) osiągnął o sześć procent wyższą efektywność niż pierwszy (GNB). | |
# Nie oznacza to jednak, że możemy już stwierdzić, że MLP jest lepsze od GNB | |
# Druga sprawa to *stabilność pomiaru*, którą określamy przez *statystyczną miarę różnorodności. | |
# Po ludzku – sprawdzamy, jak bardzo wynik odchylał się od foldu do foldu – co estymujemy przez miarę odchylenia standardowego. | |
std_results = np.std(results, axis=0) | |
print(std_results) | |
""" | |
[0.0254951 0.01749286] | |
""" | |
# Widzimy, że odchylenia standardowe sumują się do około czterech procent. | |
# Daje to nam intuicję, że MLP faktycznie powinno być lepsze niż GNB, ale nie możemy mieć pewności. | |
# Trzecia i ostatnia sprawa, to informacja o "statystycznie istotnej różnicy" pomiędzy wektorami a i b. | |
# Liczymy więc test. | |
stat_analysis = stest(a,b) | |
print(stat_analysis) | |
""" | |
TtestResult(statistic=-5.279456333376568, pvalue=0.006172416386220731, df=4) | |
""" | |
# Struktura testu statystycznego udostępnia nam trzy informacje. | |
# - wartość statystyki (`statistic`) | |
# - wartość prawdopodobieństwa (`pvalue`) | |
# - liczba punktów swobody (`df`) | |
# Statystyka ma znak ujemny. Oznacza to, że wektor `b` możemy uznać za dominujący nad wektorem `a`. | |
# Wartość prawdopodobieństwa jest niższa niż zadeklarowana `alpha`, w związku z czym uznajemy, że: | |
# "T-statystyka znajduje się poza przedziałem krytycznym testu, w związku z czym możemy odrzucić hipotezę zerową o wspólnym pochodzeniu zmiennych losowych a i b." | |
# "Oznacza to, że klasyfikator MLP w przeprowadzonym eksperymencie okazał się statystycznie istotnie lepszy niż GNB." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment