Il Potere della Non-linearità nelle Reti Neurali

La vita non è una linea retta

"Per tutte le partenze ci son coincidenze... In cerca di coincidenze, non accontentarti"
— Marta sui Tubi, "Coincidenze"

La vita raramente segue un percorso lineare. Le nostre esperienze, decisioni e incontri creano percorsi tortuosi, cerchi che si intersecano, connessioni impreviste che sfidano ogni previsione. Proprio come canta Marta sui Tubi in "Coincidenze", siamo "passeggeri di un destino" che prende forma attraverso intersezioni inaspettate, deviazioni improvvise e trasformazioni non lineari.

E se ti dicessi che le reti neurali artificiali, per funzionare veramente, devono imitare questa fondamentale non-linearità della vita?

In questo post, esploreremo come un semplice elemento non lineare sia la differenza tra un modello di machine learning limitato e uno capace di cogliere la complessità del mondo reale — proprio come nella vita, dove le trasformazioni più significative raramente seguono un percorso prevedibile e lineare.

Se vuoi un sottofondo musicale che accompagni la lettura, questa è la canzone che mi ha accompagnato nella scrittura di questo articolo:

Il problema: separare ciò che non è linearmente separabile

Immagina di dover classificare punti che formano due cerchi concentrici, uno dentro l'altro. Ogni punto appartiene a una delle due classi: quella del cerchio interno o quella del cerchio esterno. Sembra semplice per l'occhio umano, ma per un algoritmo di machine learning può rappresentare una sfida insormontabile... a meno che non utilizziamo il superpotere della non-linearità!

In questo post, esploreremo:

  1. Perché i modelli puramente lineari falliscono in problemi come questo
  2. Come le funzioni di attivazione non lineari (come ReLU) trasformano completamente le capacità dei nostri modelli
  3. Una dimostrazione pratica con codice PyTorch

I limiti della linearità

Iniziamo con un semplice esperimento: costruiamo un modello lineare e vediamo come se la cava con il nostro dataset di cerchi concentrici.

import torch
from torch import nn

class LinearModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer_1 = nn.Linear(in_features=2, out_features=10)
        self.layer_2 = nn.Linear(in_features=10, out_features=10)
        self.layer_3 = nn.Linear(in_features=10, out_features=1)
        
    def forward(self, x):
        return self.layer_3(self.layer_2(self.layer_1(x)))

Questo modello ha tre strati lineari in sequenza, ma c'è un problema fondamentale: la composizione di funzioni lineari produce sempre una funzione lineare.

Matematicamente, se y = Ax e z = By, allora z = B(Ax) = (BA)x, che è ancora una funzione lineare!

Questo significa che, indipendentemente dal numero di strati lineari che impiliamo uno sopra l'altro, il nostro modello può tracciare solo confini di decisione lineari (linee rette in 2D, piani in 3D).

Risultati dell'addestramento del modello lineare

Dopo aver addestrato il modello lineare sui nostri cerchi concentrici, ecco cosa otteniamo:

Epoch 900 | Loss 0.69315 | Acc: 50.00% | Test Loss: 0.69315 | Test Acc 50.00%

Notate il risultato? Un'accuratezza del 50% è equivalente a tirare a indovinare in un problema binario. Il nostro modello lineare non è riuscito a imparare assolutamente nulla di utile!

Introduzione della non-linearità: il potere di ReLU

Modifichiamo ora il nostro modello aggiungendo una semplice funzione di attivazione non lineare, ReLU (Rectified Linear Unit), che è definita come f(x) = max(0, x):

class NonLinearModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.layer_1 = nn.Linear(in_features=2, out_features=10)
        self.layer_2 = nn.Linear(in_features=10, out_features=10)
        self.layer_3 = nn.Linear(in_features=10, out_features=1)
        self.relu = nn.ReLU()
        
    def forward(self, x):
        return self.layer_3(self.relu(self.layer_2(self.relu(self.layer_1(x)))))

Questa piccola modifica cambia tutto! Ora il nostro modello può apprendere confini di decisione non lineari.

Rilevamento automatico del dispositivo di calcolo

Nel nostro codice, abbiamo implementato una funzione `get_device()` che rileva automaticamente il miglior dispositivo disponibile per l'addestramento:

def get_device():
    if torch.backends.mps.is_available():
        return torch.device("mps")
    elif torch.cuda.is_available():
        return torch.device("cuda")
    else:
        return torch.device("cpu")

# Poi usa
device = get_device()Code language: PHP (php)

Questa funzione verifica in ordine:

  1. Se è disponibile una GPU NVIDIA con supporto CUDA
  2. Se è disponibile un chip Apple Silicon con supporto MPS (Metal Performance Shaders)
  3. Altrimenti, utilizza la CPU come fallback

Questo rende il nostro codice completamente agnostico rispetto alla piattaforma: funzionerà su qualsiasi sistema, sfruttando automaticamente l'hardware più veloce disponibile. L'accelerazione hardware è particolarmente importante quando si addestrano reti più complesse o su dataset più grandi.

Come avviene l'addestramento: dalla loss function alle predizioni

Per comprendere meglio come funziona l'addestramento, analizziamo il processo passo dopo passo:

Dal modello ai raw logits

Quando passiamo i nostri dati attraverso il modello con `model(X_train)`, otteniamo i **raw logits**. Questi sono valori non elaborati che possono variare da -∞ a +∞ e rappresentano la "fiducia" non normalizzata del modello nella classificazione:

y_logits = model(X_train).squeeze()  # Il .squeeze() rimuove le dimensioni di grandezza 1
Code language: PHP (php)

Dai raw logits alle probabilità

Per convertire questi logits in probabilità comprese tra 0 e 1, applichiamo la funzione sigmoide:

y_probs = torch.sigmoid(y_logits)  # Converte logits in probabilità [0,1]

La funzione sigmoide è definita come σ(x) = 1/(1+e^(-x)) e "schiaccia" qualsiasi numero reale nell'intervallo [0,1].
Code language: PHP (php)

Dalle probabilità alle etichette predette

Infine, arrotondiamo queste probabilità per ottenere le nostre etichette predette (0 o 1):

y_pred = torch.round(y_probs)  # Arrotonda a 0 o 1Code language: PHP (php)

Questo passaggio trasforma ogni probabilità maggiore o uguale a 0.5 in 1 (classe positiva), e ogni probabilità minore di 0.5 in 0 (classe negativa).

La loss function: Binary Cross Entropy with Logits

Per calcolare l'errore del nostro modello, utilizziamo la Binary Cross Entropy with Logits Loss:

loss = nn.BCEWithLogitsLoss()(y_logits, y_train)

Questa funzione di loss è particolarmente adatta ai problemi di classificazione binaria perché:

  1. Combina sigmoid e BCE in un unico passaggio - È numericamente più stabile rispetto all'applicazione separata di sigmoid e BCE
  2. Lavora direttamente sui logits - Non richiede di calcolare esplicitamente le probabilità
  3. Penalizza fortemente le previsioni errate con alta confidenza - Se il modello è molto sicuro (produce un logit elevato) ma sbaglia, la penalità è maggiore

Matematicamente, la BCE with Logits Loss può essere espressa come:
L(y, ŷ) = -[y·log(σ(ŷ)) + (1-y)·log(1-σ(ŷ))]
dove y è l'etichetta vera, ŷ è il logit predetto e σ è la funzione sigmoide.

Risultati dell'addestramento del modello non lineare

Dopo l'addestramento:

Epoch 900 | Loss 0.10253 | Acc: 97.50% | Test Loss: 0.09862 | Test Acc 98.12%

Che differenza! Con l'aggiunta della non-linearità, il nostro modello raggiunge un'accuratezza superiore al 97%, quasi perfetta.

Perché la non-linearità fa la differenza?

La magia della non-linearità può essere compresa attraverso diverse prospettive:

1. Trasformazione dello spazio delle caratteristiche

La ReLU "piega" lo spazio delle caratteristiche in un modo che rende i punti linearmente separabili in questa nuova rappresentazione. Immagina di sollevare uno dei cerchi in una terza dimensione - improvvisamente potresti separare i punti con un piano!

2. Approssimazione di funzioni universali

Il teorema dell'approssimazione universale ci dice che una rete neurale con almeno uno strato nascosto e funzioni di attivazione non lineari può approssimare qualsiasi funzione continua. Senza non-linearità, questo potere espressivo sparisce.

