Normalizzazione dei dati per il deep learning: come preparare un dataset

Quando si lavora con le reti neurali e il machine learning, è molto importante avere un dataset ben strutturato e pulito. Tuttavia, non sempre si ha la fortuna di avere a disposizione un dataset perfetto, spesso si trovano dataset “sporchi”, ovvero con valori mancanti, dati non uniformi, dati outlier, ecc. In questi casi, è necessario normalizzare il dataset per renderlo adatto ad una libreria di deep learning. In questo articolo, spiegherò come normalizzare un dataset “sporco” utilizzando Python.

  1. Rimozione dei valori mancanti Il primo passo per normalizzare un dataset “sporco” è quello di rimuovere i valori mancanti. Ciò significa rimuovere tutte le righe o colonne che contengono valori mancanti. Ci sono diverse librerie Python che possono essere utilizzate per rimuovere i valori mancanti, come ad esempio Pandas.
import pandas as pd

# Leggere il dataset
df = pd.read_csv("dataset.csv")

# Rimuovere le righe che contengono valori mancanti
df = df.dropna()
  1. Rimozione dei dati outlier Il secondo passo è quello di rimuovere i dati outlier, ovvero i dati che si discostano molto dal resto del dataset. Ci sono diverse tecniche per individuare i dati outlier, come ad esempio la tecnica della deviazione standard o del rango interquartile. Una volta individuati i dati outlier, si possono rimuovere utilizzando la funzione drop di Pandas.
import numpy as np

# Individuazione dei dati outlier
mean = np.mean(df["column_name"])
std_dev = np.std(df["column_name"])
threshold = 3
outlier = []
for i in df["column_name"]:
    z_score = (i - mean) / std_dev
    if np.abs(z_score) > threshold:
        outlier.append(i)
        
# Rimozione dei dati outlier
df = df.drop(df[df["column_name"].isin(outlier)].index)
  1. Normalizzazione dei dati Il terzo passo è quello di normalizzare i dati rimanenti, ovvero i dati che non sono stati rimossi nei passaggi precedenti. La normalizzazione dei dati significa portare tutti i dati ad avere una scala comune, in modo da permettere alla rete neurale di lavorare correttamente. Ci sono diverse tecniche per normalizzare i dati, come ad esempio la normalizzazione min-max o la normalizzazione Z-score.
# Normalizzazione min-max
df["column_name"] = (df["column_name"] - df["column_name"].min()) / (df["column_name"].max() - df["column_name"].min())

# Normalizzazione Z-score
df["column_name"] = (df["column_name"] - df["column_name"].mean()) / df["column_name"].std()

Una volta normalizzato il dataset, è importante verificare la sua distribuzione. In altre parole, bisogna assicurarsi che le diverse classi di dati siano rappresentate in modo equilibrato nel dataset. In caso contrario, il modello potrebbe essere influenzato dalla maggioranza delle istanze di una classe e ignorare completamente le altre. Per esempio, se si ha un dataset di immagini con 1000 immagini di gatti e solo 10 di cani, il modello potrebbe finire per identificare solo i gatti e ignorare completamente i cani.

Per risolvere questo problema, si possono utilizzare diverse tecniche. Una di queste è il “data augmentation”, che consiste nella creazione di nuove istanze di dati a partire dalle istanze esistenti. Questo può essere fatto tramite tecniche come la rotazione, lo zoom, la riflessione, l’aggiunta di rumore, tra gli altri. In questo modo, è possibile aumentare il numero di istanze di una classe meno rappresentata e quindi bilanciare il dataset.

Ecco un esempio di data augmentation in Python utilizzando la libreria “imgaug”, che offre una vasta gamma di tecniche di data augmentation per immagini:

import imgaug.augmenters as iaa
from PIL import Image

# carica l'immagine da augmentare
img = Image.open("path/to/image.jpg")

