From “Dancing in the dark” To “It’s a kind of magic

“ML” sta per Machine Learning che è una branca del’Intelligenza Artificiale.
“For dummies” è una locuzione gergale spesso utilizzata in ambito informatico per indicare che quel materiale informativo/divulgativo è rivolto ad i principianti, quindi parliamo di Intelligenza Artificiale per principianti.
“15 min” è il tempo medio di lettura ignorando le porzioni di codice pyton (riquadri grigi).

Spesso si accosta all’Intelligenza Artificiale il concetto di macchine “pensanti”, non è esattamente così, tuttavia il Machine Learning consente alle macchine di risolvere i problemi imparando dall’esperienza, che è esattamente quello che fa il nostro cervello quindi, se la cosa vi “stuzzica” e volete capirci qualcosa di più vi invito a continuare la lettura.

Cosa dovete sapere per capirci qualcosa

“Assolutamente nulla” è più che sufficiente un po’ di “curiosità”, ma se siete esperti tanto meglio, essendo questo documento rivolto ai “dummies” ma redatto da un poco più di un dummy, sarò lieto di ascoltare qualsiasi tipo di suggerimento.

Ah dimenticavo il sottotitolo. Vi chiederete cosa c’entrano i titoli di due canzoni con il Machine Learning… “forse nulla”, ma se siete curiosi e non vi frega niente del Machine Learning, magari vi “stuzzica” cercare di capire dove voglio andare a parare.

Cosa vedremo

Analizzeremo l’approccio che dovrebbe essere seguito per la realizzazione di un modello di apprendimento automatico, utilizzato nell’ambito dello sviluppo di un ipotetico software che consente di prevedere se una massa rilevata durante un accertamento mammografico è benigna o maligna.

La traccia che seguiremo si svilupperà nei seguenti punti:

  • Reperimento del dataset
  • Preparazione dei dati
  • Training e test dei seguenti modelli
    • Decision tree
    • Random forest
    • KNN
    • Naive Bayes
    • SVM
    • Logistic Regression
    • Neural network

Reperimento del dataset

Cosa è un dataset

Il dataset è una sorgente dati in formato tabellare – esempio excel – che utilizzeremo per il training e per il test dei modelli.

Per training intendiamo la fase in cui istruiamo il modello, per test la fase in cui lo valutiamo.
Il modello invece è quella entità che una volta istruita fa le previsioni.

Durante la trattazione quando vengono citati i classifiers, fate conto che sono la stessa cosa del modello.

Le colonne (attributi) di un dataset sono suddivise logicamente per:

  • features – le variabili che condizionano il risultato, generalmente la notazione utilizzata per rappresentarle è: X;
  • labels – il risultato in funzione degli attributi, in questa circostanza la nomenclatura prevede l’utilizzo della lettera: y.

Per chi si ricorda un po’ di algebra gli verrà in mente come si definisce una funzione: y=f(x) ed in effetti parliamo sostanzialmente di questa cosa, il ML ci consente di determinare f (il modello), ovvero quella funzione che è in grado di rappresentare y in funzione di X.

Riepiloghiamo

Training data – Sono le righe del dataset che utilizzeremo esclusivamente per il training: al modello gli spieghiamo per ogni riga che a fronte di quelle features (caratteristiche della massa) sarà associata quella label (massa maligna o benigna).

Test data – Sono le righe del dataset che il modello non conosce e vengono utilizzate esclusivamente per il test: al modello gli chiediamo per ogni riga quale è la label (se la massa è maligna o benigna) a fronte di quelle features (caratteristiche della massa), la percentuale di risposte corrette viene detta “accuratezza”.

Un esempio visuale

Immaginate il training dataset come dei puntini in uno spazio bidimensionale, in cui sull’asse delle ordinate (y) abbiamo la label e su quello delle ascisse (x) la feature, il modello è quella linea che mette assieme questi puntini. Se aggiungiamo altri puntini – il dataset di test – e questi si trovano in prossimità della nostra linea siamo stati bravi ed il nostro modello sarà in grado di fare previsioni in quanto abbiamo trovato una funzione che collega tutti i nostri puntini.

