Guida Cos'è il Machine Learning in sintesi...coding from scratch

Kode

Utente Emerald
10 Dicembre 2013
1,226
81
371
623
Ultima modifica da un moderatore:
machinelearning.jpg

Ecco il terzo capitolo della serie dedicata al Machine Learning per principianti, all'interno di quest capitolo andremo ad implementare dei semplici modelli basati su Naive Bayes, Logistic Regression e una semplice rete neurale (sia utilizzando una classica feed-forward che una rete ricorrente basata su LSTM).

Ogni programma avrà una piccola intro che servirà per comprendere meglio cosa realmente si sta utilizzando. Invito comunque ad un aspirante Data Science di implementare in un proprio programma gli algoritmi proposti senza usare sklearn o keras.

Vi domanderete se sia difficile, ma vi posso assicurare che la parte su cui avrete problemi è sempre collegata alla comprensione del procedimento piuttosto che come implementarlo. In ogni caso, vi spiego i vari algoritmi nel pratico cosi da poter capire bene come elaborino e darvi anche un incipit su come implementarli da scratch.

Capitoli:






1    Naive Bayes



Naive Bayes è uno dei metodi di classificazione statistici sul Supervised Learning più utilizzati. In genere ha un discreto successo per problemi molto semplici ed è estremamente veloce. Si basa sul teorema di Naive Bayes che vede come un evento sia condizionato ad uno dato. Nello specifico quello che fa è calcolare la "likelihood" o vicinanza di un dato sample (dato in input) ad una classe e restituisce come output la classe in cui quel particolare input ha più probabilità di appartenenza.

naive_bayes_icon.png


In questo caso possiamo dividere un input X in un vettore (x1,x2...xN) di features e calcolare come quella particolare feature (che corrisponde ad una proprietà del dato, tipo il nome di una persona) appartenga ad una specifica classe c (ossia il valore della "vicinanza" di quel particolare dato ad una classe, generalmente è basato su quante occorrenze quel particolare dato è stato presente negli input del training set). Quindi la probabilità di P(c|X) è uguale alla probabilità che data una classe c una features xi appartenga a c combinata secondo il teorema fondamentale della probabilità di n esperimenti dipendenti.

Inoltre, bisogna moltiplicare le probabilità delle feature condizionate ad una classe data c per la probabilità della classe c corrispondente alla frequenza della classe c all'interno degli esempi di training. Calcolate tutte le probabilità di P(c|X) per ogni c, bisogna trovare quell'argomento c tale che massimizzi P(c|X), esso sarà la classe output di X.

Esistono varie applicazioni per Naive Bayes che, ricordiamo, è un algoritmo che lavora su valori discreti e su classi binarie (0,1) oppure con più classi possibili (metodi multinomial).

Passiamo ora ad un pò di codice:

Codice:
import numpy as np
from sklearn.naive_bayes import MultinomialNB

# generiamo dati random
rng = np.random.RandomState(1)
 X = rng.randint(5, size=(6, 100))
y = np.array([1, 2, 3, 4, 5, 6])

# instanziamo Naive Bayes
clf = MultinomialNB()

# Training sui dati di input
clf.fit(X, y)

# Predizione
print(clf.predict(X[2:3]))

# Output [3]

2    Logistic Regression



Anche se il nome può ingannare, la Logistic Regression è un metodo di classificazione e non di regressione (si lo so, i matematici hanno una concezione tutta loro dei nomi). Rispetto alla Naive Bayes, esso è utilizzato principalmente per problemi binari di tipo bool. Ossia, identifica se un input appartenga ad una classe X oppure non appartenga ad X (quindi appartiene ad Y), con X e Y le uniche due classi possibili.

Matematicamente è abbastanza semplice:

Screenshot 2021-11-13 at 13.46.44.png

