Explorarea algoritmilor de învățare automată supravegheați
Publicat: 2022-03-11Scopul principal al acestei lecturi este de a înțelege suficientă metodologie statistică pentru a putea folosi algoritmii de învățare automată din biblioteca scikit-learn a lui Python și apoi aplica aceste cunoștințe pentru a rezolva o problemă clasică de învățare automată.
Prima oprire a călătoriei noastre ne va duce printr-o scurtă istorie a învățării automate. Apoi ne vom scufunda în diferiți algoritmi. La ultima noastră oprire, vom folosi ceea ce am învățat pentru a rezolva problema de predicție a ratei de supraviețuire Titanic.
Câteva declinări de răspundere:
- Sunt un inginer de software full-stack, nu un expert în algoritmi de învățare automată.
- Presupun că știi niște Python de bază.
- Acesta este exploratoriu, așa că nu fiecare detaliu este explicat așa cum ar fi într-un tutorial.
Cu acest lucru notat, haideți să ne scufundăm!
O introducere rapidă în algoritmii de învățare automată
De îndată ce te aventurezi în acest domeniu, îți dai seama că învățarea automată este mai puțin romantică decât ai putea crede. Inițial, eram plin de speranțe că, după ce voi afla mai multe, voi putea să-mi construiesc propriul Jarvis AI, care să-și petreacă toată ziua programând software-ul și să câștige bani pentru mine, astfel încât să pot petrece zile întregi în aer liber citind cărți, conducând o motocicletă, și bucurându-mă de un stil de viață nesăbuit, în timp ce Jarvis-ul meu personal îmi face buzunarele mai adânci. Cu toate acestea, mi-am dat seama curând că baza algoritmilor de învățare automată este statistica, pe care personal o găsesc plictisitoare și neinteresantă. Din fericire, s-a dovedit că statisticile „plictisitoare” au niște aplicații foarte fascinante.
Veți descoperi în curând că pentru a ajunge la acele aplicații fascinante, trebuie să înțelegeți foarte bine statisticile. Unul dintre obiectivele algoritmilor de învățare automată este de a găsi dependențe statistice în datele furnizate.
Datele furnizate ar putea fi orice, de la verificarea tensiunii arteriale în funcție de vârstă până la găsirea de text scris de mână pe baza culorii diferiților pixeli.
Acestea fiind spuse, eram curios să văd dacă aș putea folosi algoritmi de învățare automată pentru a găsi dependențe în funcțiile hash criptografice (SHA, MD5, etc.) - cu toate acestea, nu poți face asta cu adevărat, deoarece primitivele cripto corespunzătoare sunt construite în așa fel. că elimină dependențele și produc rezultate semnificativ greu de anticipat. Cred că, având în vedere o perioadă infinită de timp, algoritmii de învățare automată ar putea sparge orice model criptografic.
Din păcate, nu avem atât de mult timp, așa că trebuie să găsim o altă modalitate de a extrage eficient criptomoneda. Cât de departe am ajuns până acum?
O scurtă istorie a algoritmilor de învățare automată
Rădăcinile algoritmilor de învățare automată provin de la Thomas Bayes, care a fost statistician englez care a trăit în secolul al XVIII-lea. Lucrarea sa An Essay Towards Solving a Problem in the Doctrine of Chances stă la baza Teorema lui Bayes, care este aplicată pe scară largă în domeniul statisticii.
În secolul al XIX-lea, Pierre-Simon Laplace a publicat Theorie analytique des probabilites , extinzându-se asupra lucrării lui Bayes și definind ceea ce știm astăzi ca Teorema lui Bayes. Cu puțin timp înainte, Adrien-Marie Legendre descrisese metoda „cel mai mici pătrate”, folosită și astăzi pe scară largă în învățarea supervizată.
Secolul al XX-lea este perioada în care au fost făcute majoritatea descoperirilor cunoscute public în acest domeniu. Andrey Markov a inventat lanțurile Markov, pe care le-a folosit pentru a analiza poezii. Alan Turing a propus o mașină de învățare care ar putea deveni inteligentă artificial, prefigurand practic algoritmi genetici. Frank Rosenblatt a inventat Perceptronul , stârnind entuziasm uriaș și o acoperire excelentă în mass-media.
Dar apoi anii 1970 au văzut mult pesimism în jurul ideii de IA – și, prin urmare, finanțare redusă – așa că această perioadă se numește iarnă AI . Redescoperirea retropropagarii în anii 1980 a provocat o renaștere în cercetarea învățării automate. Și astăzi, este încă o dată un subiect fierbinte.
Regretatul Leo Breiman a făcut distincția între două paradigme de modelare statistică: modelarea datelor și modelarea algoritmică. „Modelare algoritmică” înseamnă mai mult sau mai puțin algoritmii de învățare automată, cum ar fi pădurea aleatoare .
Învățarea automată și statistica sunt domenii strâns legate. Potrivit lui Michael I. Jordan, ideile de învățare automată, de la principii metodologice la instrumente teoretice, au avut o lungă preistorie în statistică. El a sugerat, de asemenea, știința datelor ca termen substituent pentru problema generală la care lucrează implicit amândoi specialiștii în învățarea automată și statisticienii.
Categorii de algoritmi de învățare automată
Domeniul învățării automate se bazează pe doi piloni principali numiți învățare supravegheată și învățare nesupravegheată . Unii oameni consideră, de asemenea, că un nou domeniu de studiu – învățarea profundă – este separat de problema învățării supravegheate față de învățarea nesupravegheată.
Învățarea supravegheată este atunci când unui computer i se prezintă exemple de intrări și ieșirile dorite ale acestora. Scopul computerului este de a învăța o formulă generală care mapează intrările la ieșiri. Aceasta poate fi împărțită în continuare în:
- Învățare semi-supravegheată , care este atunci când computerului i se oferă un set de antrenament incomplet cu unele rezultate lipsă
- Învățare activă , care este atunci când computerul poate obține etichete de antrenament doar pentru un set foarte limitat de instanțe. Când sunt utilizate în mod interactiv, seturile lor de antrenament pot fi prezentate utilizatorului pentru etichetare.
- Învățare prin întărire , care este atunci când datele de antrenament sunt date doar ca feedback la acțiunile programului în mediul dinamic, cum ar fi conducerea unui vehicul sau jocul împotriva unui adversar
În schimb, învățarea nesupravegheată este atunci când nu sunt date deloc etichete și depinde de algoritm să găsească structura în intrarea sa. Învățarea nesupravegheată poate fi un obiectiv în sine atunci când avem nevoie doar să descoperim tipare ascunse.
Învățarea profundă este un nou domeniu de studiu care este inspirat de structura și funcția creierului uman și se bazează mai degrabă pe rețele neuronale artificiale decât pe concepte statistice. Învățarea profundă poate fi utilizată atât în abordările supravegheate, cât și în cele nesupravegheate.
În acest articol, vom parcurge doar câțiva dintre algoritmii de învățare automată supravegheați mai simpli și îi vom folosi pentru a calcula șansele de supraviețuire ale unui individ în scufundarea tragică a Titanicului. Dar, în general, dacă nu sunteți sigur ce algoritm să utilizați, un loc frumos pentru a începe este fișa de cheat al algoritmului de învățare automată a scikit-learn.
Modele de bază de învățare automată supravegheată
Poate cel mai simplu algoritm posibil este regresia liniară. Uneori, aceasta poate fi reprezentată grafic ca o linie dreaptă, dar în ciuda numelui său, dacă există o ipoteză polinomială, această linie ar putea fi în schimb o curbă. În orice caz, modelează relațiile dintre variabila dependentă scalară $y$ și una sau mai multe valori explicative notate cu $x$.
În termeni profani, aceasta înseamnă că regresia liniară este algoritmul care învață dependența dintre fiecare $x$ și $y$ cunoscut, astfel încât mai târziu să îl putem folosi pentru a prezice $y$ pentru un eșantion necunoscut de $x$.
În primul nostru exemplu de învățare supravegheată, vom folosi un model de regresie liniară de bază pentru a prezice tensiunea arterială a unei persoane, având în vedere vârsta acesteia. Acesta este un set de date foarte simplu, cu două caracteristici semnificative: vârsta și tensiunea arterială.
După cum am menționat deja mai sus, majoritatea algoritmilor de învățare automată funcționează prin găsirea unei dependențe statistice în datele furnizate acestora. Această dependență se numește ipoteză și este de obicei notată cu $h(\theta)$.
Pentru a înțelege ipoteza, să începem prin a încărca și a explora datele.
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>]
În graficul de mai sus, fiecare punct albastru reprezintă eșantionul nostru de date, iar linia albastră este ipoteza pe care algoritmul nostru trebuie să o învețe. Deci, care este, de fapt, această ipoteză?
Pentru a rezolva această problemă, trebuie să învățăm dependența dintre $x$ și $y$, care se notează cu $y = f(x)$. Prin urmare, $f(x)$ este funcția țintă ideală. Algoritmul de învățare automată va încerca să ghicească funcția de ipoteză $h(x)$ care este cea mai apropiată aproximare a necunoscutului $f(x)$.
Cea mai simplă formă posibilă de ipoteză pentru problema regresiei liniare arată astfel: $h_\theta(x) = \theta_0 + \theta_1 * x$. Avem o singură variabilă scalară de intrare $x$ care scoate o singură variabilă scalară $y$, unde $\theta_0$ și $\theta_1$ sunt parametrii pe care trebuie să-i învățăm. Procesul de potrivire a acestei linii albastre în date se numește regresie liniară. Este important să înțelegem că avem un singur parametru de intrare $x_1$; cu toate acestea, o mulțime de funcții de ipoteză vor include și unitatea de părtinire ($x_0$). Deci ipoteza noastră rezultată are o formă de $h_\theta(x) = \theta_0 * x_0 + \theta_1 * x_1$. Dar putem evita să scriem $x_0$ deoarece este aproape întotdeauna egal cu 1.
Revenind la linia albastră. Ipoteza noastră arată ca $h(x) = 84 + 1,24x$, ceea ce înseamnă că $\theta_0 = 84$ și $\theta_1 = 1,24$. Cum putem obține automat acele valori $\theta$?
Trebuie să definim o funcție de cost . În esență, funcția de cost calculează pur și simplu eroarea pătratică medie dintre predicția modelului și rezultatul real.
\[J(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\\ ]De exemplu, ipoteza noastră prezice că pentru cineva care are 48 de ani, tensiunea arterială ar trebui să fie $h(48) = 84 + 1,24 * 48 = 143mmHg$; totuși, în eșantionul nostru de antrenament, avem valoarea de 130 mmHg$. Prin urmare, eroarea este $(143 - 130)^2 = 169$. Acum trebuie să calculăm această eroare pentru fiecare intrare din setul nostru de date de antrenament, apoi să o însumăm ($\sum_{i=1}^m(h_\theta(x^{(i))}) - y^{(i )})^2$) și scoateți valoarea medie din asta.
Acest lucru ne oferă un singur număr scalar care reprezintă costul funcției. Scopul nostru este să găsim valori $\theta$ astfel încât funcția de cost să fie cea mai mică; cu alte cuvinte, dorim să minimizăm funcția de cost. Acest lucru va părea intuitiv: dacă avem o valoare mică a funcției de cost, aceasta înseamnă că eroarea de predicție este și ea mică.
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
Acum, trebuie să găsim astfel de valori de $\theta$ astfel încât valoarea funcției noastre de cost să fie minimă. Dar cum facem asta?
\[minJ(\theta) = \frac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)})^2\\ ]Există mai mulți algoritmi posibili, dar cel mai popular este coborârea în gradient . Pentru a înțelege intuiția din spatele metodei de coborâre a gradientului, să o reprezentăm mai întâi pe grafic. De dragul simplității, vom presupune o ipoteză mai simplă $h(\theta) = \theta_1 * x$. În continuare, vom reprezenta o diagramă 2D simplă în care $x$ este valoarea lui $\theta$ și $y$ este funcția de cost în acest moment.
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()
Funcția de cost este convexă, ceea ce înseamnă că pe intervalul $[a, b]$ există un singur minim. Ceea ce înseamnă din nou că cei mai buni parametri $\theta$ sunt în punctul în care funcția de cost este minimă.
Practic, gradient descent este un algoritm care încearcă să găsească setul de parametri care minimizează funcția. Începe cu un set inițial de parametri și face iterativ pași în direcția negativă a gradientului funcției.
Dacă calculăm derivata unei funcții de ipoteză într-un anumit punct, aceasta ne va oferi o pantă a dreptei tangente la curba în acel punct. Aceasta înseamnă că putem calcula panta în fiecare punct al graficului.
Modul în care funcționează algoritmul este următorul:
- Alegem un punct de plecare aleatoriu (aleatoriu $\theta$).
- Calculați derivata funcției de cost în acest moment.
- Faceți pasul mic spre panta $\theta_j := \theta_j - \lambda * \frac{\partial}{\partial \theta_j} * J(\theta)$.
- Repetați pașii 2-3 până când convergem.
Acum, condiția de convergență depinde de implementarea algoritmului. Ne putem opri după 50 de pași, după un anumit prag sau orice altceva.
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
Nu vom implementa acești algoritmi în acest articol. În schimb, vom folosi scikit-learn
, o bibliotecă open-source Python de învățare automată. Oferă o mulțime de API-uri foarte utile pentru diferite probleme de data mining și de învățare automată.
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]]
Tipuri de date statistice
Când lucrați cu date pentru probleme de învățare automată, este important să recunoașteți diferitele tipuri de date. Putem avea date numerice (continue sau discrete), categoriale sau ordinale.
Datele numerice au sens ca măsurătoare. De exemplu, vârsta, greutatea, numărul de bitcoini pe care o persoană îi deține sau câte articole poate scrie persoana pe lună. Datele numerice pot fi împărțite în continuare în tipuri discrete și continue.
- Datele discrete reprezintă date care pot fi numărate cu numere întregi, de exemplu, numărul de camere dintr-un apartament sau numărul de aruncări de monede.
- Datele continue nu pot fi neapărat reprezentate cu numere întregi. De exemplu, dacă măsurați distanța pe care o puteți sări, aceasta poate fi de 2 metri, sau 1,5 metri sau 1,652245 metri.
Datele categorice reprezintă valori precum sexul persoanei, starea civilă, țara etc. Aceste date pot lua valoare numerică, dar acele numere nu au semnificație matematică. Nu le puteți adăuga împreună.
Datele ordinale pot fi o combinație a celorlalte două tipuri, prin aceea că categoriile pot fi numerotate într-un mod semnificativ din punct de vedere matematic. Un exemplu comun sunt evaluările: de multe ori ni se cere să evaluăm lucrurile pe o scară de la unu la zece și sunt permise numai numere întregi. Deși putem folosi acest lucru numeric - de exemplu, pentru a găsi o evaluare medie pentru ceva -, adesea tratăm datele ca și cum ar fi categorice atunci când vine vorba de aplicarea metodelor de învățare automată.
Regresie logistică
Regresia liniară este un algoritm extraordinar care ne ajută să prezicem valori numerice, de exemplu, prețul casei cu dimensiunea și numărul de camere specifice. Cu toate acestea, uneori, este posibil să dorim și să prezicem date categorice, pentru a obține răspunsuri la întrebări precum:
- Acesta este un câine sau o pisică?
- Această tumoare este malignă sau benignă?
- Acest vin este bun sau rău?
- Acest e-mail este sau nu spam?
Sau chiar:
- Ce număr este în imagine?
- Cărei categorii aparține acest e-mail?
Toate aceste întrebări sunt specifice problemei de clasificare . Iar cel mai simplu algoritm de clasificare se numește regresie logistică , care este în cele din urmă același cu regresia liniară , cu excepția faptului că are o ipoteză diferită.
În primul rând, putem reutiliza aceeași ipoteză liniară $h_\theta(x) = \theta^TX$ (aceasta este în formă vectorizată). În timp ce regresia liniară poate scoate orice număr din intervalul $[a, b]$, regresia logistică poate scoate numai valori în $[−1, 1]$, care este probabilitatea ca obiectul să se încadreze sau nu într-o anumită categorie.
Folosind o funcție sigmoidă , putem converti orice valoare numerică pentru a reprezenta o valoare pe intervalul $[−1, 1]$.
\[f(x) = \frac{1}{1 + e^x}\]Acum, în loc de $x$, trebuie să trecem o ipoteză existentă și, prin urmare, vom obține:
\[f(x) = \frac{1}{1 + e^{\theta_0 + \theta_1 * x_1 + ... + \theta_n * x_n}}\]După aceea, putem aplica un prag simplu spunând că dacă ipoteza este mai mare decât zero, aceasta este o valoare adevărată, altfel falsă.
\[h_\theta(x) = \begin{cases} 1 & \mbox{if } \theta^TX > 0 \\ 0 & \mbox{else} \end{cases}\]Aceasta înseamnă că putem folosi aceeași funcție de cost și același algoritm de coborâre a gradientului pentru a învăța o ipoteză pentru regresia logistică.
În următorul nostru exemplu de algoritm de învățare automată, îi vom sfătui pe piloții navetei spațiale dacă ar trebui sau nu să folosească controlul automat sau manual de aterizare. Avem un set de date foarte mic - 15 mostre - care constă din șase caracteristici și adevărul de bază .
În algoritmii de învățare automată, termenul „ adevăr de bază ” se referă la acuratețea clasificării setului de antrenament pentru tehnicile de învățare supravegheată.
Setul nostru de date este complet, ceea ce înseamnă că nu lipsesc caracteristici; cu toate acestea, unele dintre caracteristici au un „*” în loc de categorie, ceea ce înseamnă că această caracteristică nu contează. Vom înlocui toate asteriscurile cu zerouri.
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%
Validare?
În exemplul anterior, am validat performanța modelului nostru folosind datele de învățare. Cu toate acestea, este aceasta acum o opțiune bună, având în vedere că algoritmul nostru poate fi subadaptat sau supraadaptat datele? Să aruncăm o privire la exemplul mai simplu când avem o caracteristică care reprezintă dimensiunea unei case și alta care reprezintă prețul acesteia.
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()