Dove si trova il dataset

Il dataset si può scaricare dal seguente indirizzo: Mammographic Mass

Ogni riga di questo dataset classifica una particolare massa, quindi per chi “ragiona ad oggetti” una riga è una istanza rappresentata dai seguenti attributi (chi ha competenze mediche perdoni la traduzione letterale dei possibili valori di alcuni attributi):

  • BI-RADS assessment – Indice di gravità da 1 to 5
  • Age – Età del paziente in anni
  • Shape – circolare=1 ovale=2 lobulare=3 irregolare=4
  • Margin – circoscritto=1 microlobulato=2 oscurato=3 non-definito=4 spicolato=5
  • Density – elevata=1 iso=2 bassa=3 contente-grasso=4
  • Severity – benigna=0 or maligna=1

BI-RADS lo scartiamo in quanto non è attributo predittivo (dovete fidarvi…).

Gli attributi “age”, “shape”, “margin” e “density” sono le features mentre “severity” è quello che vogliamo prevedere, quindi la label.

Come potete notare il valore degli attributi è espresso in numeri, questo è un prerequisito in quanto il machine learning utilizza modelli di tipo matematico, quindi i dati devono essere necessariamente espressi in formato numerico.

Preparazione dei dati

Download del dataset

import pandas as pd
#Indichiamo le colonne che vogliamo importare 
col_names = ["bi-rads", "age", "shape", "margin", "density", "severity"]
#Url del dataset
dataset_url = "https://archive.ics.uci.edu/ml/machine-learning-databases/mammographic-masses/mammographic_masses.data"
#Il parametro na_values serve per dire a pandas che se l'attributo contiene il valore ?
#deve essere considerato come non impostato (null value)
df = pd.read_csv(dataset_url, na_values = ['?'], names=col_names)

Analisi e pulizia dei dati

Intanto vediamo cosa c’è dentro:

df.head()
bi-rads age shape margin density severity
0 5.0 67.0 3.0 5.0 3.0 1
1 4.0 43.0 1.0 1.0 NaN 1
2 5.0 58.0 4.0 5.0 3.0 1
3 4.0 28.0 1.0 1.0 3.0 0
4 5.0 74.0 1.0 5.0 NaN 1

Possiamo notare ad esempio che in queste prime cinque righe ce ne sono due con la “density” non impostata (NaN).

Ora vediamo di fare una analisi statistica del dataset:

df.describe()
bi-rads age shape margin density severity
count 959.000000 956.000000 930.000000 913.000000 885.000000 961.000000
mean 4.348279 55.487448 2.721505 2.796276 2.910734 0.463059
std 1.783031 14.480131 1.242792 1.566546 0.380444 0.498893
min 0.000000 18.000000 1.000000 1.000000 1.000000 0.000000
25% 4.000000 45.000000 2.000000 1.000000 3.000000 0.000000
50% 4.000000 57.000000 3.000000 3.000000 3.000000 0.000000
75% 5.000000 66.000000 4.000000 4.000000 3.000000 1.000000
max 55.000000 96.000000 4.000000 5.000000 4.000000 1.000000

La riga “count” mostra in quante righe si trova quell’attributo.
Su 961 righe che hanno l’attributo “severity” impostato ne abbiamo solo 885 che hanno impostato la “density”, 913 il “margin”, ecc…
Questo significa che alcune righe sono prive di attributi, estraiamole (non perdete troppo tempo ad analizzare il tabulato)

df.loc[(df['age'].isnull()) |
              (df['shape'].isnull()) |
              (df['margin'].isnull()) |
              (df['density'].isnull())]
