Poznawanie nadzorowanych algorytmów uczenia maszynowego
Opublikowany: 2022-03-11Głównym celem tego czytania jest zrozumienie wystarczającej metodologii statystycznej, aby móc wykorzystać algorytmy uczenia maszynowego w bibliotece scikit-learn Pythona, a następnie zastosować tę wiedzę do rozwiązania klasycznego problemu uczenia maszynowego.
Pierwszy przystanek naszej podróży poprowadzi nas przez krótką historię uczenia maszynowego. Następnie zagłębimy się w różne algorytmy. Na naszym ostatnim przystanku wykorzystamy to, czego się nauczyliśmy, aby rozwiązać problem przewidywania współczynnika przeżycia Titanica.
Niektóre zastrzeżenia:
- Jestem pełnoprawnym inżynierem oprogramowania, a nie ekspertem od algorytmów uczenia maszynowego.
- Zakładam, że znasz podstawy Pythona.
- To jest eksploracyjne, więc nie każdy szczegół jest wyjaśniony tak, jak w samouczku.
Mając to na uwadze, zanurzmy się!
Szybkie wprowadzenie do algorytmów uczenia maszynowego
Gdy tylko wejdziesz w tę dziedzinę, zdasz sobie sprawę, że uczenie maszynowe jest mniej romantyczne, niż mogłoby się wydawać. Początkowo miałem nadzieję, że po nauczeniu się więcej będę mógł zbudować własną sztuczną inteligencję Jarvisa, która będzie spędzać cały dzień na kodowaniu oprogramowania i zarabianiu dla mnie, abym mógł spędzać całe dni na świeżym powietrzu, czytając książki, jeżdżąc motocyklem, i cieszę się lekkomyślnym stylem życia, podczas gdy mój osobisty Jarvis pogłębia moje kieszenie. Jednak szybko zdałem sobie sprawę, że podstawą algorytmów uczenia maszynowego są statystyki, które osobiście uważam za nudne i nieciekawe. Na szczęście okazało się, że „nudne” statystyki mają bardzo fascynujące zastosowania.
Wkrótce odkryjesz, że aby dostać się do tych fascynujących aplikacji, musisz bardzo dobrze rozumieć statystyki. Jednym z celów algorytmów uczenia maszynowego jest znalezienie zależności statystycznych w dostarczanych danych.
Dostarczone dane mogą obejmować wszystko, od sprawdzania ciśnienia krwi w stosunku do wieku po znajdowanie odręcznego tekstu na podstawie koloru różnych pikseli.
To powiedziawszy, byłem ciekawy, czy mógłbym użyć algorytmów uczenia maszynowego do znalezienia zależności w kryptograficznych funkcjach haszujących (SHA, MD5 itp.) — jednak tak naprawdę nie możesz tego zrobić, ponieważ odpowiednie prymitywy kryptograficzne są skonstruowane w taki sposób że eliminują zależności i generują znacznie trudne do przewidzenia dane wyjściowe. Uważam, że biorąc pod uwagę nieskończoną ilość czasu, algorytmy uczenia maszynowego mogłyby złamać każdy model kryptograficzny.
Niestety nie mamy tak dużo czasu, więc musimy znaleźć inny sposób na efektywne kopanie kryptowaluty. Jak daleko zaszliśmy do tej pory?
Krótka historia algorytmów uczenia maszynowego
Korzenie algorytmów uczenia maszynowego wywodzą się od Thomasa Bayesa, angielskiego statystyka żyjącego w XVIII wieku. Jego artykuł An Essay Towards Solving a Problem in the Doctrine of Chances stanowi podstawę twierdzenia Bayesa, które jest szeroko stosowane w dziedzinie statystyki.
W XIX wieku Pierre-Simon Laplace opublikował Theorie analytique des probabilites , rozszerzając prace Bayesa i definiując to, co znamy dzisiaj jako twierdzenie Bayesa. Niedługo wcześniej Adrien-Marie Legendre opisał metodę „najmniejszych kwadratów”, również powszechnie stosowaną dzisiaj w uczeniu nadzorowanym.
Wiek XX to okres, w którym dokonano większości znanych publicznie odkryć w tej dziedzinie. Andrey Markov wynalazł Markowa łańcuchy, których używał do analizy wierszy. Alan Turing zaproponował maszynę uczącą się, która mogłaby stać się sztucznie inteligentną, zasadniczo zapowiadając algorytmy genetyczne. Frank Rosenblatt wynalazł Perceptron , wywołując ogromne podekscytowanie i świetne relacje w mediach.
Ale potem lata siedemdziesiąte przyniosły wiele pesymizmu wokół idei sztucznej inteligencji – a tym samym ograniczonego finansowania – więc ten okres nazywa się zimą AI . Ponowne odkrycie propagacji wstecznej w latach 80. spowodowało odrodzenie badań nad uczeniem maszynowym. A dziś znowu jest to gorący temat.
Nieżyjący już Leo Breiman rozróżnił dwa paradygmaty modelowania statystycznego: modelowanie danych i modelowanie algorytmiczne. „Modelowanie algorytmiczne” oznacza mniej więcej algorytmy uczenia maszynowego, takie jak losowy las .
Uczenie maszynowe i statystyka to ściśle powiązane dziedziny. Według Michaela I. Jordana idee uczenia maszynowego, od zasad metodologicznych po narzędzia teoretyczne, miały długą prehistorię w statystyce. Zasugerował również, że nauka o danych jest terminem zastępczym dla ogólnego problemu, nad którym pracują specjaliści od uczenia maszynowego i statystycy.
Kategorie algorytmów uczenia maszynowego
Pole uczenia maszynowego opiera się na dwóch głównych filarach, zwanych uczeniem nadzorowanym i uczeniem bez nadzoru . Niektórzy uważają również, że nowy kierunek studiów — uczenie głębokie — należy oddzielić od kwestii uczenia się nadzorowanego i nienadzorowanego.
Uczenie nadzorowane ma miejsce wtedy, gdy komputerowi przedstawiane są przykłady danych wejściowych i pożądane wyniki. Celem komputera jest poznanie ogólnej formuły, która odwzorowuje wejścia na wyjścia. Można to dalej podzielić na:
- Częściowo nadzorowane uczenie , które polega na tym, że komputer otrzymuje niekompletny zestaw szkoleniowy, w którym brakuje niektórych wyników
- Aktywne uczenie się , kiedy komputer może uzyskać etykiety treningowe tylko dla bardzo ograniczonego zestawu instancji. W przypadku użycia interaktywnego ich zestawy treningowe mogą być prezentowane użytkownikowi do etykietowania.
- Uczenie ze wzmocnieniem , które polega na tym, że dane treningowe są przekazywane tylko jako informacja zwrotna o działaniach programu w dynamicznym środowisku, takim jak prowadzenie pojazdu lub granie z przeciwnikiem
W przeciwieństwie do tego, uczenie nienadzorowane ma miejsce wtedy, gdy w ogóle nie podano etykiet i od algorytmu zależy znalezienie struktury na wejściu. Uczenie się bez nadzoru może być celem samym w sobie, gdy musimy tylko odkryć ukryte wzorce.
Głębokie uczenie to nowy kierunek studiów, który jest inspirowany strukturą i funkcją ludzkiego mózgu i opiera się na sztucznych sieciach neuronowych, a nie tylko na koncepcjach statystycznych. Głębokie uczenie może być stosowane zarówno w podejściu nadzorowanym, jak i nienadzorowanym.
W tym artykule omówimy tylko niektóre z prostszych nadzorowanych algorytmów uczenia maszynowego i wykorzystamy je do obliczenia szans na przeżycie jednostki podczas tragicznego zatonięcia Titanica. Ogólnie rzecz biorąc, jeśli nie masz pewności, którego algorytmu użyć, dobrym miejscem na rozpoczęcie jest ściągawka z algorytmem uczenia maszynowego scikit-learn.
Podstawowe nadzorowane modele uczenia maszynowego
Być może najłatwiejszym możliwym algorytmem jest regresja liniowa. Czasami można to przedstawić graficznie jako linię prostą, ale pomimo swojej nazwy, jeśli istnieje hipoteza wielomianowa, ta linia może zamiast tego być krzywą. Tak czy inaczej, modeluje relacje między skalarną zmienną zależną $y$ a jedną lub większą liczbą wartości objaśniających oznaczonych przez $x$.
W terminologii laika oznacza to, że regresja liniowa jest algorytmem, który uczy się zależności między każdym znanym $x$ i $y$, tak że później możemy go użyć do przewidzenia $y$ dla nieznanej próbki $x$.
W naszym pierwszym przykładzie uczenia nadzorowanego użyjemy podstawowego modelu regresji liniowej, aby przewidzieć ciśnienie krwi danej osoby na podstawie jej wieku. Jest to bardzo prosty zbiór danych o dwóch znaczących cechach: wieku i ciśnieniu krwi.
Jak już wspomniano powyżej, większość algorytmów uczenia maszynowego działa na zasadzie znajdowania statystycznej zależności w dostarczanych im danych. Ta zależność nazywana jest hipotezą i jest zwykle oznaczana przez $h(\theta)$.
Aby obliczyć hipotezę, zacznijmy od załadowania i zbadania danych.
import matplotlib.pyplot as plt from pandas import read_csv import os # Load data data_path = os.path.join(os.getcwd(), "data/blood-pressure.txt") dataset = read_csv(data_path, delim_whitespace=True) # We have 30 entries in our dataset and four features. The first feature is the ID of the entry. # The second feature is always 1. The third feature is the age and the last feature is the blood pressure. # We will now drop the ID and One feature for now, as this is not important. dataset = dataset.drop(['ID', 'One'], axis=1) # And we will display this graph %matplotlib inline dataset.plot.scatter(x='Age', y='Pressure') # Now, we will assume that we already know the hypothesis and it looks like a straight line h = lambda x: 84 + 1.24 * x # Let's add this line on the chart now ages = range(18, 85) estimated = [] for i in ages: estimated.append(h(i)) plt.plot(ages, estimated, 'b') [<matplotlib.lines.Line2D at 0x11424b828>]
Na powyższym wykresie każda niebieska kropka reprezentuje naszą próbkę danych, a niebieska linia to hipoteza, której nasz algorytm musi się nauczyć. Czym właściwie jest ta hipoteza?
Aby rozwiązać ten problem, musimy nauczyć się zależności między $x$ a $y$, co oznacza $y = f(x)$. Dlatego $f(x)$ jest idealną funkcją celu. Algorytm uczenia maszynowego spróbuje odgadnąć funkcję hipotezy $h(x)$, która jest najbliższym przybliżeniem nieznanego $f(x)$.
Najprostsza możliwa postać hipotezy dla problemu regresji liniowej wygląda następująco: $h_\theta(x) = \theta_0 + \theta_1 * x$. Mamy pojedynczą wejściową zmienną skalarną $x$, która wyprowadza pojedynczą zmienną skalarną $y$, gdzie $\theta_0$ i $\theta_1$ to parametry, których musimy się nauczyć. Proces dopasowywania tej niebieskiej linii do danych nazywa się regresją liniową. Ważne jest, aby zrozumieć, że mamy tylko jeden parametr wejściowy $x_1$; jednak wiele funkcji hipotez będzie zawierało również jednostkę odchylenia ($x_0$). Zatem nasza wynikowa hipoteza ma postać $h_\theta(x) = \theta_0 * x_0 + \theta_1 * x_1$. Ale możemy uniknąć pisania $x_0$, ponieważ prawie zawsze jest równe 1.
Wracając do niebieskiej linii. Nasza hipoteza wygląda następująco: $h(x) = 84 + 1,24x$, co oznacza, że $\theta_0 = 84$ i $\theta_1 = 1,24$. Jak możemy automatycznie wyprowadzić te wartości $\theta$?
Musimy zdefiniować funkcję kosztu . Zasadniczo funkcja kosztu po prostu oblicza pierwiastek błędu średniokwadratowego między przewidywaniem modelu a rzeczywistym wynikiem.
\[J(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Na przykład nasza hipoteza przewiduje, że dla osoby w wieku 48 lat ciśnienie krwi powinno wynosić $h(48) = 84 + 1,24 * 48 = 143 mmHg$; jednak w naszej próbce szkoleniowej mamy wartość 130 $ mmHg $. Dlatego błąd wynosi $(143 - 130)^2 = 169 $. Teraz musimy obliczyć ten błąd dla każdego wpisu w naszym uczącym zbiorze danych, a następnie zsumować go ($\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i )})^2$) i wyjmij z tego średnią wartość.
To daje nam pojedynczą liczbę skalarną, która reprezentuje koszt funkcji. Naszym celem jest znalezienie wartości $\theta$ takich, że funkcja kosztu jest najniższa; innymi słowy, chcemy zminimalizować funkcję kosztu. Miejmy nadzieję, że będzie to wydawać się intuicyjne: jeśli mamy małą wartość funkcji kosztu, oznacza to, że błąd przewidywania również jest mały.
import numpy as np # Let's calculate the cost for the hypothesis above h = lambda x, theta_0, theta_1: theta_0 + theta_1 * x def cost(X, y, t0, t1): m = len(X) # the number of the training samples c = np.power(np.subtract(h(X, t0, t1), y), 2) return (1 / (2 * m)) * sum(c) X = dataset.values[:, 0] y = dataset.values[:, 1] print('J(Theta) = %2.2f' % cost(X, y, 84, 1.24)) J(Theta) = 1901.95
Teraz musimy znaleźć takie wartości $\theta$, aby wartość naszej funkcji kosztu była minimalna. Ale jak to robimy?
\[minJ(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\ ]Istnieje kilka możliwych algorytmów, ale najpopularniejszym jest zejście gradientowe . Aby zrozumieć intuicję stojącą za metodą opadania gradientu, najpierw wykreślmy ją na wykresie. Dla uproszczenia przyjmiemy prostszą hipotezę $h(\theta) = \theta_1 * x$. Następnie wykreślimy prosty wykres 2D, gdzie $x$ to wartość $\theta$, a $y$ to funkcja kosztu w tym momencie.
import matplotlib.pyplot as plt fig = plt.figure() # Generate the data theta_1 = np.arange(-10, 14, 0.1) J_cost = [] for t1 in theta_1: J_cost += [ cost(X, y, 0, t1) ] plt.plot(theta_1, J_cost) plt.xlabel(r'$\theta_1$') plt.ylabel(r'$J(\theta)$') plt.show() Funkcja kosztu jest wypukła, co oznacza, że na przedziale $[a, b]$ jest tylko jedno minimum. Co ponownie oznacza, że najlepsze parametry $\theta$ znajdują się w punkcie, w którym funkcja kosztu jest minimalna.
Zasadniczo gradient to algorytm, który próbuje znaleźć zestaw parametrów minimalizujących funkcję. Rozpoczyna się od początkowego zestawu parametrów i iteracyjnie wykonuje kroki w kierunku ujemnym gradientu funkcji.
Jeśli obliczymy pochodną funkcji hipotezy w określonym punkcie, da nam to nachylenie linii stycznej do krzywej w tym punkcie. Oznacza to, że możemy obliczyć nachylenie w każdym punkcie na wykresie.
Sposób działania algorytmu jest następujący:
- Wybieramy losowy punkt początkowy (losowy $\theta$).
- Oblicz pochodną funkcji kosztu w tym momencie.
- Zrób mały krok w kierunku zbocza $\theta_j := \theta_j - \lambda * \frac{\partial}{\partial \theta_j} * J(\theta)$.
- Powtarzaj kroki 2-3, aż się zbiegniemy.
Teraz warunek zbieżności zależy od implementacji algorytmu. Możemy zatrzymać się po 50 krokach, po jakimś progu lub cokolwiek innego.
import math # Example of the simple gradient descent algorithm taken from Wikipedia cur_x = 2.5 # The algorithm starts at point x gamma = 0.005 # Step size multiplier precision = 0.00001 previous_step_size = cur_x df = lambda x: 2 * x * math.cos(x) # Remember the learning curve and plot it while previous_step_size > precision: prev_x = cur_x cur_x += -gamma * df(prev_x) previous_step_size = abs(cur_x - prev_x) print("The local minimum occurs at %f" % cur_x) The local minimum occurs at 4.712194
W tym artykule nie będziemy zaimplementować tych algorytmów. Zamiast tego wykorzystamy szeroko przyjętą scikit-learn , bibliotekę uczenia maszynowego typu open source w języku Python. Zapewnia wiele bardzo przydatnych interfejsów API dla różnych problemów z eksploracją danych i uczeniem maszynowym.
from sklearn.linear_model import LinearRegression # LinearRegression uses the gradient descent method # Our data X = dataset[['Age']] y = dataset[['Pressure']] regr = LinearRegression() regr.fit(X, y) # Plot outputs plt.xlabel('Age') plt.ylabel('Blood pressure') plt.scatter(X, y, color='black') plt.plot(X, regr.predict(X), color='blue') plt.show() plt.gcf().clear() <matplotlib.figure.Figure at 0x120fae1d0>
print( 'Predicted blood pressure at 25 yo = ', regr.predict(25) ) print( 'Predicted blood pressure at 45 yo = ', regr.predict(45) ) print( 'Predicted blood pressure at 27 yo = ', regr.predict(27) ) print( 'Predicted blood pressure at 34.5 yo = ', regr.predict(34.5) ) print( 'Predicted blood pressure at 78 yo = ', regr.predict(78) ) Predicted blood pressure at 25 yo = [[ 122.98647692]] Predicted blood pressure at 45 yo = [[ 142.40388395]] Predicted blood pressure at 27 yo = [[ 124.92821763]] Predicted blood pressure at 34.5 yo = [[ 132.20974526]] Predicted blood pressure at 78 yo = [[ 174.44260555]]Rodzaje danych statystycznych
Podczas pracy z danymi dotyczącymi problemów z uczeniem maszynowym ważne jest rozpoznawanie różnych typów danych. Możemy mieć dane liczbowe (ciągłe lub dyskretne), kategoryczne lub porządkowe.
Dane liczbowe mają znaczenie jako pomiar. Na przykład wiek, waga, liczba bitcoinów, które dana osoba posiada lub ile artykułów może napisać miesięcznie. Dane liczbowe można dalej podzielić na typy dyskretne i ciągłe.
- Dane dyskretne reprezentują dane, które można policzyć za pomocą liczb całkowitych, np. liczbę pokoi w mieszkaniu lub liczbę rzutów monetą.
- Dane ciągłe niekoniecznie muszą być reprezentowane przez liczby całkowite. Na przykład, jeśli mierzysz odległość, jaką możesz przeskoczyć, może to być 2 metry, 1,5 metra lub 1,652245 metrów.
Dane kategoryczne reprezentują wartości takie jak płeć osoby, stan cywilny, kraj itp. Dane te mogą przybierać wartości liczbowe, ale liczby te nie mają znaczenia matematycznego. Nie możesz ich dodać do siebie.
Dane porządkowe mogą być mieszanką dwóch pozostałych typów, przy czym kategorie mogą być numerowane w matematycznie sensowny sposób. Typowym przykładem są oceny: często jesteśmy proszeni o ocenę rzeczy w skali od jednego do dziesięciu i dozwolone są tylko liczby całkowite. Chociaż możemy użyć tego liczbowo — np. aby znaleźć średnią ocenę dla czegoś — często traktujemy dane tak, jakby były kategoryczne, jeśli chodzi o zastosowanie do nich metod uczenia maszynowego.
Regresja logistyczna
Regresja liniowa to niesamowity algorytm, który pomaga nam przewidzieć wartości liczbowe, np. cenę domu przy określonej wielkości i liczbie pokoi. Czasami jednak możemy również chcieć przewidzieć dane kategoryczne, aby uzyskać odpowiedzi na pytania takie jak:
- Czy to pies czy kot?
- Czy ten nowotwór jest złośliwy czy łagodny?
- Czy to wino jest dobre czy złe?
- Czy ten e-mail jest spamem, czy nie?
Lub nawet:
- Który numer jest na zdjęciu?
- Do jakiej kategorii należy ten e-mail?
Wszystkie te pytania dotyczą problemu klasyfikacji . A najprostszy algorytm klasyfikacji nazywa się regresją logistyczną , która ostatecznie jest taka sama jak regresja liniowa , z tym wyjątkiem, że ma inną hipotezę.
Przede wszystkim możemy ponownie użyć tej samej hipotezy liniowej $h_\theta(x) = \theta^TX$ (jest to w formie zwektoryzowanej). Podczas gdy regresja liniowa może wyprowadzić dowolną liczbę z przedziału $[a, b]$, regresja logistyczna może wyprowadzić tylko wartości w $[−1, 1]$, które jest prawdopodobieństwem, że obiekt znajdzie się w danej kategorii lub nie.
Używając funkcji sigmoid , możemy przekonwertować dowolną wartość liczbową na wartość z przedziału $[−1, 1]$.
\[f(x) = \frac{1}{1 + e^x}\]Teraz, zamiast $x$, musimy przekazać istniejącą hipotezę i dlatego otrzymamy:
\[f(x) = \frac{1}{1 + e^{\theta_0 + \theta_1 * x_1 + ... + \theta_n * x_n}}\]Następnie możemy zastosować prosty próg mówiący, że jeśli hipoteza jest większa od zera, to jest to wartość prawdziwa, w przeciwnym razie fałszywa.
\[h_\theta(x) = \begin{cases} 1 & \mbox{if } \theta^TX > 0 \\ 0 & \mbox{else} \end{cases}\]Oznacza to, że możemy użyć tej samej funkcji kosztu i tego samego algorytmu opadania gradientu, aby nauczyć się hipotezy dla regresji logistycznej.
W naszym następnym przykładzie algorytmu uczenia maszynowego doradzimy pilotom promu kosmicznego, czy powinni używać automatycznego czy ręcznego sterowania lądowaniem. Mamy bardzo mały zbiór danych — 15 próbek — który składa się z sześciu funkcji i podstawowej prawdy .
W algorytmach uczenia maszynowego termin „ prawda podstawowa ” odnosi się do dokładności klasyfikacji zestawu uczącego dla technik uczenia nadzorowanego.
Nasz zbiór danych jest kompletny, co oznacza, że nie brakuje w nim żadnych funkcji; jednak niektóre funkcje mają „*” zamiast kategorii, co oznacza, że ta funkcja nie ma znaczenia. Wszystkie takie gwiazdki zastąpimy zerami.
from sklearn.linear_model import LogisticRegression # Data data_path = os.path.join(os.getcwd(), "data/shuttle-landing-control.csv") dataset = read_csv(data_path, header=None, names=['Auto', 'Stability', 'Error', 'Sign', 'Wind', 'Magnitude', 'Visibility'], na_values='*').fillna(0) # Prepare features X = dataset[['Stability', 'Error', 'Sign', 'Wind', 'Magnitude', 'Visibility']] y = dataset[['Auto']].values.reshape(1, -1)[0] model = LogisticRegression() model.fit(X, y) # For now, we're missing one important concept. We don't know how well our model # works, and because of that, we cannot really improve the performance of our hypothesis. # There are a lot of useful metrics, but for now, we will validate how well # our algorithm performs on the dataset it learned from. "Score of our model is %2.2f%%" % (model.score(X, y) * 100) Score of our model is 73.33%
Walidacja?
W poprzednim przykładzie sprawdziliśmy wydajność naszego modelu przy użyciu danych uczących. Jednak czy jest to teraz dobra opcja, biorąc pod uwagę, że nasz algorytm może albo niedopracować, albo przepełnić dane? Rzućmy okiem na prostszy przykład, gdy mamy jedną cechę, która reprezentuje wielkość domu, a drugą, która reprezentuje jego cenę.
from sklearn.pipeline import make_pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.model_selection import cross_val_score # Ground truth function ground_truth = lambda X: np.cos(15 + np.pi * X) # Generate random observations around the ground truth function n_samples = 15 degrees = [1, 4, 30] X = np.linspace(-1, 1, n_samples) y = ground_truth(X) + np.random.randn(n_samples) * 0.1 plt.figure(figsize=(14, 5)) models = {} # Plot all machine learning algorithm models for idx, degree in enumerate(degrees): ax = plt.subplot(1, len(degrees), idx + 1) plt.setp(ax, xticks=(), yticks=()) # Define the model polynomial_features = PolynomialFeatures(degree=degree) model = make_pipeline(polynomial_features, LinearRegression()) models[degree] = model # Train the model model.fit(X[:, np.newaxis], y) # Evaluate the model using cross-validation scores = cross_val_score(model, X[:, np.newaxis], y) X_test = X plt.plot(X_test, model.predict(X_test[:, np.newaxis]), label="Model") plt.scatter(X, y, edgecolor='b', s=20, label="Observations") plt.xlabel("x") plt.ylabel("y") plt.ylim((-2, 2)) plt.title("Degree {}\nMSE = {:.2e}".format( degree, -scores.mean())) plt.show() 
Model algorytmu uczenia maszynowego jest niedostateczny , jeśli nie może uogólniać ani danych uczących, ani nowych obserwacji. W powyższym przykładzie używamy prostej hipotezy liniowej, która tak naprawdę nie reprezentuje rzeczywistego zestawu danych treningowych i będzie miała bardzo słabą wydajność. Zazwyczaj niedopasowanie nie jest omawiane, ponieważ można je łatwo wykryć przy dobrej metryce.
Jeśli nasz algorytm pamięta każdą pokazaną obserwację, będzie miał słabą wydajność w nowych obserwacjach poza treningowym zbiorem danych. Nazywa się to overfittingiem . Na przykład, model wielomianowy 30 stopni przechodzi przez większość punktów i ma bardzo dobry wynik w zbiorze uczącym, ale wszystko poza tym działałoby źle.
Nasz zestaw danych składa się z jednej funkcji i jest łatwy do wykreślenia w przestrzeni 2D; jednak w prawdziwym życiu możemy mieć zbiory danych z setkami funkcji, co uniemożliwia ich wizualne nakreślenie w przestrzeni euklidesowej. Jakie mamy inne opcje, aby sprawdzić, czy model jest niedopasowany lub przesadnie dopasowany?
Czas przedstawić Ci koncepcję krzywej uczenia się . Jest to prosty wykres, który przedstawia średni kwadrat błędu na podstawie liczby próbek uczących.
W materiałach do nauki zazwyczaj zobaczysz wykresy podobne do tych:
Jednak w prawdziwym życiu możesz nie uzyskać tak doskonałego obrazu. Wykreślmy krzywą uczenia się dla każdego z naszych modeli.
from sklearn.model_selection import learning_curve, ShuffleSplit # Plot learning curves plt.figure(figsize=(20, 5)) for idx, degree in enumerate(models): ax = plt.subplot(1, len(degrees), idx + 1) plt.title("Degree {}".format(degree)) plt.grid() plt.xlabel("Training examples") plt.ylabel("Score") train_sizes = np.linspace(.6, 1.0, 6) # Cross-validation with 100 iterations to get a smoother mean test and training # score curves, each time with 20% of the data randomly selected as a validation set. cv = ShuffleSplit(n_splits=100, test_size=0.2, random_state=0) model = models[degree] train_sizes, train_scores, test_scores = learning_curve( model, X[:, np.newaxis], y, cv=cv, train_sizes=train_sizes, n_jobs=4) train_scores_mean = np.mean(train_scores, axis=1) test_scores_mean = np.mean(test_scores, axis=1) plt.plot(train_sizes, train_scores_mean, 'o-', color="r", label="Training score") plt.plot(train_sizes, test_scores_mean, 'o-', color="g", label="Test score") plt.legend(loc = "best") plt.show() W naszym symulowanym scenariuszu niebieska linia, która reprezentuje wynik treningu, wydaje się być linią prostą. W rzeczywistości nadal nieznacznie maleje – faktycznie widać to na wykresie wielomianowym pierwszego stopnia, ale w innych jest to zbyt subtelne, aby to stwierdzić w tej rozdzielczości. Przynajmniej wyraźnie widzimy, że istnieje ogromna luka między krzywymi uczenia się dla treningu a obserwacjami testowymi ze scenariuszem „wysokiego błędu”.
Na „normalnym” wykresie szybkości uczenia się pośrodku możesz zobaczyć, jak łączą się linie wyniku treningu i wyniku testu.
A na wykresie „wysokiej wariancji” widać, że przy małej liczbie próbek wyniki testu i treningu są bardzo podobne; jednak po zwiększeniu liczby próbek wynik treningu pozostaje prawie doskonały, podczas gdy wynik testu od niego rośnie.
Możemy naprawić niedopasowane modele (zwane również modelami z wysokim obciążeniem ), jeśli zastosujemy hipotezę nieliniową, np. hipotezę o większej liczbie cech wielomianowych.
Nasz overfitting model ( high variance ) przechodzi przez każdy pokazany przykład; jednak gdy wprowadzamy dane testowe, luka między krzywymi uczenia się powiększa się. Możemy użyć regularyzacji, walidacji krzyżowej i większej liczby próbek danych, aby naprawić przesadne modele.
Weryfikacja krzyżowa
Jedną z powszechnych praktyk unikania nadmiernego dopasowania jest trzymanie części dostępnych danych i używanie ich jako zestawu testowego. Jednak podczas oceny różnych ustawień modelu, takich jak liczba cech wielomianowych, nadal istnieje ryzyko przesadnego dopasowania zestawu testowego, ponieważ parametry można modyfikować, aby uzyskać optymalną wydajność estymatora, a dzięki temu nasza wiedza na temat zestawu testowego może wyciek do modelu. Aby rozwiązać ten problem, musimy trzymać się jeszcze jednej części zbioru danych, która nazywa się „zestawem walidacyjnym”. Szkolenie przebiega na zbiorze uczącym, a gdy uznamy, że osiągnęliśmy optymalną wydajność modelu, możemy dokonać ostatecznej oceny przy użyciu zbioru walidacyjnego.
Jednak dzieląc dostępne dane na trzy zestawy, radykalnie zmniejszamy liczbę próbek, które można wykorzystać do uczenia modeli, a wyniki mogą zależeć od konkretnego losowego wyboru dla pary zestawów uczenie-walidacja.
Jednym z rozwiązań tego problemu jest procedura zwana walidacją krzyżową. W standardowej walidacji krzyżowej $k$-fold, dzielimy dane na podzbiory $k$, zwane fałdami. Następnie iteracyjnie trenujemy algorytm na fałdach $k-1$, używając pozostałego fałdu jako zestawu testowego (zwanego „foldout fold”).
Walidacja krzyżowa pozwala na dostrojenie parametrów tylko za pomocą oryginalnego zestawu treningowego. Pozwala to zachować zestaw testowy jako naprawdę niewidoczny zestaw danych do wyboru ostatecznego modelu.
Istnieje znacznie więcej technik walidacji krzyżowej, takich jak pomijanie P , warstwowe składanie $k$ , tasowanie i dzielenie itp., ale wykraczają one poza zakres tego artykułu.
Regularyzacja
To kolejna technika, która może pomóc w rozwiązaniu problemu nadmiernego dopasowania modelu. Większość zbiorów danych ma wzór i trochę szumu. Celem regularyzacji jest zmniejszenie wpływu szumu na model.
Istnieją trzy główne techniki regularyzacji: Lasso, Tichonow i elastyczna sieć.
Regularyzacja L1 (lub regularyzacja Lasso ) wybierze pewne cechy do zmniejszenia do zera, tak aby nie odgrywały żadnej roli w ostatecznym modelu. L1 można postrzegać jako metodę wyboru ważnych funkcji.
Regularyzacja L2 (lub regularyzacja Tichonowa ) wymusza, aby wszystkie cechy były stosunkowo małe, tak że będą miały mniejszy wpływ na model.
Siatka elastyczna to połączenie L1 i L2.
Normalizacja (skalowanie funkcji)
Skalowanie funkcji jest również ważnym krokiem podczas wstępnego przetwarzania danych. Nasz zbiór danych może zawierać cechy o wartościach $[-\infty, \infty]$ oraz inne cechy o innej skali. Jest to metoda standaryzacji zakresów niezależnych wartości.
Skalowanie funkcji jest również ważnym procesem poprawiającym wydajność modeli uczenia się. Po pierwsze, opadanie gradientu będzie zbieżne znacznie szybciej, jeśli wszystkie cechy zostaną przeskalowane do tej samej normy. Ponadto wiele algorytmów — na przykład maszyny wektorów nośnych (SVM) — działa, obliczając odległość między dwoma punktami, a jeśli jedna z funkcji ma szerokie wartości, to ta funkcja będzie miała duży wpływ na odległość.
Wsparcie maszyn wektorowych
SVM to kolejny szeroko rozpowszechniony algorytm uczenia maszynowego, który można wykorzystać do rozwiązywania problemów z klasyfikacją i regresją. W SVM wykreślamy każdą obserwację jako punkt w przestrzeni $n$-wymiarowej, gdzie $n$ to liczba cech, które posiadamy. Wartością każdej cechy jest wartość poszczególnych współrzędnych. Następnie próbujemy znaleźć hiperpłaszczyznę, która wystarczająco dobrze oddziela dwie klasy.
Po zidentyfikowaniu najlepszej hiperpłaszczyzny, chcemy dodać marginesy, które jeszcze bardziej oddzielą te dwie klasy.
SVM jest bardzo skuteczny, gdy liczba funkcji jest bardzo duża lub jeśli liczba funkcji jest większa niż liczba próbek danych. Ponieważ jednak SVM działa na zasadzie wektorowej, ważne jest, aby znormalizować dane przed użyciem.
Sieci neuronowe
Algorytmy sieci neuronowych są prawdopodobnie najbardziej ekscytującą dziedziną badań nad uczeniem maszynowym. Sieci neuronowe próbują naśladować sposób, w jaki neurony w mózgu są ze sobą połączone.
Tak wygląda sieć neuronowa. Łączymy wiele węzłów razem, gdzie każdy węzeł pobiera zestaw danych wejściowych, stosujemy na nich obliczenia i wyprowadza wartość.
Istnieje ogromna różnorodność algorytmów sieci neuronowych do uczenia nadzorowanego i nienadzorowanego. Sieci neuronowe mogą być używane do prowadzenia autonomicznych samochodów, grania w gry, lądowania samolotów, klasyfikowania obrazów i nie tylko.
Niesławny Titanic
RMS Titanic był brytyjskim liniowcem pasażerskim, który zatonął na Północnym Oceanie Atlantyckim 15 kwietnia 1912 roku po zderzeniu z górą lodową. Było około 2224 członków załogi i pasażerów, a ponad 1500 zginęło, co czyni ją jedną z najbardziej śmiertelnych katastrof morskich wszechczasów.
Teraz, ponieważ rozumiemy intuicję stojącą za najbardziej podstawowymi algorytmami uczenia maszynowego używanymi do rozwiązywania problemów z klasyfikacją, możemy wykorzystać naszą wiedzę do przewidywania wyniku przeżycia osób na pokładzie Titanica.
Nasz zbiór danych zostanie wypożyczony z platformy konkursowej Kaggle data science.
import os from pandas import read_csv, concat # Load data data_path = os.path.join(os.getcwd(), "data/titanic.csv") dataset = read_csv(data_path, skipinitialspace=True) dataset.head(5)| Identyfikator pasażera | Przeżył | Klasa P | Imię | Seks | Wiek | SibSp | Spiec | Bilet | Opłata | Kabina | Zaokrętowany | |
| 0 | 1 | 0 | 3 | Braund, pan Owen Harris | mężczyzna | 22,0 | 1 | 0 | A/5 21171 | 7,2500 | NaN | S |
| 1 | 2 | 1 | 1 | Cumings, pani John Bradley (Florence Briggs Th... | Płeć żeńska | 38,0 | 1 | 0 | PC 17599 | 71,2833 | C85 | C |
| 2 | 3 | 1 | 3 | Heikkinen, panno Laina | Płeć żeńska | 26,0 | 0 | 0 | STON/O2. 3101282 | 7,9250 | NaN | S |
| 3 | 4 | 1 | 1 | Futrelle, pani Jacques Heath (Lily May Peel) | Płeć żeńska | 35,0 | 1 | 0 | 113803 | 53.1000 | C123 | S |
| 4 | 5 | 0 | 3 | Allen, Pan William Henry | mężczyzna | 35,0 | 0 | 0 | 373450 | 8.0500 | NaN | S |
Naszym pierwszym krokiem byłoby załadowanie i zbadanie danych. Mamy 891 rekordów testowych; każdy rekord ma następującą strukturę:
- PassengerId – identyfikator pasażera na pokładzie
- przetrwanie – czy osoba przeżyła katastrofę, czy nie
- pclass – Klasa biletu, np. 1., 2., 3.
- płeć – Płeć pasażera: mężczyzna lub kobieta
- nazwa – Zawiera tytuł
- wiek – Wiek w latach
- sibsp – liczba rodzeństwa/małżonków na pokładzie Titanica
- parch – liczba rodziców/dzieci na pokładzie Titanica
- bilet – Numer biletu
- taryfa – taryfa pasażera
- kabina – Numer kabiny
- zaokrętowany – Port zaokrętowania
Ten zbiór danych zawiera zarówno dane liczbowe, jak i kategoryczne. Zwykle dobrym pomysłem jest zagłębienie się w dane i na ich podstawie sformułowanie założeń. Jednak w tym przypadku pominiemy ten krok i przejdziemy od razu do przewidywań.
import pandas as pd # We need to drop some insignificant features and map the others. # Ticket number and fare should not contribute much to the performance of our models. # Name feature has titles (eg, Mr., Miss, Doctor) included. # Gender is definitely important. # Port of embarkation may contribute some value. # Using port of embarkation may sound counter-intuitive; however, there may # be a higher survival rate for passengers who boarded in the same port. dataset['Title'] = dataset.Name.str.extract(' ([A-Za-z]+)\.', expand=False) dataset = dataset.drop(['PassengerId', 'Ticket', 'Cabin', 'Name'], axis=1) pd.crosstab(dataset['Title'], dataset['Sex'])| Title \ Sex | female | male |
| Capt | 0 | 1 |
| Col | 0 | 2 |
| Countess | 1 | 0 |
| Don | 0 | 1 |
| Dr | 1 | 6 |
| Jonkheer | 0 | 1 |
| Lady | 1 | 0 |
| Poważny | 0 | 2 |
| Master | 0 | 40 |
| Miss | 182 | 0 |
| Mlle | 2 | 0 |
| Mme | 1 | 0 |
| Mr | 0 | 517 |
| Mrs | 125 | 0 |
| Ms | 1 | 0 |
| Rev | 0 | 6 |
| Sir | 0 | 1 |
# We will replace many titles with a more common name, English equivalent, # or reclassification dataset['Title'] = dataset['Title'].replace(['Lady', 'Countess','Capt', 'Col',\ 'Don', 'Dr', 'Major', 'Rev', 'Sir', 'Jonkheer', 'Dona'], 'Other') dataset['Title'] = dataset['Title'].replace('Mlle', 'Miss') dataset['Title'] = dataset['Title'].replace('Ms', 'Miss') dataset['Title'] = dataset['Title'].replace('Mme', 'Mrs') dataset[['Title', 'Survived']].groupby(['Title'], as_index=False).mean()| Tytuł | Survived | |
| 0 | Master | 0.575000 |
| 1 | Miss | 0.702703 |
| 2 | Mr | 0.156673 |
| 3 | Mrs | 0.793651 |
| 4 | Inny | 0.347826 |
# Now we will map alphanumerical categories to numbers title_mapping = { 'Mr': 1, 'Miss': 2, 'Mrs': 3, 'Master': 4, 'Other': 5 } gender_mapping = { 'female': 1, 'male': 0 } port_mapping = { 'S': 0, 'C': 1, 'Q': 2 } # Map title dataset['Title'] = dataset['Title'].map(title_mapping).astype(int) # Map gender dataset['Sex'] = dataset['Sex'].map(gender_mapping).astype(int) # Map port freq_port = dataset.Embarked.dropna().mode()[0] dataset['Embarked'] = dataset['Embarked'].fillna(freq_port) dataset['Embarked'] = dataset['Embarked'].map(port_mapping).astype(int) # Fix missing age values dataset['Age'] = dataset['Age'].fillna(dataset['Age'].dropna().median()) dataset.head()| Survived | Pclass | Sex | Wiek | SibSp | Parch | Fare | Embarked | Tytuł | |
| 0 | 0 | 3 | 0 | 22,0 | 1 | 0 | 7.2500 | 0 | 1 |
| 1 | 1 | 1 | 1 | 38,0 | 1 | 0 | 71.2833 | 1 | 3 |
| 2 | 1 | 3 | 1 | 26,0 | 0 | 0 | 7.9250 | 0 | 2 |
| 3 | 1 | 1 | 1 | 35,0 | 1 | 0 | 53.1000 | 0 | 3 |
| 4 | 0 | 3 | 0 | 35,0 | 0 | 0 | 8.0500 | 0 | 1 |
At this point, we will rank different types of machine learning algorithms in Python by using scikit-learn to create a set of different models. It will then be easy to see which one performs the best.
- Logistic regression with varying numbers of polynomials
- Support vector machine with a linear kernel
- Support vector machine with a polynomial kernel
- Neural network
For every single model, we will use $k$-fold validation.
from sklearn.model_selection import KFold, train_test_split from sklearn.pipeline import make_pipeline from sklearn.preprocessing import PolynomialFeatures, StandardScaler from sklearn.neural_network import MLPClassifier from sklearn.svm import SVC # Prepare the data X = dataset.drop(['Survived'], axis = 1).values y = dataset[['Survived']].values X = StandardScaler().fit_transform(X) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state = None) # Prepare cross-validation (cv) cv = KFold(n_splits = 5, random_state = None) # Performance p_score = lambda model, score: print('Performance of the %s model is %0.2f%%' % (model, score * 100)) # Classifiers names = [ "Logistic Regression", "Logistic Regression with Polynomial Hypotheses", "Linear SVM", "RBF SVM", "Neural Net", ] classifiers = [ LogisticRegression(), make_pipeline(PolynomialFeatures(3), LogisticRegression()), SVC(kernel="linear", C=0.025), SVC(gamma=2, C=1), MLPClassifier(alpha=1), ] # iterate over classifiers models = [] trained_classifiers = [] for name, clf in zip(names, classifiers): scores = [] for train_indices, test_indices in cv.split(X): clf.fit(X[train_indices], y[train_indices].ravel()) scores.append( clf.score(X_test, y_test.ravel()) ) min_score = min(scores) max_score = max(scores) avg_score = sum(scores) / len(scores) trained_classifiers.append(clf) models.append((name, min_score, max_score, avg_score)) fin_models = pd.DataFrame(models, columns = ['Name', 'Min Score', 'Max Score', 'Mean Score']) fin_models.sort_values(['Mean Score']).head()| Imię | Min Score | Max Score | Mean Score | |
| 2 | Linear SVM | 0.793296 | 0.821229 | 0.803352 |
| 0 | Logistic Regression | 0.826816 | 0.860335 | 0.846927 |
| 4 | Neural Net | 0.826816 | 0.860335 | 0.849162 |
| 1 | Logistic Regression with Polynomial Hypotheses | 0.854749 | 0.882682 | 0.869274 |
| 3 | RBF SVM | 0.843575 | 0.888268 | 0.869274 |
Ok, so our experimental research says that the SVM classifier with a radial basis function (RBF) kernel performs the best. Now, we can serialize our model and re-use it in production applications.
import pickle svm_model = trained_classifiers[3] data_path = os.path.join(os.getcwd(), "best-titanic-model.pkl") pickle.dump(svm_model, open(data_path, 'wb'))Machine learning is not complicated, but it's a very broad field of study, and it requires knowledge of math and statistics in order to grasp all of its concepts.
Right now, machine learning and deep learning are among the hottest topics of discussion in Silicon Valley, and are the bread and butter of almost every data science company, mainly because they can automate many repetitive tasks including speech recognition, driving vehicles, financial trading, caring for patients, cooking, marketing, and so on.
Now you can take this knowledge and solve challenges on Kaggle.
This was a very brief introduction to supervised machine learning algorithms. Luckily, there are a lot of online courses and information about machine learning algorithms. I personally would recommend starting with Andrew Ng's course on Coursera.
Zasoby
- Andrew Ng's course on Coursera
- Kaggle datasets
- A deep learning reading list
- A list of free books on machine learning algorithms, data mining, deep learning, and related topics
- Wprowadzenie do teorii uczenia maszynowego i jej zastosowań: wizualny samouczek z przykładami
