Introduzione: perchè l'Algebra Lineare e il Linguaggio del Machine Learning
Ogni volta che un modello di machine learning elabora un'immagine, classifica un testo o genera una previsione, sotto il cofano sta eseguendo operazioni di algebra lineare. I dati di input vengono rappresentati come vettori, i pesi del modello come matrici, e l'intero processo di inferenza si riduce a una serie di moltiplicazioni matriciali e trasformazioni lineari.
In questo articolo costruiremo le fondamenta matematiche partendo dai concetti base fino ad arrivare ad autovalori, SVD e decomposizioni che sono alla base di algoritmi come PCA, raccomandazione e compressione di modelli. Ogni formula sarà accompagnata da una spiegazione intuitiva e da un'implementazione NumPy.
Cosa Imparerai
- Vettori, norme e prodotto scalare: la geometria dei dati
- Matrici: moltiplicazione, trasposta, inversa e il loro significato
- Determinante e rango: cosa ci dicono sulla trasformazione
- Autovalori e autovettori: le direzioni invarianti
- Singular Value Decomposition (SVD): lo strumento più potente del ML
- Implementazioni pratiche in NumPy
Vettori: I Mattoni Fondamentali
Un vettore e una lista ordinata di numeri. In ML, un vettore rappresenta un singolo data point: le feature di un'immagine, i pixel di un frame, le parole codificate di una frase. Un vettore in \\mathbb{R}^n ha n componenti.
Ad esempio, un vettore a 3 dimensioni:
Norma di un Vettore: Misurare la Grandezza
La norma misura la "lunghezza" di un vettore. Le due norme più usate in ML sono:
Norma L2 (Euclidea) - la distanza geometrica dall'origine:
Norma L1 (Manhattan) - la somma dei valori assoluti, utile per promuovere sparsita:
In ML, la norma L2 viene usata nella regolarizzazione Ridge per penalizzare pesi troppo grandi, mentre la norma L1 nella regolarizzazione Lasso per ottenere pesi sparsi (molti a zero), utile per la feature selection.
Prodotto Scalare: Misurare la Similarità
Il prodotto scalare (dot product) tra due vettori e forse l'operazione più importante in ML. Misura quanto due vettori "puntano nella stessa direzione":
Geometricamente, il prodotto scalare e legato all'angolo \\theta tra i vettori:
Quando \\cos\\theta = 1, i vettori sono paralleli (massima similarità). Quando \\cos\\theta = 0, sono ortogonali (nessuna relazione). Questo e esattamente il principio della cosine similarity usata nei sistemi di raccomandazione e nella ricerca semantica.
Intuizione Chiave: In una rete neurale, ogni neurone calcola un prodotto scalare tra il vettore di input \\mathbf{x} e il vettore dei pesi \\mathbf{w}, aggiunge un bias b, e applica una funzione di attivazione: \\sigma(\\mathbf{w} \\cdot \\mathbf{x} + b).
import numpy as np
# Vettori
a = np.array([2, -1, 4])
b = np.array([1, 3, 2])
# Prodotto scalare
dot = np.dot(a, b) # 2*1 + (-1)*3 + 4*2 = 7
print(f"Dot product: {dot}")
# Norme
l2_norm = np.linalg.norm(a) # sqrt(4 + 1 + 16) = sqrt(21)
l1_norm = np.linalg.norm(a, ord=1) # 2 + 1 + 4 = 7
print(f"L2 norm: {l2_norm:.4f}")
print(f"L1 norm: {l1_norm}")
# Cosine similarity
cos_sim = np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
print(f"Cosine similarity: {cos_sim:.4f}")
Matrici: Trasformazioni dei Dati
Una matrice e un array rettangolare di numeri. In ML, le matrici rappresentano dataset (righe = campioni, colonne = feature) e pesi delle reti neurali. Una matrice \\mathbf{A} \\in \\mathbb{R}^{m \\times n} ha m righe e n colonne.
Moltiplicazione Matriciale: Il Cuore del Deep Learning
La moltiplicazione tra una matrice \\mathbf{A} \\in \\mathbb{R}^{m \\times n} e un vettore \\mathbf{x} \\in \\mathbb{R}^n produce un nuovo vettore \\mathbf{y} \\in \\mathbb{R}^m:
Questa operazione e una trasformazione lineare: prende un vettore nello spazio di input e lo mappa in uno spazio di output. Un layer di una rete neurale esegue esattamente questo:
dove \\mathbf{W} e la matrice dei pesi, \\mathbf{x} l'input, \\mathbf{b} il bias, e \\sigma la funzione di attivazione.
Trasposta e Simmetria
La trasposta \\mathbf{A}^T scambia righe e colonne: (\\mathbf{A}^T)_{ij} = A_{ji}. E fondamentale perchè:
- Il prodotto scalare si scrive come \\mathbf{a}^T \\mathbf{b}
- La matrice di covarianza e \\frac{1}{n}\\mathbf{X}^T\\mathbf{X}
- Nella backpropagation, i gradienti vengono propagati con le trasposte dei pesi
Determinante: Volume e Invertibilita
Il determinante di una matrice quadrata misura come la trasformazione scala i volumi. Per una matrice 2x2:
Se \\det(\\mathbf{A}) = 0, la matrice e singolare (non invertibile): la trasformazione "schiaccia" lo spazio, perdendo informazione. Questo e un segnale di collinearita nelle feature, che causa problemi nella regressione lineare.
Rango: Dimensione Effettiva
Il rango di una matrice e il numero di righe (o colonne) linearmente indipendenti. Se una matrice \\mathbf{A} \\in \\mathbb{R}^{m \\times n} ha rango r < \\min(m, n), significa che i dati vivono in un sottospazio di dimensione r, non nello spazio completo. Questo e il principio alla base della riduzione dimensionale.
import numpy as np
# Matrice dei pesi (layer neurale: 3 input -> 2 output)
W = np.array([[0.5, -0.3, 0.8],
[0.2, 0.7, -0.4]])
x = np.array([1.0, 2.0, 3.0])
# Forward pass: trasformazione lineare
y = W @ x # oppure np.dot(W, x)
print(f"Output: {y}") # [-0.1 + ... = risultato]
# Trasposta
print(f"W shape: {W.shape}") # (2, 3)
print(f"W^T shape: {W.T.shape}") # (3, 2)
# Determinante (solo matrici quadrate)
A = np.array([[3, 1], [2, 4]])
det = np.linalg.det(A)
print(f"Determinante: {det:.2f}") # 10.0
# Rango
rank = np.linalg.matrix_rank(W)
print(f"Rango: {rank}") # 2
Autovalori e Autovettori: Le Direzioni Speciali
Gli autovettori di una matrice sono le direzioni che non cambiano orientamento quando la trasformazione viene applicata. Vengono solo scalati di un fattore, chiamato autovalore. Formalmente, per una matrice quadrata \\mathbf{A}:
dove \\mathbf{v} e un autovettore e \\lambda il corrispondente autovalore.
Intuizione geometrica: immagina di stirare un foglio di gomma. La maggior parte dei punti si sposta in direzioni diverse, ma alcune direzioni vengono solo allungate o compresse senza ruotare. Quelle sono le direzioni degli autovettori, e quanto vengono allungate e dato dagli autovalori.
Per trovare gli autovalori, risolviamo l'equazione caratteristica:
In ML, gli autovalori della matrice di covarianza ci dicono quanta varianza c'è lungo ogni direzione principale. Questo e il fondamento della PCA (Principal Component Analysis).
Applicazione ML: In PCA, gli autovettori della matrice di covarianza sono le componenti principali, e gli autovalori indicano quanta varianza cattura ciascuna componente. Selezionando i top-k autovettori, riduciamo la dimensionalità mantenendo la maggior parte dell'informazione.
import numpy as np
# Matrice simmetrica (come una matrice di covarianza)
A = np.array([[4, 2],
[2, 3]])
# Calcolo autovalori e autovettori
eigenvalues, eigenvectors = np.linalg.eigh(A)
print(f"Autovalori: {eigenvalues}")
print(f"Autovettori:\n{eigenvectors}")
# Verifica: A @ v = lambda * v
for i in range(len(eigenvalues)):
v = eigenvectors[:, i]
lam = eigenvalues[i]
lhs = A @ v
rhs = lam * v
print(f"A*v = {lhs}, lambda*v = {rhs}, uguale: {np.allclose(lhs, rhs)}")
Singular Value Decomposition (SVD): Lo Strumento Universale
La SVD e la decomposizione più importante e versatile dell'algebra lineare per il ML. Qualsiasi matrice \\mathbf{A} \\in \\mathbb{R}^{m \\times n} può essere decomposta come:
dove:
- \\mathbf{U} \\in \\mathbb{R}^{m \\times m} - matrice ortogonale (direzioni output)
- \\boldsymbol{\\Sigma} \\in \\mathbb{R}^{m \\times n} - matrice diagonale con i valori singolari \\sigma_1 \\geq \\sigma_2 \\geq \\cdots \\geq 0
- \\mathbf{V}^T \\in \\mathbb{R}^{n \\times n} - matrice ortogonale (direzioni input)
Intuizione: la SVD scompone qualsiasi trasformazione lineare in tre passaggi: una rotazione (\\mathbf{V}^T), uno scaling lungo gli assi (\\boldsymbol{\\Sigma}), e un'altra rotazione (\\mathbf{U}).
SVD Troncata: Compressione Intelligente
Mantenendo solo i primi k valori singolari otteniamo la migliore approssimazione di rango k della matrice originale:
Questo e usato per: compressione di immagini, sistemi di raccomandazione (matrix factorization), riduzione del rumore, e Latent Semantic Analysis (LSA) per testi.
import numpy as np
# Matrice (es. ratings utenti-prodotti)
A = np.array([[5, 4, 0, 0],
[4, 5, 0, 0],
[0, 0, 4, 5],
[0, 0, 5, 4]])
# SVD completa
U, sigma, Vt = np.linalg.svd(A)
print(f"Valori singolari: {sigma}")
# SVD troncata (rank-2 approssimazione)
k = 2
U_k = U[:, :k]
sigma_k = np.diag(sigma[:k])
Vt_k = Vt[:k, :]
A_approx = U_k @ sigma_k @ Vt_k
print(f"Matrice originale:\n{A}")
print(f"Approssimazione rank-{k}:\n{np.round(A_approx, 2)}")
# Errore di ricostruzione
error = np.linalg.norm(A - A_approx, 'fro')
print(f"Errore Frobenius: {error:.4f}")
# Varianza spiegata
explained = np.sum(sigma[:k]**2) / np.sum(sigma**2) * 100
print(f"Varianza spiegata con k={k}: {explained:.1f}%")
Broadcasting e Operazioni Efficienti in NumPy
NumPy supporta il broadcasting, un meccanismo che permette di eseguire operazioni tra array di dimensioni diverse senza creare copie. Questo e fondamentale per scrivere codice ML efficiente.
import numpy as np
# Dataset: 1000 campioni, 10 feature
X = np.random.randn(1000, 10)
# Normalizzazione: sottrai media, dividi per std
mean = X.mean(axis=0) # shape: (10,)
std = X.std(axis=0) # shape: (10,)
X_norm = (X - mean) / std # Broadcasting automatico!
# Batch matrix multiplication
# W: (10, 5), X: (1000, 10) -> output: (1000, 5)
W = np.random.randn(10, 5)
output = X_norm @ W # Equivalente a np.dot(X_norm, W)
print(f"Output shape: {output.shape}")
# Moltiplicazione element-wise vs matrix
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(f"Element-wise: {a * b}") # [[5, 12], [21, 32]]
print(f"Matrix mult: {a @ b}") # [[19, 22], [43, 50]]
Applicazione Pratica: Forward Pass di una Rete Neurale
Mettiamo tutto insieme implementando un forward pass completo di una rete neurale a 2 layer usando solo algebra lineare:
import numpy as np
def relu(x):
return np.maximum(0, x)
def softmax(x):
exp_x = np.exp(x - np.max(x, axis=1, keepdims=True))
return exp_x / np.sum(exp_x, axis=1, keepdims=True)
# Architettura: 4 input -> 8 hidden -> 3 output (classificazione)
np.random.seed(42)
W1 = np.random.randn(4, 8) * 0.1 # Pesi layer 1
b1 = np.zeros(8) # Bias layer 1
W2 = np.random.randn(8, 3) * 0.1 # Pesi layer 2
b2 = np.zeros(3) # Bias layer 2
# Input: batch di 5 campioni, 4 feature ciascuno
X = np.random.randn(5, 4)
# Forward pass (pura algebra lineare!)
# Layer 1: trasformazione lineare + attivazione
z1 = X @ W1 + b1 # (5, 4) @ (4, 8) + (8,) = (5, 8)
h1 = relu(z1) # (5, 8) - attivazione ReLU
# Layer 2: trasformazione lineare + softmax
z2 = h1 @ W2 + b2 # (5, 8) @ (8, 3) + (3,) = (5, 3)
probs = softmax(z2) # (5, 3) - probabilità per 3 classi
print(f"Probabilità output:\n{np.round(probs, 4)}")
print(f"Somma per riga: {probs.sum(axis=1)}") # Deve essere ~1.0
print(f"Classi predette: {np.argmax(probs, axis=1)}")
Riepilogo e Connessioni con il ML
Punti Chiave da Ricordare
- Prodotto scalare \\mathbf{a} \\cdot \\mathbf{b}: misura similarità, e l'operazione base di ogni neurone
- Moltiplicazione matriciale \\mathbf{W}\\mathbf{x}: trasformazione lineare, cuore del forward pass
- Autovalori: indicano le direzioni di massima varianza (PCA)
- SVD: decomposizione universale per compressione, raccomandazione, denoising
- Norma L2: usata per regolarizzazione Ridge, previene overfitting
- Rango: dimensione effettiva dei dati, base per la riduzione dimensionale
Nel Prossimo Articolo: esploreremo il calcolo differenziale per il deep learning. Vedremo come i gradienti permettono alle reti neurali di apprendere e come la chain rule rende possibile la backpropagation.