bi-rads age shape margin density severity
1 4.0 43.0 1.0 1.0 NaN 1
4 5.0 74.0 1.0 5.0 NaN 1
5 4.0 65.0 1.0 NaN 3.0 0
6 4.0 70.0 NaN NaN 3.0 0
7 5.0 42.0 1.0 NaN 3.0 0
9 5.0 60.0 NaN 5.0 1.0 1
12 4.0 64.0 1.0 NaN 3.0 0
19 4.0 40.0 1.0 NaN NaN 0
20 NaN 66.0 NaN NaN 1.0 1
22 4.0 43.0 1.0 NaN NaN 0
26 2.0 66.0 1.0 1.0 NaN 0
27 5.0 63.0 3.0 NaN 3.0 0
35 4.0 77.0 3.0 NaN NaN 0
38 4.0 48.0 4.0 5.0 NaN 1
40 4.0 59.0 2.0 1.0 NaN 0
43 4.0 61.0 2.0 1.0 NaN 0
45 5.0 44.0 2.0 4.0 NaN 1
47 4.0 23.0 1.0 1.0 NaN 0
48 2.0 42.0 NaN NaN 4.0 0
52 4.0 23.0 1.0 1.0 NaN 0
53 4.0 63.0 2.0 1.0 NaN 0
54 4.0 53.0 NaN 5.0 3.0 1
55 4.0 43.0 3.0 4.0 NaN 0
57 5.0 51.0 2.0 4.0 NaN 0
58 4.0 45.0 2.0 1.0 NaN 0
59 5.0 59.0 2.0 NaN NaN 1
63 3.0 57.0 2.0 1.0 NaN 0
65 4.0 25.0 2.0 1.0 NaN 0
67 5.0 72.0 4.0 3.0 NaN 1
74 5.0 70.0 NaN 4.0 NaN 1
496 4.0 82.0 NaN 5.0 3.0 1
501 5.0 59.0 4.0 4.0 NaN 1
519 3.0 68.0 NaN NaN 3.0 0
520 4.0 62.0 4.0 NaN 3.0 1
521 5.0 65.0 1.0 NaN 3.0 1
531 4.0 55.0 NaN NaN 3.0 0
537 5.0 63.0 NaN 4.0 3.0 1
541 4.0 49.0 2.0 NaN 3.0 0
554 5.0 70.0 NaN 5.0 3.0 1
561 2.0 59.0 NaN 4.0 3.0 0
569 4.0 64.0 3.0 4.0 NaN 1
574 4.0 60.0 3.0 NaN NaN 0
581 2.0 65.0 NaN 1.0 2.0 0
614 3.0 46.0 NaN 5.0 NaN 1
627 4.0 57.0 2.0 1.0 NaN 0
660 4.0 58.0 NaN 4.0 3.0 1
661 4.0 51.0 NaN 4.0 3.0 0
662 3.0 50.0 NaN NaN 3.0 1
665 4.0 27.0 2.0 1.0 NaN 0
677 4.0 57.0 4.0 4.0 NaN 1
683 5.0 NaN 3.0 3.0 3.0 1
691 4.0 72.0 3.0 NaN 3.0 0
723 4.0 60.0 3.0 NaN 4.0 0
745 6.0 76.0 3.0 NaN 3.0 0
752 5.0 48.0 NaN 4.0 NaN 1
778 4.0 60.0 NaN 4.0 3.0 0
819 4.0 35.0 3.0 NaN 2.0 0
824 6.0 40.0 NaN 3.0 4.0 1
884 5.0 NaN 4.0 4.0 3.0 1
923 5.0 NaN 4.0 3.0 3.0 1

130 rows × 6 columns

Visto che le righe con attributi non impostati sono distribuite in modo casuale possiamo ipotizzare che anche eliminandole non dovremmo inficiare i dati (fidatevi questo sarà più chiaro fra qualche secondo):

#Eliminiamo le righe con campi non impostati
df = df.dropna()
#Estraiamo nuovamente le statistiche
df.describe()
bi-rads age shape margin density severity
count 830.000000 830.000000 830.000000 830.000000 830.000000 830.000000
mean 4.393976 55.781928 2.781928 2.813253 2.915663 0.485542
std 1.888371 14.671782 1.242361 1.567175 0.350936 0.500092
min 0.000000 18.000000 1.000000 1.000000 1.000000 0.000000
25% 4.000000 46.000000 2.000000 1.000000 3.000000 0.000000
50% 4.000000 57.000000 3.000000 3.000000 3.000000 0.000000
75% 5.000000 66.000000 4.000000 4.000000 3.000000 1.000000
max 55.000000 96.000000 4.000000 5.000000 4.000000 1.000000

