Skip to content

Instantly share code, notes, and snippets.

@xehivs

xehivs/script.py Secret

Created March 20, 2024 10:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xehivs/0def0261090abcae9ec2d88304bc7fe4 to your computer and use it in GitHub Desktop.
Save xehivs/0def0261090abcae9ec2d88304bc7fe4 to your computer and use it in GitHub Desktop.
MVP eksperymentu klasyfikacji
"""
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