3. Comportamento a "interruttori"

La ReLU agisce come un interruttore che accende o spegne specifici neuroni in base all'input. Questo crea regioni di attivazione complesse che possono modellarsi attorno a forme arbitrarie nel nostro spazio di input.

Visualizzazione del confine di decisione

Nella figura sopra, possiamo vedere:

  • A sinistra: il modello lineare che tenta (e fallisce) di separare i cerchi con una linea retta
  • A destra: il modello non lineare che traccia un confine circolare quasi perfetto

Conclusione:
Abbracciare la non-linearità nella tecnologia e nella vita

"Parti senza aver fatto il biglietto, parti per restare ancora sveglio... Non accontentarti"
— Marta sui Tubi, "Coincidenze"

Abbiamo visto come l'aggiunta di un semplice elemento di non-linearità (la funzione ReLU) trasformi completamente le capacità di una rete neurale, permettendole di risolvere problemi che altrimenti sarebbero impossibili con approcci puramente lineari.

È affascinante come questo principio risuoni anche nella nostra vita quotidiana. Come ci ricordano i Marta sui Tubi nella loro canzone "Coincidenze", la vita è piena di intersezioni inaspettate, trasformazioni e "coincidenze" che non seguono percorsi prevedibili o lineari.

Le decisioni più importanti raramente sono il risultato di processi lineari — spesso emergono da connessioni impreviste, intuizioni improvvise o dalla capacità di "piegare" il nostro pensiero in modi nuovi e creativi. I problemi più complessi raramente hanno soluzioni dirette — richiedono approcci non convenzionali, pensiero laterale, e l'abilità di vedere oltre i confini lineari.

Che si tratti di reti neurali artificiali o della crescita personale, la lezione è la stessa: per affrontare problemi complessi, bisogna abbracciare la non-linearità. Non accontentarsi di soluzioni lineari, prevedibili. Partire "senza aver fatto il biglietto", esplorare nuove dimensioni, e permettere alle nostre menti (naturali o artificiali) di tracciare percorsi che una linea retta non potrebbe mai seguire.

In un certo senso, la funzione ReLU è per le reti neurali ciò che la curiosità e l'apertura mentale sono per noi: un modo per trasformare il nostro spazio delle possibilità e vedere connessioni che altrimenti rimarrebbero invisibili.

E così, mentre continuiamo a sviluppare sistemi di AI sempre più sofisticati, forse possiamo prendere ispirazione sia dalla matematica che dalla poesia — riconoscendo che il vero potere, tanto nella tecnologia quanto nella vita, si trova spesso oltre la linea retta.

Come ci ricorda la canzone: "Ovunque lo vorrai sarà il centro del mondo... In cerca di coincidenze, non accontentarti."

Perché, in fondo, è proprio nella non-linearità — delle reti e della vita — che troviamo la nostra vera potenza trasformativa.

Related Post

Maggio 13, 2025
Dentro la Mente degli LLM: La Matematica che alimenta i Transformers

Introduzione I Large Language Models (LLM) come GPT, BERT e le loro varianti come DistilBERT rappresentano una delle più straordinarie innovazioni dell'intelligenza artificiale moderna. Alla base di questi sistemi troviamo un'architettura chiamata "Transformer", introdotta nel celebre paper "Attention Is All You Need" pubblicato da Google nel 2017, che ha rivoluzionato l'elaborazione del linguaggio naturale. Ma […]

Giugno 9, 2025
Dal Maggiordomo Congelato agli NPC Intelligenti: L'Evoluzione dell'IA nei Videogame

Ricordate il povero maggiordomo di Lara Croft? Quello che tutti noi abbiamo tristemente rinchiuso nella cella frigorifera? È ora di liberarlo... digitalmente parlando! Il Maggiordomo che Cambiò Tutto (Senza Volerlo) Chi ha giocato ai primi Tomb Raider ricorda sicuramente il maggiordomo di casa Croft: quell'adorabile (e fastidioso) NPC che ci seguiva ovunque nella villa, spesso […]

veronicaschembri
Copyright © Veronica Schembri

Privacy Policy
Cookie Policy
💬