Le statistiche del nuovo tabulato sono rimaste inalterate, questo significa che non abbiamo compromesso la valenza statistica del dataset.

Ora estraiamo le features e la label, “bi-rads” come anticipato non la includiamo in quanto non influenza la previsione.

La fase di cleaning del dataset deve prevedere, oltre all’eliminazione di righe con attributi non impostati (o quanto meno è un aspetto da valutare), anche l’eliminazione delle colonne (features) che non influenzano la previsione in quanto appesantirebbero inutilmente il training del modello.

#estraggo le features
feature_cols = ["age", "shape", "margin", "density"]

#Matrice delle features (variabili)
X = df[feature_cols]

#Matrice delle labels, in questo caso è la colonna "severity" (colonna dei risultati)
y = df.severity
Per ragioni legate a come “funzionano” i modelli si ottengono risultati migliori quando i numeri del dataset non sono troppo distanti tra loro, quindi normalizziamo il dataset (fidiamoci…):
#importo l'oggetto che fa questo lavoro
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
#Gli do in pasto la matrice delle features
scaler.fit(X)
#Applico la trasformazione
X = scaler.transform(X)
 La normalizzazione delle labels “y” generalmente non serve.

Training e test dei modelli

Definisco una funzione che mi consentirà di confrontare i modelli:
results = []
#Funzione per mostrare il riepilogo dei risultato
def show_results(classifier, scores):
    results.append([classifier, format("%0.2f (+/- %0.2f)" % (scores.mean(), scores.std() * 2))])
    return pd.DataFrame(data=results, columns=['Classifier', 'Accuracy'])
 Per il training utilizzeremo il 75% delle righe del dataset e per il test il rimanente 25%, separiamo le righe per il training con quelle per i test (split):

Split

#importiamo le librerie necessarie per lo split
import numpy
from sklearn.model_selection import train_test_split

#questo serve solo per rendere reiterabili i risultati
numpy.random.seed(1234)
# X_train - features di train (attributi delle istanze di massa che condizionano il risultato)
# X_test - features di test
# y_train - label di train (maligna|benigna)
# y_test - label di train
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=1)

Decision Trees

Il primo modello su cui lavoriamo è DecisionTreeClassifier:

#Importiamo il classificatore
from sklearn.tree import DecisionTreeClassifier
#Lo creiamo
clf = DecisionTreeClassifier(random_state=0)
#Lo istruiamo
clf.fit(X_train, y_train)
DecisionTreeClassifier(class_weight=None, criterion='gini', max_depth=None,
            max_features=None, max_leaf_nodes=None,
            min_impurity_decrease=0.0, min_impurity_split=None,
            min_samples_leaf=1, min_samples_split=2,
            min_weight_fraction_leaf=0.0, presort=False, random_state=0,
            splitter='best')
Ora lo valutiamo.

Utilizzeremo il dataset di test (“X_test” ed “y_test”) per misurare l’accuratezza:

#Pass al metodo score features e labels di test
accuracy = clf.score(X_test, y_test)
#stampo il risultato
print("Accuracy: ",accuracy)
Accuracy:  0.7307692307692307
Vedo che questo modello è in grado di fare previsioni con una precisione di circa il 73%
Tuttavia una tecnica più efficace dl validazione è la cross validation e si procede in questo modo:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(clf, X, y, cv=10)
#stampo risultato
results_df = show_results('DecisionTreeClassifier', scores)
results_df
Classifier Accuracy
0 DecisionTreeClassifier 0.74 (+/- 0.07)

RandomForestClassifier

Ora valutiamo RandomForestClassifier.
La cosa interessante è che si procede esattamente come nel caso del DecisionTreeClassifier, con la differenza ovvia, che a codice indichiamo il nuovo modello:

#Importo il classificatore
from sklearn.ensemble import RandomForestClassifier
#Lo creo
rcf = RandomForestClassifier(n_estimators=100, max_depth=2, random_state=0)
#Lo valuto
scores = cross_val_score(rcf, X, y, cv=10)
#stampo risultato
results_df = show_results('RandomForestClassifier', scores)
results_df

 

Classifier Accuracy
0 DecisionTreeClassifier 0.74 (+/- 0.07)
1 RandomForestClassifier 0.80 (+/- 0.11)
Vedo che RandomForestClassifier è decisamente meglio

SVM

SVM

from sklearn.svm import SVC
svc = SVC(kernel='linear')
scores = cross_val_score(svc, X, y, cv=10)
#stampo risultato
results_df = show_results('SVC', scores)
results_df

 

Classifier Accuracy
0 DecisionTreeClassifier 0.74 (+/- 0.07)
1 RandomForestClassifier 0.80 (+/- 0.11)
2 SVC 0.80 (+/- 0.12)

SVC è analogo al RandomForestClassifier

KNN

KNN

from sklearn.neighbors import KNeighborsClassifier
knc = KNeighborsClassifier(n_neighbors=10)
scores = cross_val_score(knc, X, y, cv=10)
#stampo risultato
results_df = show_results('KNeighborsClassifier', scores)
results_df

 

Classifier Accuracy
0 DecisionTreeClassifier 0.74 (+/- 0.07)
1 RandomForestClassifier 0.80 (+/- 0.11)
2 SVC 0.80 (+/- 0.12)
3 KNeighborsClassifier 0.79 (+/- 0.10)

Tuning 1

Ora vediamo come possiamo provare a modificare gli iperparametri per verificare se riusciamo a migliorarne l’accuratezza, visto che KNN prevede l’impostazione di “n_neighbors” (più tantissimi altri) facciamo fare queste verifiche a pyton:

#Misuro l'accuratezza di knc per il parametro n_neighbors che va da 1 a 20
for k in range(1, 20):
    knc = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knc, X, y, cv=10)
    print("K: %s Accuracy: %0.2f (+/- %0.2f)" % (k, scores.mean(), scores.std() * 2))
K: 1 Accuracy: 0.72 (+/- 0.07)
K: 2 Accuracy: 0.69 (+/- 0.11)
K: 3 Accuracy: 0.75 (+/- 0.11)
K: 4 Accuracy: 0.73 (+/- 0.08)
K: 5 Accuracy: 0.77 (+/- 0.10)
K: 6 Accuracy: 0.76 (+/- 0.10)
K: 7 Accuracy: 0.79 (+/- 0.09)
K: 8 Accuracy: 0.77 (+/- 0.11)
K: 9 Accuracy: 0.79 (+/- 0.10)
K: 10 Accuracy: 0.79 (+/- 0.10)
K: 11 Accuracy: 0.79 (+/- 0.10)
K: 12 Accuracy: 0.78 (+/- 0.09)
K: 13 Accuracy: 0.78 (+/- 0.10)
K: 14 Accuracy: 0.79 (+/- 0.09)
K: 15 Accuracy: 0.79 (+/- 0.10)
K: 16 Accuracy: 0.78 (+/- 0.11)
K: 17 Accuracy: 0.78 (+/- 0.11)
K: 18 Accuracy: 0.78 (+/- 0.11)
K: 19 Accuracy: 0.78 (+/- 0.11)

Mi rendo conto che con “n_neighbors=10” ottengo i risultati migliori ma aumentandone ulteriormente il valore non si ha alcun beneficio. Questo è un aspetto importante in quanto si è portati a pompare il modello con la convinzione di aumentarne la precisione, tuttavia in certe circostanze non serve a nulla.

Naive Bayes

Naive Bayes
Rispetto agli altri modelli questo accetta tra i valori del dataset solo numeri positivi che vanno da 0 ad 1 quindi devo fare una ulteriore normalizzazione:

from sklearn.naive_bayes import MultinomialNB
from sklearn.preprocessing import MinMaxScaler
#Creo il normalizzatore
scaler = MinMaxScaler()
#Gli passo le features
scaler.fit(X)
#Normalizzo
X_nb = scaler.transform(X)
mmnb = MultinomialNB()
scores = cross_val_score(mmnb, X_nb, y, cv=10)
results_df = show_results('MultinomialNB', scores)ff
results_df

 

Classifier Accuracy
0 DecisionTreeClassifier 0.74 (+/- 0.07)
1 RandomForestClassifier 0.80 (+/- 0.11)
2 SVC 0.80 (+/- 0.12)
3 KNeighborsClassifier 0.79 (+/- 0.10)
4 MultinomialNB 0.78 (+/- 0.12)

Tuning 2

In questa circostanza iteriamo per i possibili valori dell’iperparametro “kernel” nel caso di SVC:

#L'iperparametro che voglio modificare è il kernel
#ne proverò tre
kernels = ['linear', 'rbf', 'poly']
for kernel in kernels:
    svc = SVC(kernel=kernel, gamma='scale')
    scores = cross_val_score(svc, X, y, cv=10)
    print("Kernel: %s, Accuracy: %0.2f (+/- %0.2f)" % (kernel, scores.mean(), scores.std() * 2))
Kernel: linear, Accuracy: 0.80 (+/- 0.12)
Kernel: rbf, Accuracy: 0.80 (+/- 0.10)
Kernel: poly, Accuracy: 0.79 (+/- 0.11)

Vedo che non cambia quasi nulla.

 Logistic Regression

Logistic Regression
Ora proviamo con quello che è uno dei modelli più elementari:

from sklearn.linear_model import LogisticRegression
logisticRegression = LogisticRegression(solver="lbfgs")
scores = cross_val_score(logisticRegression, X, y, cv=10)
results_df = show_results('LogisticRegression', scores)
results_df

 

Classifier Accuracy
0 DecisionTreeClassifier 0.74 (+/- 0.07)
1 RandomForestClassifier 0.80 (+/- 0.11)
2 SVC 0.80 (+/- 0.12)
3 KNeighborsClassifier 0.79 (+/- 0.10)
4 MultinomialNB 0.78 (+/- 0.12)
5 LogisticRegression 0.81 (+/- 0.10)

Constatiamo che è un po’ meglio di tutti gli altri. L’insegnamento che dobbiamo trarne è che anche nel Machine Learning: semplice non fa rima con inutile.

Neural Networks

Per finire utilizziamo una rete neurale, qui la cosa si fa leggermente più complessa in quanto la rete neurale ha una sua topologia, livelli, neuroni, ecc…

Imposto alcune variabili di ambiente ed importo le varie librerie:

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.models import Sequential
from tensorflow.keras.wrappers.scikit_learn import KerasClassifier

