Hai mai scritto model.fit(X, y) in scikit-learn e pensato: «Bello, funziona… ma cosa è successo là dentro?» Se la risposta è sì, questo tutorial è per te. Oggi non useremo nessun modello preconfezionato: costruiremo da zero, in puro NumPy, il perceptron di Rosenblatt (1957) e il suo successore Adaline. Sono i nonni di ogni rete neurale moderna, e quando avrai finito di implementarli, la frase «la rete aggiorna i pesi» smetterà di essere magia e diventerà una riga di codice che hai scritto tu.
Il percettrone come aritmetica, la funzione a gradino, la regola di apprendimento e il muro dello XOR sono nel libro.
Leggi «Il percettrone e la sua regola di apprendimento» nel libro →
Ti serve solo Python 3.12 e un minimo di dimestichezza con NumPy. Se non ricordi cos’è il prodotto scalare, ti basta sapere che è una somma di prodotti elemento per elemento: x @ w fa esattamente x[0]*w[0] + x[1]*w[1] + .... Tutto qui.
Il neurone più semplice del mondo
Immagina un buttafuori all’ingresso di un locale. Per ogni persona valuta alcuni indizi — età, abbigliamento, se è in lista — e a ogni indizio dà un peso diverso: essere in lista conta moltissimo, la giacca conta poco. Somma mentalmente tutti gli indizi pesati e, se il totale supera una certa soglia, ti fa entrare. Altrimenti no. Niente vie di mezzo.
Il perceptron è esattamente questo buttafuori, in forma matematica. Prende le feature in ingresso x₁, x₂, ..., xₙ, le moltiplica per i rispettivi pesi w₁, w₂, ..., wₙ, aggiunge un bias (la «soglia di simpatia» di base del buttafuori) e passa il totale a una funzione gradino: se la somma è ≥ 0 risponde +1, altrimenti −1. Fine. Un neurone artificiale, nella sua versione del 1957, è tutto qui.
La parte interessante non è il calcolo: è come il perceptron trova i pesi giusti. E qui arriva la regola di apprendimento.
La regola di apprendimento: sbaglia, correggi, ripeti
Il perceptron impara per tentativi, un esempio alla volta. Gli mostri un punto, lui predice, e succede una di due cose: se ha indovinato, non tocca nulla; se ha sbagliato, sposta i pesi nella direzione dell’esempio sbagliato, di una quantità controllata dal tasso di apprendimento η (eta). È come regolare la manopola della doccia: acqua troppo fredda, giri verso il caldo; troppo calda, giri verso il freddo. Non calcoli la temperatura ottima a tavolino — correggi in base all’ultimo errore, e dopo qualche iterazione converge.
Un modello che impara non è niente di mistico: è un vettore di numeri più una regola per correggerli quando sbagliano.
Il neurone di Rosenblatt: ingressi pesati, somma, funzione gradino. La linea tratteggiata è l’apprendimento — quando la predizione è sbagliata, i pesi vengono corretti in proporzione all’errore.
Il codice: perceptron in 30 righe
Prepara l’ambiente. Uso uv, il gestore che nel 2026 ha praticamente sostituito pip per velocità (ma un classico python -m venv va benissimo):
# nel terminale
uv venv && source .venv/bin/activate
uv pip install numpy scikit-learn
Ed ecco il perceptron completo. Leggilo con calma: ogni riga fa una cosa sola.
import numpy as np
class Perceptron:
"""Il perceptron di Rosenblatt, da zero."""
def __init__(self, eta=0.1, n_epoche=10, seed=42):
self.eta = eta # tasso di apprendimento
self.n_epoche = n_epoche # passate complete sul dataset
self.seed = seed
def fit(self, X, y):
gen = np.random.default_rng(self.seed)
# un peso per feature, più w[0] che fa da bias
self.w = gen.normal(0.0, 0.01, size=X.shape[1] + 1)
self.errori_ = [] # errori per epoca, per monitorare
for _ in range(self.n_epoche):
errori = 0
for xi, target in zip(X, y):
# se la predizione è giusta, delta vale 0: non si tocca nulla
delta = self.eta * (target - self.predict(xi))
self.w[1:] += delta * xi # correggi i pesi...
self.w[0] += delta # ...e il bias
errori += int(delta != 0.0)
self.errori_.append(errori)
return self
def net_input(self, X):
# la somma pesata: prodotto scalare più bias
return X @ self.w[1:] + self.w[0]
def predict(self, X):
# funzione gradino: +1 sopra la soglia, -1 sotto
return np.where(self.net_input(X) >= 0.0, 1, -1)
Nota il dettaglio elegante: target - self.predict(xi) vale 0 quando il perceptron indovina, +2 o −2 quando sbaglia. La correzione si «accende» da sola solo sugli errori. Rosenblatt dimostrò che, se le due classi sono separabili da una linea retta, questa regola converge sempre in un numero finito di passi. Se non lo sono, invece, il perceptron balla per sempre senza fermarsi — ed è per questo che serve un successore.
Adaline: imparare da quanto sbagli, non solo se sbagli
Il perceptron riceve un feedback binario: giusto o sbagliato. È come un esame passa/non-passa: sai se sei stato bocciato, ma non se ti mancava un punto o trenta. L’Adaline (ADAptive LInear NEuron, Widrow e Hoff, 1960) introduce l’idea che ha reso possibile il deep learning: misurare l’errore continuo — la distanza tra la somma pesata e il valore vero, prima del gradino — e usare la discesa del gradiente per ridurlo. Se non ricordi cos’è la discesa del gradiente, ti basta sapere che è camminare in discesa sulla superficie dell’errore, un passetto alla volta, nella direzione di massima pendenza.
class Adaline:
"""Come il perceptron, ma impara dall'errore continuo."""
def __init__(self, eta=0.05, n_epoche=30, seed=42):
self.eta = eta
self.n_epoche = n_epoche
self.seed = seed
def fit(self, X, y):
gen = np.random.default_rng(self.seed)
self.w = gen.normal(0.0, 0.01, size=X.shape[1] + 1)
self.costi_ = []
for _ in range(self.n_epoche):
output = self.net_input(X) # attivazione LINEARE, niente gradino
errori = y - output # errore continuo, su tutti i punti
# discesa del gradiente sull'errore quadratico medio
self.w[1:] += self.eta * X.T @ errori / len(y)
self.w[0] += self.eta * errori.mean()
self.costi_.append(float((errori ** 2).mean()))
return self
def net_input(self, X):
return X @ self.w[1:] + self.w[0]
def predict(self, X):
# il gradino resta, ma solo per la predizione finale
return np.where(self.net_input(X) >= 0.0, 1, -1)
Due differenze rispetto al perceptron, entrambe fondamentali. Primo: l’aggiornamento avviene su tutto il dataset insieme (batch), non un punto alla volta. Secondo: la quantità y - output è un numero reale, non ±2 — l’Adaline sa di quanto ha sbagliato, e corregge in proporzione. Questa coppia di idee (funzione di costo continua + gradiente) è letteralmente la stessa che addestra i modelli da miliardi di parametri di oggi. Cambia la scala, non il principio.
Alla prova su dati veri
Testiamo entrambi sul dataset Iris, usando due classi e due feature per poter ragionare in due dimensioni:
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
iris = load_iris()
# prime 100 righe = setosa e versicolor (separabili linearmente)
X = iris.data[:100, [0, 2]] # lunghezza sepalo e petalo
y = np.where(iris.target[:100] == 0, -1, 1)
# l'Adaline senza scaling converge male: standardizziamo
X_std = StandardScaler().fit_transform(X)
per = Perceptron(eta=0.1, n_epoche=10).fit(X_std, y)
ada = Adaline(eta=0.05, n_epoche=30).fit(X_std, y)
print("Errori perceptron per epoca:", per.errori_)
print("Costo Adaline (prime 5 epoche):",
[round(c, 4) for c in ada.costi_[:5]])
print("Accuratezza perceptron:", (per.predict(X_std) == y).mean())
print("Accuratezza Adaline: ", (ada.predict(X_std) == y).mean())
Dovresti vedere gli errori del perceptron azzerarsi dopo poche epoche (le classi sono separabili, quindi la convergenza è garantita) e il costo dell’Adaline scendere in modo liscio e monotono. Prova a rimuovere lo StandardScaler: il perceptron se la cava comunque, l’Adaline arranca. È una lezione che vale per tutto il ML basato su gradiente — la scala delle feature conta.
Quando (non) usarli
Onestà prima di tutto: in un progetto reale del 2026 non userai mai un perceptron scritto a mano. Per un classificatore lineare c’è LogisticRegression di scikit-learn, che converge meglio, dà probabilità e gestisce più classi. Il perceptron, da solo, non risolve nemmeno lo XOR — il famoso limite evidenziato da Minsky e Papert nel 1969, che congelò la ricerca sulle reti neurali per anni.
Il valore di questo esercizio è un altro: adesso sai esattamente cosa sono i pesi, cosa fa il learning rate, perché una funzione di costo continua batte un feedback binario e perché le feature vanno scalate. Ogni rete neurale che incontrerai — CNN, transformer, quello che verrà — è fatta di strati di questi neuroni, con attivazioni più morbide e molte più dimensioni. La comprensione che hai costruito oggi si trasferisce intatta.
Prova tu
Due esercizi, in ordine di difficoltà. Primo: cambia le classi da (setosa, versicolor) a (versicolor, virginica), che non sono linearmente separabili con queste feature. Osserva cosa succede a per.errori_: converge o oscilla per sempre? E l’Adaline? Secondo: trasforma l’Adaline in versione stocastica, aggiornando i pesi un esempio alla volta come il perceptron (ma con l’errore continuo). Hai appena implementato lo SGD, l’algoritmo che addestra mezzo deep learning.
Per approfondire: il capitolo 2 di Machine Learning with PyTorch and Scikit-Learn di Sebastian Raschka dedica cinquanta pagine proprio a perceptron e Adaline, con la storia completa e le derivazioni matematiche passo passo.

I commenti sono riservati agli iscritti.
Accedi per commentare