Modelul algoritmului de învățare automată este insuficient dacă nu poate generaliza nici datele de antrenament, nici observațiile noi. În exemplul de mai sus, folosim o ipoteză liniară simplă care nu reprezintă cu adevărat setul de date de antrenament real și va avea performanțe foarte slabe. De obicei, subadaptarea nu este discutată, deoarece poate fi detectată cu ușurință având în vedere o măsură bună.
Dacă algoritmul nostru își amintește fiecare observație care a fost arătată, atunci va avea performanțe slabe la noile observații în afara setului de date de antrenament. Aceasta se numește supraajustare . De exemplu, un model polinom de gradul 30 trece prin cele mai multe puncte și are un scor foarte bun pe setul de antrenament, dar orice în afara acestuia ar funcționa prost.
Setul nostru de date constă dintr-o singură caracteristică și este ușor de reprezentat în spațiu 2D; cu toate acestea, în viața reală, este posibil să avem seturi de date cu sute de caracteristici, ceea ce le face imposibil de reprezentat vizual în spațiul euclidian. Ce alte opțiuni avem pentru a vedea dacă modelul este sub-adaptat sau supra-adaptat?
Este timpul să vă prezentăm conceptul de curbă de învățare . Acesta este un grafic simplu care prezintă eroarea pătratică medie asupra numărului de eșantioane de antrenament.
În materialele de învățare veți vedea de obicei grafice similare cu acestea:
Cu toate acestea, în viața reală, este posibil să nu obțineți o imagine atât de perfectă. Să trasăm curba de învățare pentru fiecare dintre modelele noastre.
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()
În scenariul nostru simulat, linia albastră, care reprezintă scorul de antrenament, pare o linie dreaptă. În realitate, încă scade ușor - puteți vedea acest lucru în graficul polinom de gradul întâi, dar în celelalte este prea subtil pentru a spune la această rezoluție. Cel puțin vedem clar că există un decalaj uriaș între curbele de învățare pentru antrenament și observațiile de testare cu un scenariu „prevăzut ridicat”.
Pe graficul „normal” al ratei de învățare din mijloc, puteți vedea cum se îmbină scorul de antrenament și liniile de scor la test.
Și pe graficul „varianță mare”, puteți vedea că, cu un număr redus de eșantioane, scorurile la test și la antrenament sunt foarte asemănătoare; cu toate acestea, atunci când creșteți numărul de mostre, scorul de antrenament rămâne aproape perfect, în timp ce scorul de test crește departe de acesta.
Putem repara modele de underfitting (numite și modele cu părtinire mare ) dacă folosim o ipoteză neliniară, de exemplu, ipoteza cu mai multe caracteristici polinomiale.
Modelul nostru de supraadaptare ( varianță mare ) trece prin fiecare exemplu prezentat; cu toate acestea, atunci când introducem date de testare, decalajul dintre curbele de învățare se mărește. Putem folosi regularizarea, validarea încrucișată și mai multe eșantioane de date pentru a remedia modelele de supraadaptare.
Validare încrucișată
Una dintre practicile obișnuite pentru a evita supraadaptarea este de a păstra o parte din datele disponibile și de a le folosi ca set de testare. Cu toate acestea, atunci când evaluăm diferite setări de model, cum ar fi numărul de caracteristici polinomiale, suntem încă expuși riscului de a supraadapta setul de testare, deoarece parametrii pot fi ajustați pentru a obține performanța optimă a estimatorului și, din această cauză, cunoștințele noastre despre setul de testare pot fi modificate. scurgerea în model. Pentru a rezolva această problemă, trebuie să păstrăm încă o parte a setului de date, care se numește „setul de validare”. Antrenamentul se desfășoară pe setul de antrenament și, atunci când credem că am atins performanța optimă a modelului, putem face o evaluare finală utilizând setul de validare.
Cu toate acestea, prin împărțirea datelor disponibile în trei seturi, reducem dramatic numărul de eșantioane care pot fi utilizate pentru antrenamentul modelelor, iar rezultatele pot depinde de o anumită alegere aleatorie pentru perechea de antrenament-validare de seturi.
O soluție la această problemă este o procedură numită validare încrucișată. În validarea încrucișată standard $k$-fold, împărțim datele în $k$ subseturi, numite pliuri. Apoi, antrenăm algoritmul în mod iterativ pe $k-1$ pliuri în timp ce folosim pliul rămas ca set de testare (numit „fold holdout”).
Validarea încrucișată vă permite să reglați parametrii doar cu setul original de antrenament. Acest lucru vă permite să păstrați setul de testare ca un set de date cu adevărat nevăzut pentru selectarea modelului final.
Există mult mai multe tehnici de validare încrucișată, cum ar fi lăsați P afară , stratificat $k$-fold , amestecare și împărțire etc., dar acestea depășesc domeniul de aplicare al acestui articol.
Regularizare
Aceasta este o altă tehnică care poate ajuta la rezolvarea problemei supraajustării modelului. Cele mai multe dintre seturile de date au un model și ceva zgomot. Scopul regularizării este reducerea influenței zgomotului asupra modelului.
Există trei tehnici principale de regularizare: Lasso, Tikhonov și plasă elastică.
Regularizarea L1 (sau regularizarea Lasso ) va selecta unele caracteristici pentru a se micșora la zero, astfel încât acestea să nu joace niciun rol în modelul final. L1 poate fi văzut ca o metodă de selectare a caracteristicilor importante.
Regularizarea L2 (sau regularizarea Tikhonov ) va forța toate caracteristicile să fie relativ mici, astfel încât acestea să ofere o influență mai mică asupra modelului.
Plasa elastica este combinatia dintre L1 si L2.
Normalizare (Scalarea caracteristicilor)
Scalarea caracteristicilor este, de asemenea, un pas important în timpul preprocesării datelor. Setul nostru de date poate avea caracteristici cu valorile $[-\infty, \infty]$ și alte caracteristici cu o scară diferită. Aceasta este o metodă de standardizare a intervalelor de valori independente.
Scalarea caracteristicilor este, de asemenea, un proces important pentru îmbunătățirea performanței modelelor de învățare. În primul rând, coborârea gradientului va converge mult mai repede dacă toate caracteristicile sunt scalate la aceeași normă. De asemenea, o mulțime de algoritmi — de exemplu, mașinile vectoriale de suport (SVM) — funcționează prin calcularea distanței dintre două puncte și dacă una dintre caracteristici are valori largi, atunci distanța va fi foarte influențată de această caracteristică.
Suport mașini vectoriale
SVM este încă un alt algoritm de învățare automată foarte popular, care poate fi utilizat pentru probleme de clasificare și regresie. În SVM, reprezentăm fiecare observație ca un punct în spațiul dimensional $n$ unde $n$ este numărul de caracteristici pe care le avem. Valoarea fiecărei caracteristici este valoarea anumitor coordonate. Apoi, încercăm să găsim un hiperplan care să separe două clase suficient de bine.
După ce identificăm cel mai bun hiperplan, vrem să adăugăm margini, care ar separa mai mult cele două clase.
SVM este foarte eficient acolo unde numărul de caracteristici este foarte mare sau dacă numărul de caracteristici este mai mare decât numărul de mostre de date. Cu toate acestea, deoarece SVM funcționează pe o bază vectorială, este esențial să normalizați datele înainte de utilizare.
Rețele neuronale
Algoritmii rețelelor neuronale sunt probabil cel mai interesant domeniu al studiilor de învățare automată. Rețelele neuronale încearcă să imite modul în care neuronii creierului sunt conectați între ei.
Așa arată o rețea neuronală. Combinăm o mulțime de noduri împreună, unde fiecare nod preia un set de intrări, aplicăm câteva calcule asupra lor și scoatem o valoare.
Există o mare varietate de algoritmi de rețele neuronale atât pentru învățarea supravegheată, cât și pentru învățarea nesupravegheată. Rețelele neuronale pot fi folosite pentru a conduce mașini autonome, a juca jocuri, a ateriza avioane, a clasifica imagini și multe altele.
Infamul Titanic
RMS Titanic a fost o linie britanică de pasageri care s-a scufundat în Oceanul Atlantic de Nord pe 15 aprilie 1912, după ce s-a ciocnit cu un aisberg. Au fost aproximativ 2.224 de membri ai echipajului și pasagerii și mai mult de 1.500 au murit, făcându-l unul dintre cele mai mortale dezastre maritime comerciale din toate timpurile.
Acum, deoarece înțelegem intuiția din spatele celor mai de bază algoritmi de învățare automată utilizați pentru problemele de clasificare, ne putem aplica cunoștințele pentru a prezice rezultatul supraviețuirii pentru cei de la bordul Titanicului.
Setul nostru de date va fi împrumutat de la platforma Kaggle de competiții de știință a datelor.
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)
ID pasager | A supraviețuit | Pclass | Nume | Sex | Vârstă | SibSp | Usca | Bilet | Tarif | Cabină | îmbarcat | |
0 | 1 | 0 | 3 | Braund, domnule Owen Harris | masculin | 22.0 | 1 | 0 | A/5 21171 | 7,2500 | NaN | S |
1 | 2 | 1 | 1 | Cumings, doamna John Bradley (Florence Briggs Th... | Femeie | 38,0 | 1 | 0 | PC 17599 | 71,2833 | C85 | C |
2 | 3 | 1 | 3 | Heikkinen, domnișoară Laina | Femeie | 26.0 | 0 | 0 | STON/O2. 3101282 | 7,9250 | NaN | S |
3 | 4 | 1 | 1 | Futrelle, doamna Jacques Heath (Lily May Peel) | Femeie | 35,0 | 1 | 0 | 113803 | 53,1000 | C123 | S |
4 | 5 | 0 | 3 | Allen, domnule William Henry | masculin | 35,0 | 0 | 0 | 373450 | 8,0500 | NaN | S |
Primul nostru pas ar fi să încărcăm și să explorăm datele. Avem 891 de înregistrări de testare; fiecare înregistrare are următoarea structură:
- passengerId – ID-ul pasagerului de la bord
- supraviețuire – Indiferent dacă persoana a supraviețuit sau nu accidentului
- pclass – Clasa de bilete, de exemplu, 1st, 2nd, 3rd
- gen – Genul pasagerului: bărbat sau femeie
- nume – Titlul inclus
- vârsta – Vârsta în ani
- sibsp – Numărul de frați/soți la bordul Titanicului
- parch – Numărul de părinți/copii la bordul Titanicului
- bilet – numărul biletului
- tarif – Tarif pentru pasageri
- cabină – numărul de cabină
- imbarcat – Port de imbarcare
Acest set de date conține atât date numerice, cât și date categoriale. De obicei, este o idee bună să vă scufundați mai adânc în date și, pe baza acestora, să veniți cu ipoteze. Cu toate acestea, în acest caz, vom sări peste acest pas și vom trece direct la predicții.
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 | masculin |
Capt | 0 | 1 |
Col | 0 | 2 |
Countess | 1 | 0 |
Don | 0 | 1 |
Dr | 1 | 6 |
Jonkheer | 0 | 1 |
Lady | 1 | 0 |
Major | 0 | 2 |
Maestru | 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()
Title | Survived | |
0 | Maestru | 0.575000 |
1 | Miss | 0.702703 |
2 | Mr | 0.156673 |
3 | Mrs | 0.793651 |
4 | Alte | 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 | Vârstă | SibSp | Parch | Fare | Embarked | Title | |
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()
Nume | 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.
Resurse
- 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
- O introducere în teoria învățării automate și aplicațiile sale: un tutorial vizual cu exemple