Introduzione: Quando i Dati Non Bastano
Nel machine learning, i dati sono tutto. Ma spesso non ne abbiamo abbastanza, oppure il dataset e sbilanciato (una classe ha molti più campioni delle altre). La data augmentation e un insieme di tecniche per espandere artificialmente il dataset, creando nuovi campioni a partire da quelli esistenti. La matematica dietro queste tecniche va dalle trasformazioni geometriche all'interpolazione statistica.
Cosa Imparerai
- Trasformazioni geometriche per immagini: rotazione, flip, scaling
- Mixup e CutMix: interpolazione tra campioni
- SMOTE: oversampling sintetico per classi minoritarie
- Augmentation per testo e serie temporali
- Modelli generativi: GAN e VAE per dati sintetici
- Quando l'augmentation aiuta e quando fa danni
Trasformazioni Geometriche per Immagini
Le trasformazioni geometriche sono le forme più semplici e intuitive di augmentation. Ogni trasformazione può essere espressa come una matrice di trasformazione applicata alle coordinate dei pixel.
Rotazione
Una rotazione di angolo \\theta nel piano 2D:
Scaling (Zoom)
Trasformazione Affine Generale
Combinando rotazione, scaling, traslazione e shear in coordinate omogenee:
import numpy as np
def rotate_image(image, angle_deg):
"""Rotazione di un'immagine (semplificata per array 2D)."""
angle_rad = np.radians(angle_deg)
cos_a, sin_a = np.cos(angle_rad), np.sin(angle_rad)
# Matrice di rotazione
R = np.array([[cos_a, -sin_a],
[sin_a, cos_a]])
h, w = image.shape[:2]
center = np.array([h/2, w/2])
# Crea immagine ruotata
rotated = np.zeros_like(image)
for i in range(h):
for j in range(w):
coords = np.array([i, j]) - center
src = R.T @ coords + center
si, sj = int(round(src[0])), int(round(src[1]))
if 0 <= si < h and 0 <= sj < w:
rotated[i, j] = image[si, sj]
return rotated
def augment_batch(images, labels):
"""Applica augmentation casuale a un batch."""
augmented_images = []
augmented_labels = []
for img, label in zip(images, labels):
augmented_images.append(img)
augmented_labels.append(label)
# Flip orizzontale (50% probabilità)
if np.random.random() > 0.5:
augmented_images.append(np.fliplr(img))
augmented_labels.append(label)
# Flip verticale (30% probabilità)
if np.random.random() > 0.7:
augmented_images.append(np.flipud(img))
augmented_labels.append(label)
# Rumore gaussiano
if np.random.random() > 0.5:
noise = np.random.normal(0, 0.05, img.shape)
augmented_images.append(np.clip(img + noise, 0, 1))
augmented_labels.append(label)
return np.array(augmented_images), np.array(augmented_labels)
# Esempio
np.random.seed(42)
batch = np.random.rand(4, 8, 8) # 4 immagini 8x8
labels = np.array([0, 1, 0, 2])
aug_images, aug_labels = augment_batch(batch, labels)
print(f"Originale: {batch.shape[0]} immagini")
print(f"Augmentato: {aug_images.shape[0]} immagini")
Mixup: Interpolazione tra Campioni
Mixup crea nuovi campioni interpolando linearmente tra coppie di campioni esistenti (sia input che label):
dove \\lambda \\sim \\text{Beta}(\\alpha, \\alpha) con \\alpha \\in (0, \\infty). Tipicamente \\alpha = 0.2 (miscela leggera).
perchè funziona: Mixup agisce come regolarizzatore, forzando il modello a fare predizioni lineari tra i campioni. Riduce l'overfitting e migliora la calibrazione.
import numpy as np
def mixup(X, y, alpha=0.2):
"""Mixup data augmentation."""
n = X.shape[0]
# Lambda dalla distribuzione Beta
lam = np.random.beta(alpha, alpha, size=n)
# Permutazione casuale per il secondo campione
indices = np.random.permutation(n)
# Per feature multidimensionali, reshape lambda
lam_x = lam.reshape(-1, *([1] * (X.ndim - 1)))
# Interpolazione
X_mix = lam_x * X + (1 - lam_x) * X[indices]
y_mix = lam * y + (1 - lam) * y[indices]
return X_mix, y_mix
# Esempio con one-hot labels
X = np.random.randn(100, 10) # 100 campioni, 10 feature
y = np.eye(3)[np.random.randint(0, 3, 100)] # One-hot, 3 classi
X_mix, y_mix = mixup(X, y, alpha=0.2)
print(f"Campione originale y[0]: {y[0]}")
print(f"Campione mixup y_mix[0]: {np.round(y_mix[0], 3)}")
print(f"Somma label mixup: {y_mix[0].sum():.4f}") # Deve essere ~1
CutMix: Ritagliare e Incollare
CutMix taglia una regione rettangolare da un'immagine e la sostituisce con la corrispondente regione di un'altra. La label e proporzionale all'area:
dove r_w, r_h sono larghezza e altezza della regione ritagliata, e W, H le dimensioni dell'immagine.
SMOTE: Oversampling per Classi Minoritarie
SMOTE (Synthetic Minority Over-sampling Technique) genera campioni sintetici per la classe minoritaria interpolando tra campioni esistenti e i loro k nearest neighbors:
dove \\mathbf{x}_{nn} e un vicino casuale tra i k-nearest neighbors di \\mathbf{x}_i.
Quando usare SMOTE: per dataset tabulari con classi sbilanciate (frode, diagnosi mediche, anomalie). NON usare per immagini (meglio Focal Loss o class weights) e NON applicarlo al test set (solo al training).
import numpy as np
from sklearn.neighbors import NearestNeighbors
def smote(X_minority, n_synthetic, k=5):
"""SMOTE: genera campioni sintetici dalla classe minoritaria."""
n_samples = X_minority.shape[0]
k_actual = min(k, n_samples - 1)
# Trova k nearest neighbors
nn = NearestNeighbors(n_neighbors=k_actual + 1)
nn.fit(X_minority)
distances, indices = nn.kneighbors(X_minority)
synthetic = []
for _ in range(n_synthetic):
# Scegli un campione casuale
idx = np.random.randint(0, n_samples)
# Scegli un vicino casuale (escludi se stesso: indice 0)
nn_idx = indices[idx, np.random.randint(1, k_actual + 1)]
# Interpola
lam = np.random.random()
new_sample = X_minority[idx] + lam * (X_minority[nn_idx] - X_minority[idx])
synthetic.append(new_sample)
return np.array(synthetic)
# Dataset sbilanciato: 100 classe 0, 10 classe 1
np.random.seed(42)
X_majority = np.random.randn(100, 5) + np.array([2, 0, 0, 0, 0])
X_minority = np.random.randn(10, 5) + np.array([-2, 0, 0, 0, 0])
# Genera 90 campioni sintetici per bilanciare
X_synthetic = smote(X_minority, n_synthetic=90, k=5)
X_balanced = np.vstack([X_majority, X_minority, X_synthetic])
y_balanced = np.array([0]*100 + [1]*10 + [1]*90)
print(f"Originale: classe 0={100}, classe 1={10}")
print(f"Bilanciato: classe 0={100}, classe 1={100}")
print(f"Shape bilanciato: {X_balanced.shape}")
Augmentation per Testo
Per i dati testuali, le tecniche principali sono:
- Synonym Replacement: sostituisci parole con sinonimi
- Random Insertion: inserisci sinonimi in posizioni casuali
- Random Swap: scambia posizioni di parole
- Random Deletion: elimina parole casuali con probabilità p
- Back-Translation: traduci in un'altra lingua e ritorna (EN -> FR -> EN)
Augmentation per Serie Temporali
Tecniche specifiche per dati temporali:
Jittering (Rumore Gaussiano)
Time Warping
Distorce l'asse temporale con una funzione monotona casuale, accelerando o rallentando porzioni della serie.
Window Slicing
Estrae sotto-sequenze casuali di lunghezza w < T e le riscala alla lunghezza originale.
Modelli Generativi per Dati Sintetici
GAN (Generative Adversarial Networks)
Un generatore G e un discriminatore D competono in un gioco min-max:
G genera dati falsi da rumore z, D cerca di distinguere veri da falsi. All'equilibrio, G produce campioni indistinguibili dai dati reali.
VAE (Variational Autoencoder)
La loss del VAE combina ricostruzione e regolarizzazione dello spazio latente:
Il primo termine e la qualità della ricostruzione, il secondo forza lo spazio latente ad essere una Gaussiana standard, permettendo di campionare nuovi dati.
Quando l'Augmentation Funziona (e Quando No)
Funziona:
- Dataset piccoli con pochi campioni per classe
- Trasformazioni che preservano la semantica (flip di immagini naturali)
- Classi sbilanciate (SMOTE per tabulari, Focal Loss + augmentation per immagini)
Non funziona o fa danni:
- Trasformazioni che cambiano la semantica (ruotare cifre 6 e 9)
- Augmentation troppo aggressiva (deforma i dati oltre il riconoscibile)
- Applicare augmentation al test set (data leakage)
- Dati già abbondanti e diversificati
Riepilogo
Punti Chiave da Ricordare
- Trasformazioni geometriche: matrici di rotazione, scaling, flip - base dell'augmentation per immagini
- Mixup: \\tilde{x} = \\lambda x_i + (1-\\lambda) x_j - regolarizzazione tramite interpolazione
- SMOTE: interpola tra vicini della classe minoritaria per bilanciare
- GAN: min-max game per generare dati sintetici realistici
- VAE: ricostruzione + KL divergence per spazio latente campionabile
- Regola d'oro: augmentation deve preservare la semantica e non toccare il test set
Conclusione della Serie: con questo articolo si conclude la serie "Matematica e Statistica per l'AI". Abbiamo coperto i fondamenti dall'algebra lineare alla teoria dell'informazione, dall'ottimizzazione alla matematica dei Transformer. Ogni concetto e stato collegato alle applicazioni pratiche in ML/AI con implementazioni NumPy. Queste fondamenta matematiche ti permetteranno di comprendere in profondità qualsiasi algoritmo di machine learning che incontrerai.