Creo il modello di rete neurale che sarà costituito da:

  • In input layer di 4 neuroni input_dim=4 (la regola è un neurone per ogni feature);
  • Un layer intermedio di 6 neuroni model.add(Dense(6...), (in realtà maggiore è il numero di neuroni di questo layer migliore dovrebbe essere la capacità della rete neurale di fare previsioni esatte, tuttavia esiste un limite fisiologico oltre il quale non ha senso andare, comunque molto dipende dalla complessità del problema che vogliamo risolvere);
  • Per finire un output layer con 1 neurone model.add(Dense(1...), in questo caso ne utilizziamo 1 in quanto la classificazione è binaria (maligna|beningna).
def create_model():
    model = Sequential()
    #Aggiungo layer di 6 neuroni attivati da un input layer di 4 neuroni
    model.add(Dense(6, input_dim=4, kernel_initializer='normal', activation='relu'))
    #Aggiungo l'output layer
    model.add(Dense(1, kernel_initializer='normal', activation='sigmoid'))
    #Compilo il modello
    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
    return model
A questo punto posso procedere esattamente come abbiamo sempre fatto per tutti gli altri modelli:
estimator = KerasClassifier(build_fn=create_model, epochs=100, verbose=0)
scores = cross_val_score(estimator, X, y, cv=10)
results_df = show_results('Neural networks', scores)
results_df

 

Classifier Accuracy
0 DecisionTreeClassifier 0.74 (+/- 0.07)
1 RandomForestClassifier 0.80 (+/- 0.11)
2 SVC 0.80 (+/- 0.12)
3 KNeighborsClassifier 0.79 (+/- 0.10)
4 MultinomialNB 0.78 (+/- 0.12)
5 LogisticRegression 0.81 (+/- 0.10)
6 Neural networks 0.80 (+/- 0.10)

Chi ha vinto la sfida?

“DecisionTreeClassifier” è ultimo della classifica, “LogisticRegression” (media 0.81) ha vinto dimostrando che in certe circostanze, anche in questo “astruso mondo dell’intelligenza artificiale”, Davide (LogisticRegression) può vincere ancora contro Golia (Neural networks).

Il Machine Learning mi affascina da sempre, ma chi ha voglia di approfondire si trova di fronte ad un universo di cose, e di certo non è facilitato dalla terminologia: anaconda, pandas, numpy, keras, tensorflow, rnn, cnn, reinforcement learning, data cleaning, hyper parameters, ecc… che è tanto variegata quanto fantasiosa, e la prima sensazione che si prova è di essere in balia di qualcosa che è troppo più grande di noi.

Nonostante ciò, spinto dalla curiosità mi sono messo a fare qualche tutorial on line con l’aspettativa, visto che spesso su altri argomenti ha funzionato, di ottenere buoni risultati, ma l’aspettativa era sistematicamente disattesa e l’unico risultato a conclusione del tutorial era: “wow” che bello!!!
Ma se cercavo di ripercorrere il percorso era tutto piuttosto oscuro, mi sentivo come uno che sta ballando al buio, una sensazione piacevole ma che svanisce nel momento in cui ti rendi conto che non hai la più pallida idea di dove sei finito, né tanto meno come ci sei arrivato.

Poi ho avuto la possibilità di seguire un corso on line: “Machine Learning, Data Science and Deep Learning with Pyton” con la certezza di non capirci nulla.
A fine corso era previsto un test, quanto vi ho presentato in questo articolo.
Mettersi alla prova su questioni così delicate utilizzando un approccio che sino a qualche mese fa per me era “arabo” era decisamente stimolante, e sapere di poter procedere con calma senza la “mannaia” dell’esame andato male rendeva la cosa anche divertente.
Mano a mano che procedevo mi rendevo conto che tutte quelle “fancy words” (come si divertiva a definirle il docente) cominciavano ad assumere un significato, inoltre il quadro si faceva sempre un po’ più nitido, poi, quando ho iniziato a fare il mio lavoro – che è scrivere codice – appurando che con “così poco codice python” si riusciva a risolvere un problema così complesso, mi sono detto: “wow” ma questa è una specie di magia!!!
Anche perché se un dummy – io – con due righe di codice riesce a risolvere questo problema (che è un po’ di più dell’inflazionato:System.out.println("Hello world!!!"), cosa possono fare quelli bravi?

Per tale ragione, spinto da raptus di esaltazione ho sentito la necessità di condividere questa esperienza, ed i titoli delle due canzoni citate nel sottotitolo sintetizzano in modo perfetto il mio pensiero sul Machine Learning all’inizio ed alla fine di questo percorso, e per tale ragione “Dancing in the dark” e “It’s a kind of magic” si trovano esattamente dove ha senso che siano…

PS
Per i pochi che non hanno mai ascoltato questi due pezzi meravigliosi e per i più che hanno voglia di riascoltarli, mi congedo mettendovi a disposizione i links:

PS2
Ah per la cronaca, proprio per rimanere sul tema delle “fancy words”, lo strumento che ho utilizzato per fare questa cosa si chiama Jupyter notebook!!!

PS3 non è la playstation, ma cinque per nove fa 47!!!