Dato un input x, la sua classificazione è data da p(x) che è uguale alla sigmoid function di una funzione di regressione lineare (corrispondente all'esponente del numero di eulero). Quello che si fa, è amplificare la classificazione lineare della linear regression usando una curva approssimativa (la sigmoid stessa). Il valore della classe assegnata è fortemente condizionata da beta0 che raffigura l'intercetta, ossia il punto corrispondente all'intersezione della fusione sigma con l'asse delle y e beta1 che, invece, è uguale al coefficiente angolare di x. Inoltre, un valore epsilon corrisponde ad un bias aggiuntivo (valore intero che ha la funzione di smoothing delle immagini per i margini di errore). Graficamente possiamo vedere l'approssimazione dei punti ad una delle due classi per un esempio:

Screenshot 2021-11-13 at 13.47.31.png

Da come si può notare dal grafico, i punti rappresentano l'approssimazione dell'appartenenza di un dato ad una delle due classi, ovviamente la curva non è sempre precisa, i falsi positivi o negativi sono sempre presenti in ogni modello ed è anche uno dei motivi per cui le predizioni non sono mai precise al 100%.

Let's code it!

Python:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
import seaborn as sn
import matplotlib.pyplot as plt

# esempio di data frame in JSON
candidates = {'gmat': [780,750,690,710,680,730,690,720,740,690,610,690,710,680,770,610,580,650,540,590,620,600,550,550,570,670,660,580,650,660,640,620,660,660,680,650,670,580,590,690],
              'gpa': [4,3.9,3.3,3.7,3.9,3.7,2.3,3.3,3.3,1.7,2.7,3.7,3.7,3.3,3.3,3,2.7,3.7,2.7,2.3,3.3,2,2.3,2.7,3,3.3,3.7,2.3,3.7,3.3,3,2.7,4,3.3,3.3,2.3,2.7,3.3,1.7,3.7],
              'work_experience': [3,4,3,5,4,6,1,4,5,1,3,5,6,4,3,1,4,6,2,3,2,1,4,1,2,6,4,2,6,5,1,2,4,6,5,1,2,1,4,5],
              'admitted': [1,1,0,1,0,1,0,1,1,0,0,1,1,0,1,0,0,1,0,0,1,0,0,0,0,1,1,0,1,1,0,0,1,1,1,0,0,0,0,1]
              }

# Conversione di un JSON in un formato DataFrame fornito dalla libreria pandas
df = pd.DataFrame(candidates,columns= ['gmat', 'gpa','work_experience','admitted'])

#print (df) se vuoi visualizzare la struttura

# le colonne di X sono quelle in input
X = df[['gmat', 'gpa','work_experience']]

# la colonna di Y rappresenta l'output, il modello deve trovare una funzione f(X) -> Y che stabilisca le regole per dare Y in risposta ad X.
y = df['admitted']


# Splittiamo il dataset utilizzando sklearn
X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.25,random_state=0)

# Instanziamo il modello di base fornito da sklearn
logistic_regression= LogisticRegression()

# Training basandosi sui dati del training set uscente dallo splitting
logistic_regression.fit(X_train,y_train)

# Predizione sul testing set
y_pred=logistic_regression.predict(X_test)

# Rappresentazione della confusion matrix per la visualizzazione dei falsi positivi/negativi e true positive/negative (essendo un problema binario, possiamo rappresentare i risultati
# in tale maniera
confusion_matrix = pd.crosstab(y_test, y_pred, rownames=['Actual'], colnames=['Predicted'])
sn.heatmap(confusion_matrix, annot=True)

# Accuracy e visualizzazione dei risultati sul testing set rispetto alle predizioni.
print('Accuracy: ',metrics.accuracy_score(y_test, y_pred))
plt.show()

3    Deep Neural Network



Come anticipato nel capitolo precedente (vedere l'indice per visionarlo), le reti neurali rappresentano una forma di calcolo sub-simbolico algoritmicamente non dimostrabile. In altri termini, sappiamo la logica dietro la struttura e il "how-to-work" ma non possiamo predire come si aggiorneranno i pesi (ricollegandosi ai problemi computazionalmente non risolvibili in tempi polinomiali, la predizione di un "next-step" di una rete neurale è NP su una size data dalla variazione delle features dell'input).