# definisci il pipeline di augmentazione
seq = iaa.Sequential([
    iaa.Flipud(0.5),    # flip verticale con probabilità 0.5
    iaa.Affine(rotate=(-10, 10)),   # rotazione casuale tra -10 e 10 gradi
    iaa.GaussianBlur(sigma=(0, 1.0)),   # sfocatura gaussiana con sigma casuale tra 0 e 1.0
    iaa.AddToHueAndSaturation(value=(-10, 10))   # variazione casuale della tonalità e saturazione
])

# applica l'augmentazione all'immagine
aug_img = seq.augment_image(img)

# mostra l'immagine originale e quella aumentata
img.show()
aug_img.show()

In questo esempio, abbiamo definito un pipeline di augmentazion che consiste in una flip verticale con probabilità del 50%, una rotazione casuale tra -10 e 10 gradi, una sfocatura gaussiana con sigma casuale tra 0 e 1.0 e una variazione casuale della tonalità e saturazione. Successivamente, abbiamo applicato il pipeline all’immagine originale e abbiamo visualizzato sia l’immagine originale che quella aumentata. Questo esempio mostra come la data augmentation possa essere utilizzata per generare più varianti di un’immagine e migliorare la capacità di generalizzazione di un modello di deep learning.

Inoltre, un’altra tecnica che può essere utilizzata per affrontare il problema della distribuzione sbilanciata dei dati è il “weighted loss”. Questo metodo prevede l’assegnazione di un peso maggiore alle istanze di dati delle classi meno rappresentate e un peso minore alle istanze delle classi più rappresentate, in modo da bilanciare l’importanza delle diverse classi durante l’addestramento del modello.

Ecco un esempio di come utilizzare la “weighted loss” in TensorFlow 2.x:

import tensorflow as tf

# Definisci i pesi per le classi
class_weights = {0: 1.0, 1: 2.0, 2: 3.0}

# Definisci la funzione di perdita pesata
def weighted_loss(class_weights):
    def loss(y_true, y_pred):
        # Calcola la perdita cross-entropy
        ce_loss = tf.keras.losses.categorical_crossentropy(y_true, y_pred)
        # Applica i pesi
        weight_vector = tf.gather(tf.constant(list(class_weights.values())), tf.argmax(y_true, axis=1))
        weighted_loss = weight_vector * ce_loss
        # Calcola la media
        mean_loss = tf.reduce_mean(weighted_loss)
        return mean_loss
    return loss

# Definisci il modello
model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(64, activation='relu', input_shape=(784,)),
    tf.keras.layers.Dense(32, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])

# Compila il modello
model.compile(optimizer='adam', loss=weighted_loss(class_weights), metrics=['accuracy'])

In questo esempio, class_weights è un dizionario che assegna un peso ad ogni classe. La funzione weighted_loss è definita come una funzione annidata che accetta i pesi come parametro e restituisce una funzione di perdita personalizzata. All’interno della funzione di perdita, calcoliamo la perdita cross-entropy come di consueto e poi applichiamo i pesi alle perdite corrispondenti. Infine, calcoliamo la media delle perdite pesate per ottenere la perdita finale.

Infine, nel metodo compile() del modello, passiamo la funzione weighted_loss come parametro loss. In questo modo, il modello utilizzerà la perdita personalizzata pesata durante l’addestramento.

In conclusione, la normalizzazione dei dati è un passo fondamentale nella preparazione di un dataset per l’addestramento di modelli di deep learning. È importante considerare sia la normalizzazione dei dati stessi sia la distribuzione delle diverse classi di dati all’interno del dataset. Utilizzando le tecniche appropriate, è possibile ottenere un dataset equilibrato e normalizzato che permette l’addestramento di modelli di deep learning altamente precisi e affidabili.

In conclusione, normalizzare un dataset “sporco” è un passaggio importante per rendere il dataset adatto ad una libreria di deep learning. Ci sono diverse tecniche per normalizzare i dati, e la scelta dipende dal tipo di dati che si stanno normalizzando. Utilizzando le librerie Python come Pandas e NumPy, la normalizzazione dei dati diventa un processo semplice e veloce.