Ricordiamo che abbiamo due tipi di reti neurali:
1. Feed-forward: In cui l'input di un layer è l'output del layer precedente, il primo layer ha come input il dato stesso mentre l'output layer ha come output il risultato della rete neurale.
2. Recurrent network: Reti in cui l'output può rientrare nell'input di neuroni già utilizzati secondo il concetto di "hidden state". Implementabile in layer LSTM o GRU che risolvono un problema detto di "vanishing gradient" (https://gaz.wiki/wiki/it/Vanishing_gradient_problem).

Sotto un punto di vista di implementazione, tensorflow e keras riescono a costruire modelli dinamici andando ad aggiungere dei semplici oggetti Layers (di uno specifico tipo) ad un modello di tipo Model.

3.1    Feed-forward network

Per le reti feed-forward andiamo a creare un semplice modello in Tensorflow:

Codice:
import tensorflow as tf

model = tf.keras.models.Sequential() # creazione del modello
model.add(tf.keras.Input(shape=(16,))) # input di 16 neuroni
model.add(tf.keras.layers.Dense(32, activation='relu')) # funzione densamente connessa con funzione di attivazione ReLu
model.add(tf.keras.layers.Dense(32)) # applichiamo un altro layer senza funzione di attivazione come output
model.output_shape

Le funzioni di attivazioni non fanno altro che modificare la somma dei prodotti degli input per i propri pesi secondo l'immagine fornita da una funzione. Nel nostro caso la ReLu è una delle più semplici, in cui si prende come immagine il massimo tra 0 e x. Ossia f(-10) = 0, f(10) = 10. In altri termini, è 0 se l'input è negativo, uguale all'input nel caso sia positivo (matematicamente lineare).

I layer Dense collegano ogni neurone con ciascun neurone del layer successivo. Quindi nel caso abbiamo un layer Dense di 10 neuroni susseguito da un altro layer con 5 neuroni, avremmo 10 x 5 = 50 collegamenti. Per maggiori info, leggere la documentazione ufficiale qui: https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense

3.2    Recurrent network usando LSTM

Un esempio di applicazioni delle reti neurali è nel mondo del Natural Language Processing in cui le reti neurali sono in grado di comprendere il testo scritto da un essere umano e classificarlo secondo vari criteri (emozioni, sentimenti, significati) che rappresentano le classi di un problema. Nel nostro caso andiamo a definire un modello multinomials (classificatore fatto da 3 o più possibili classi da cui scegliere).

Andiamo a presentare solo il modello, ma a questo va aggiunto una parte di pre-processing, data analysis e data loading che presenterò in un mio notebook al termine della discussione.

Python:
from keras.models import Sequential
from keras.layers import LSTM, Dense, Dropout, Masking, Embedding

model = Sequential() # Creazione di un modello

# Embedding layer che trasforma un testo codificato in una collezione di vettori, uno per ogni parola.
model.add(
    Embedding(input_dim=num_words, # dimensione dell'input pari al numero di parole
              input_length = training_length, # lunghezza dell'input pari al testo con lunghezza massima
              output_dim=100,
              weights=[embedding_matrix], # pesi iniziali definiti in una matrice pre-trained
              trainable=False, # nel caso non si ha una matrice pre-trained, quindi weights = null, trainable deve essere a True
              mask_zero=True))

# Masking layer for pre-trained embeddings, per maggiori info, visitare: https://www.tensorflow.org/api_docs/python/tf/keras/layers/Masking
model.add(Masking(mask_value=0.0))

# Recurrent layer, rappresenta una rete LSTM (https://developer.nvidia.com/discover/lstm) che restituisce un singolo output in seguito alla lettura da sinistra a destra del testo e la comprensione e collegamento dei vari concetti che rappresenta in maniera semantica.
model.add(LSTM(64, return_sequences=False,
               dropout=0.1, recurrent_dropout=0.1))

# Fully connected layer, rappresenta un layer classico utile per classificare ciò che si è appresi dal testo nel layer precedente
model.add(Dense(64, activation='relu'))

# Dropout for regularization, ossia processo che diminuisce l'overfitting dovuti alla customizzazione dei risultati basata solamente dai dati del training set e non in ambito generico.
model.add(Dropout(0.5))

# Output layer, ossia uno strato pieno di neuroni corrispondenti ognuno ad una parola in cui applicare il softmax per determinare la singola classe del testo
# (un pò come Naive Bayes per intenderci)
model.add(Dense(num_words, activation='softmax'))

# Compilazione del modello utilizzando la funzione loss della crossentropia categorica e come ottimizator l'algoritmo Adam.
model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']) # si vuole stampare per epoca la metrica di accuracy

history = model.fit(
                    X_train,  # input
                    y_train,  # output che dovremmo attenderci
                    batch_size=2048, # numero di esempi dati in input parallelamente
                    epochs=150, # numero di sessioni di traning su cui sottomettere il modello (ogni epoca ha un ribilanciamento dei pesi ossia il "training"
                                           #. e una sottomissione in input di ogni dato del training set)
                    validation_data=(X_valid, y_valid)) # nello splitting, il training set viene nuovamente diviso in cui una piccola parte serve per la sottomissione post-trained per verificare la
                                                                                 # validità del bilanciamento dei pesi.

Sono consapevole che molti concetti siano nuovi, per cui vi invito a leggere la documentazione fornita da tensorflow (https://www.tensorflow.org/resources/learn-ml) in modo da chiarire qualsiasi dubbio.


4    FAQ finali



In genere, dopo una spiegazione effettuata solamente per mostrare una panoramica generale delle reti neurali e di come si implementino usando le API più famose, le domande più frequenti sono:

4.1    Come funziona in pratica il training di una rete neurale?

Si esegue un esempio (singolo se batch_size = 1, o multiplo se batch_size = n) e si calcola un valore sull'output dato dalla rete e sul label dell'esempio utilizzando la funzione loss (nel nostro caso, categorial_crossentropy). Arrivati a questo punto:

1. Si calcola la derivata della funzione su quel punto per trovare la tangente per la discesa del gradiente
2. Si aggiornano i pesi utilizzando il gradiente su ogni singolo neurone partendo dall'ultimo layer fino al primo in dipendenza del valore bilanciato precedente (ricordo che l'ordine è inverso rispetto all'input).
3. Si passa all'esecuzione di un nuovo dato di input sui pesi modificati

Man mano che si va avanti, i pesi verranno bilanciati e migliorati dato che si andrà a minimizzare la funzione loss che rappresenta la funzione di errore del modello (misura la differenza o drift del valore predetto rispetto a quello reale).

4.2    Posso imparare le reti neurali senza utilizzare nozioni matematiche?

Le API di Tensorflow come quelle di PyTorch o quelle fornite semplicemente da keras sono, generalmente, ad alto livello. Quindi si, potresti creare modelli senza sapere effettivamente come elaborino matematicamente i valori ma, come detto, andresti a creare qualcosa come se fosse una black box e non capiresti il perché di alcuni risultati. Purtroppo (o per fortuna) il Machine Learning si basa su nozioni probabilistiche, statistiche e matematiche, escludendo tali principi rimane solo una conoscenza superficiale delle nozioni presentate.

4.3    Dove posso approfondire tali concetti?

Potresti utilizzare i tutorial di learning offerti da Google su Tensorflow, oppure comprare (o scaricare in PDF) libri sul Machine Learning.
Nello specifico, tableau fornisce una guida su quali siano i migliori in ML: https://www.tableau.com/it-it/learn/articles/books-about-machine-learning
Invece, per il Deep learning vi consiglierei quello su cui ho studiato:

Amazon product ASIN 0262035618
Vedi: https://www.amazon.it/Deep-Learning-Ian-Goodfellow/dp/0262035618

Anche se non precludo che ve ne siano di migliori.

4.4    Quali sono i pre-requisiti per capire fino in fondo il Machine Learning?

Credo che un semplice corso di Probabilistica e Statistica possa essere sufficiente sia per apprendere nozioni sotto un punto di vista matematico, sia per imparare le basi della maggior parte dei modelli di ML classici in circolazione. Ovviamente, questo è solo un piccolo consiglio, potreste benissimo evitare di fare un corso di tale genere e apprendere ugualmente i concetti citati.


4.5    Un alternativa ai libri e ai siti citati che sia molto più veloce per apprendere e applicare praticamente il ML/DL?

Consiglio i tutorial su javatpoint che, secondo me, sono quelli più completi. Inoltre sono spiegati anche abbastanza bene. L' unico problema è che sono in inglese.


5    Conclusioni



Ricordo che la guida è per definire una panoramica sul Machine Learning per chi non si è mai approcciato a questo mondo, e non ha lo scopo di fornire conoscenze ma solo per orientare chi è interessato agli argomenti. Per approfondire i concetti, leggete i libri che vi ho consigliato nelle FAQ o la documentazione su Tensorflow, Keras o altre API simili.

Per visionare un intero progetto basato sull'applicazione di vari modelli, vi rimando ad un mio semplice progetto su Github fatto per un corso universitario:https://github.com/kode-git/yelp-review-dataset-nlp

Per eseguirlo:

1. Scaricare il dataset su: https://www.yelp.com/dataset
2. Eseguire da terminale
Codice:
 git clone https://github.com/kode-git/yelp-review-dataset-nlp.git
3. Inserire il file yelp_academic_dataset_review.json nella directory da impostare nel notebook
4. Installare jupyter come descritto nella guida: https://jupyter.org/install
5. Per evitare problemi di versioning:
Codice:
pip install --upgrade jupyter_http_over_ws>=0.0.7 && \
  jupyter serverextension enable --py jupyter_http_over_ws
6. Eseguire jupyter per la sincronizzazione con i notebook di Google Colab:
Codice:
jupyter notebook \
  --NotebookApp.allow_origin='https://colab.research.google.com' \
  --port=8888 \
  --NotebookApp.port_retries=0 --allow-root
7. Andare su https://colab.research.google.com o in alternativa su https://localhost:8888 per eseguirlo da jupyter
7.1 Nel caso si voglia usare Google Colab, caricare il dataset cliccando sulla cartella nella barra a sinistra e poi sul simbolo di "upload", selezionare il notebook .ipynb e caricarlo.
7.2 In alto a destra cliccare su "connect to local runtime" (devi aver eseguito i punti 5 e 6 per poterlo fare con successo).
8. Modificare la riga:
Python:
os.chdir('./colab-to-dataset')
# dove il dataset è in colab-to-dataset/review.json e
# il yelp.ipynb nella stessa directory della cartella colab-to-dataset
9. Eseguire le ">" ai lati di ogni snippet di codice

Per quanto riguarda tale modello, ci impiega all'incirca 7 ore totali per effettuare il run di tutti i training dei modelli. Questo perché il dataset utilizzato è in uso applicativo a livelli accademici su contesti reali che vedono come dataset un file json di 5.9 GB.