Vam tancar 05-03 amb una limitació confessada: per capturar la veritat no lineal de Rutalia vam haver de fabricar les features a mà (polinomis, interaccions). Les xarxes neuronals eliminen aquesta feina: aprenen també les transformacions. La bona notícia és que ja tens les dues peces que les componen: la combinació lineal + sigmoide de la regressió logística (05-02) és exactament una neurona, i el descens de gradient (05-03) és exactament el seu algorisme d'entrenament. L'única cosa genuïnament nova és la retropropagació (backpropagation): com calcular el gradient quan hi ha capes apilades — la regla de la cadena convertida en algorisme. En aquesta lliçó construirem una xarxa des de zero: el forward pass calculat a mà amb números concrets, la derivació guiada del backprop en una xarxa mínima, i una implementació completa en numpy pur entrenada sobre el dataset canònic per predir retards.
Contingut
- De la neurona a la xarxa multicapa
- Per què calen activacions no lineals
- Funcions d'activació
- Forward pass a mà: un lliurament concret
- Retropropagació: la regla de la cadena feta algorisme
- Implementació completa en numpy
- Entrenament: èpoques, batches i corbes de pèrdua
- Què és el deep learning i quan compensa
- Frameworks: què automatitzen PyTorch i Keras
De la neurona a la xarxa multicapa
Una neurona artificial és la regressió logística de 05-02: rep entrades, calcula una combinació lineal i aplica una funció d'activació.
La novetat estructural: apilar neurones en capes. Cada neurona d'una capa rep les sortides de totes les de la capa anterior:
flowchart LR
subgraph E["Entrada (features)"]
x1[distancia_km]
x2[hora_sortida]
end
subgraph O["Capa oculta"]
h1((h1))
h2((h2))
end
subgraph S["Sortida"]
o((p_retard))
end
x1 --> h1 & h2
x2 --> h1 & h2
h1 --> o
h2 --> o
La interpretació clau: la capa oculta aprèn features intermèdies. A 05-03, el terme distancia × hora_punta el vam inventar nosaltres; aquí, una neurona oculta pot aprendre a activar-se just davant de "lliurament llarg en hora punta" — la xarxa fabrica les seves pròpies interaccions. Els pesos de cada capa s'agrupen en una matriu, i tot el forward pass són multiplicacions de matrius: per això numpy (i les GPU) són l'hàbitat natural de les xarxes.
Per què calen activacions no lineals
Pregunta d'examen: què passa si traiem les activacions (g = identitat)? Encadenem dues capes lineals:
El resultat és W'·x + b': una altra funció lineal. Per moltes capes que apilis, una xarxa sense activacions col·lapsa exactament a la regressió lineal de 05-03 — centenars de paràmetres per expressar el mateix que una recta. La no-linealitat entre capes és el que impedeix el col·lapse i dona a la xarxa el seu poder: amb prou neurones ocultes i una activació no lineal, una xarxa pot aproximar qualsevol funció contínua (teorema d'aproximació universal). Poder representar-la no garanteix poder aprendre-la, però obre la porta.
Funcions d'activació
| Funció | Fórmula | Rang | Derivada | Ús típic |
|---|---|---|---|---|
| Sigmoide | 1/(1+e⁻ᶻ) | (0, 1) | σ(z)·(1−σ(z)) | Capa de sortida en classificació binària (és una probabilitat) |
| Tanh | tanh(z) | (−1, 1) | 1−tanh²(z) | Capes ocultes (històrica); centrada en 0 |
| ReLU | max(0, z) | [0, ∞) | 1 si z>0, si no 0 | Capes ocultes (l'estàndard modern) |
Per què ReLU va destronar la sigmoide a les capes ocultes? Per les seves derivades. La derivada de la sigmoide val com a màxim 0,25 i es fa gairebé 0 quan |z| és gran (la neurona se satura): en encadenar capes, els gradients es multipliquen i s'esvaeixen — les primeres capes deixen d'aprendre. La derivada de ReLU és 1 a tota la zona activa: el gradient travessa les capes sense encongir-se, i a més costa una comparació en lloc d'una exponencial. Regla pràctica: ReLU a les ocultes, sigmoide a la sortida binària.
Forward pass a mà: un lliurament concret
Res no substitueix fer els comptes una vegada. Xarxa mínima: 2 entrades (estandarditzades), 2 neurones ocultes amb ReLU, 1 sortida sigmoide. Prenem un lliurament tipus C-1042: lluny i en hora punta → després d'estandarditzar, x = (1.0, 1.5) (distància, hora). Pesos actuals (els hauria deixat així un entrenament a mitges):
Pas a pas:
Capa oculta (preactivació): z1 = 0.8·1.0 + 0.5·1.5 + 0.1 = 1.65 → h1 = ReLU(1.65) = 1.65 z2 = -0.4·1.0 + 0.9·1.5 + 0.2 = 1.15 → h2 = ReLU(1.15) = 1.15 Sortida: z3 = 1.2·1.65 + (-0.7)·1.15 + (-0.3) = 0.875 p = σ(0.875) = 1/(1+e^-0.875) ≈ 0.706
La xarxa estima un 70,6% de probabilitat de retard per a aquest lliurament. En codi, tot el càlcul és:
relu = lambda z: np.maximum(0, z)
x = np.array([1.0, 1.5])
h = relu(W1.T @ x + b1) # capa oculta: (2,)
p = sigmoide(W2 @ h + b2) # sortida: escalar, 0.706Si el lliurament va arribar realment tard (y = 1), l'error és p − y = −0.294: la xarxa s'ha quedat curta. La pregunta del milió: quant ha de canviar cadascun dels 9 paràmetres (4+2 de la capa oculta, 2+1 de la sortida) per reduir aquest error? Respondre-la és la retropropagació.
Retropropagació: la regla de la cadena feta algorisme
A 05-03, amb un sol nivell (ŷ = w·x + b), derivar el cost respecte de cada pes era directe. Ara W1 afecta el cost a través de h, que afecta a través de z3, que afecta a través de p. L'eina és la regla de la cadena: la derivada d'una composició és el producte de les derivades de les baules. La retropropagació és simplement organitzar-la per no repetir càlculs: es computa el gradient de l'última capa i es propaga cap enrere, reutilitzant a cada capa el que ja s'ha calculat a la següent.
Derivem guiats, sobre la xarxa mínima, amb cost d'entropia creuada L = −[y·ln p + (1−y)·ln(1−p)] (l'estàndard en classificació; el seu gradient és més net que el del MSE aquí).
Baula 1 — de la pèrdua a z3. Component la derivada de L respecte de p amb la de la sigmoide (σ' = σ(1−σ)) es produeix una cancel·lació memorable:
El "missatge d'error" de la sortida és, literalment, l'error de predicció. (La mateixa forma que tenia el gradient a la regressió lineal: no és casualitat, l'entropia creuada està triada per a això.)
Baula 2 — gradients de la capa de sortida. z3 = W2·h + b2, així que cada pes de W2 rep l'error escalat per l'activació que el va travessar:
Baula 3 — propagar l'error a la capa oculta. Quina part de la culpa té cada neurona oculta? La que li transmet el seu pes cap a la sortida, filtrada per la derivada de la seva ReLU (1 aquí, perquè totes dues z eren positives; una neurona "apagada" no rebria culpa):
δ1 = δ3 · W2[0] · ReLU'(z1) = −0.294 · 1.2 · 1 = −0.353 δ2 = δ3 · W2[1] · ReLU'(z2) = −0.294 · (−0.7) · 1 = +0.206
Fixa't en el signe de δ2: com que el seu pes de sortida és negatiu (−0,7), a h2 li convé baixar perquè p pugi. La xarxa reparteix la culpa amb signe.
Baula 4 — gradients de la capa oculta. Mateix patró que la baula 2, un nivell més avall: ∂L/∂W1[i,j] = δj · xi, ∂L/∂b1 = δ.
I amb tots els gradients, un pas de descens de 05-03 tal qual: W ← W − α·∂L/∂W. Això és tota la retropropagació: forward guardant les activacions, backward multiplicant errors per pesos i derivades d'activació, capa a capa cap enrere. Computacionalment és programació dinàmica sobre el graf de càlcul (01-03): cada δ es calcula una vegada i es reutilitza per a tots els gradients de la seva capa; el backward costa el mateix que el forward, O(nombre de pesos), quan l'aplicació ingènua de la regla de la cadena costaria molt més.
Implementació completa en numpy
Generalitzem a matrius i al dataset canònic complet (13 features, capa oculta de 16, sortida binària):
class XarxaNeuronal:
"""Xarxa de 2 capes (oculta ReLU + sortida sigmoide) en numpy pur."""
def __init__(self, d_entrada, d_oculta, llavor=42):
rng = np.random.default_rng(llavor)
# Inicialització petita i aleatòria: trencar la simetria és essencial
# (amb pesos iguals, totes les neurones aprendrien el mateix)
self.W1 = rng.normal(0, np.sqrt(2 / d_entrada), (d_entrada, d_oculta))
self.b1 = np.zeros(d_oculta)
self.W2 = rng.normal(0, np.sqrt(2 / d_oculta), (d_oculta, 1))
self.b2 = np.zeros(1)
def forward(self, X):
"""Prediu i GUARDA les activacions (el backprop les necessitarà)."""
self.Z1 = X @ self.W1 + self.b1 # (n, 16) preactivació
self.H = np.maximum(0, self.Z1) # (n, 16) ReLU
self.P = sigmoide(self.H @ self.W2 + self.b2) # (n, 1) probabilitat
return self.P
def backward(self, X, y, alpha=0.1):
"""Retropropagació + un pas de descens de gradient."""
n = len(y)
# Baula 1: error a la sortida (entropia creuada + sigmoide)
d3 = self.P - y.reshape(-1, 1) # (n, 1)
# Baula 2: gradients de la capa de sortida
gW2 = self.H.T @ d3 / n # (16, 1)
gb2 = d3.mean(axis=0)
# Baula 3: propagar l'error a la capa oculta
d1 = (d3 @ self.W2.T) * (self.Z1 > 0) # (n, 16); ReLU' = màscara
# Baula 4: gradients de la capa oculta
gW1 = X.T @ d1 / n # (13, 16)
gb1 = d1.mean(axis=0)
# Pas de descens (05-03, sense canvis)
self.W2 -= alpha * gW2; self.b2 -= alpha * gb2
self.W1 -= alpha * gW1; self.b1 -= alpha * gb1
def perdua(self, X, y):
p = np.clip(self.forward(X), 1e-9, 1 - 1e-9) # evita log(0)
y = y.reshape(-1, 1)
return float(-(y * np.log(p) + (1 - y) * np.log(1 - p)).mean())Cada línia del backward és una de les baules que hem derivat a mà, vectoritzada per a n exemples alhora. La màscara (self.Z1 > 0) és la derivada de ReLU aplicada en bloc: les neurones apagades no propaguen culpa.
Entrenament: èpoques, batches i corbes de pèrdua
El bucle d'entrenament és el mini-batch de 05-03, literalment:
# Dataset canònic, objectiu retard, pipeline de 05-01
xarxa = XarxaNeuronal(d_entrada=X_tr_e.shape[1], d_oculta=16)
rng = np.random.default_rng(0)
historial = {"train": [], "test": []}
for epoca in range(300):
ordre = rng.permutation(len(y_tr)) # barrejar cada època
for i in range(0, len(y_tr), 64): # mini-batches de 64
lot = ordre[i:i + 64]
xarxa.forward(X_tr_e[lot])
xarxa.backward(X_tr_e[lot], y_tr[lot], alpha=0.1)
historial["train"].append(xarxa.perdua(X_tr_e, y_tr))
historial["test"].append(xarxa.perdua(X_te_e, y_te))
pred = (xarxa.forward(X_te_e) > 0.5).astype(int).ravel()
print(f"Exactitud xarxa numpy: {(pred == y_te).mean():.3f}")Vocabulari que ja coneixes de 05-03, ara en el seu hàbitat definitiu:
- Època: una passada completa per l'entrenament. Aquí 300; les xarxes en necessiten moltes.
- Mini-batch: 64 lliuraments per pas de gradient — l'equilibri soroll/cost de 05-03.
- Corba de pèrdua:
historial["train"]ihistorial["test"]són el teu electrocardiograma. Si totes dues baixen, tot bé; si train baixa i test puja, la xarxa ha començat a memoritzar — és el moment de parar (early stopping: la versió temporal de la poda de 05-02).
En aquest problema la xarxa hauria d'igualar o superar lleugerament el random forest de 05-02, capturant l'estructura multiplicativa sense enginyeria de features. I una advertència important: a diferència del MSE lineal de 05-03, la superfície de pèrdua d'una xarxa no és convexa — hi ha mínims locals i altiplans, la inicialització importa (per això trenca la simetria amb valors aleatoris) i dos entrenaments amb llavors diferents poden donar xarxes diferents. El soroll del mini-batch, curiosament, ajuda a escapar dels racons dolents.
Què és el deep learning i quan compensa
Deep learning = xarxes amb moltes capes ocultes, on cada capa aprèn representacions sobre les de l'anterior (píxels → vores → formes → objectes). La profunditat compon transformacions, i això és exponencialment expressiu. Però no és gratis, i per a dades com les de Rutalia rarament és la primera opció:
| Escenari | Dades | Model recomanat | Per què |
|---|---|---|---|
| Tabular petit/mitjà (el nostre dataset) | 10³-10⁵ files | Random forest / gradient boosting / xarxa petita | Els arbres rendeixen igual o millor, sense ajust fi i més barats |
| Tabular massiu amb interaccions complexes | >10⁶ files | Les xarxes mitjanes competeixen | El volum alimenta la capacitat |
| Imatges (paquet danyat a la foto?) | Moltes | CNN | Convolucions: exploten l'estructura espacial |
| Seqüències/sèries (demanda per hora) | Moltes | RNN / transformers | Exploten l'ordre temporal |
| Text (notes d'incidència del repartidor) | Moltes | Transformers | Atenció sobre el context; base dels LLM |
CNN, RNN i transformers són arquitectures especialitzades: la mateixa maquinària (neurones, activacions, backprop, descens de gradient) amb connexions estructurades per a cada tipus de dada. No les desenvolupem aquí — amb el que has après, tens la base exacta sobre la qual es construeixen.
Advertència pràctica de cost: entrenar deep learning seriosament és computacionalment car — hores o dies de GPU, cerca d'hiperparàmetres (arquitectura, α, batch, èpoques...) que multiplica els entrenaments, i factura energètica i econòmica real. La nostra xarxa de 16 neurones entrena en segons en CPU; un model de visió són ordres de magnitud més. Abans de desplegar deep learning a Rutalia, la pregunta obligada és si un random forest, entrenat en minuts i interpretable, no dona ja el 95% del benefici.
Frameworks: què automatitzen PyTorch i Keras
A la pràctica professional ningú no escriu el backward a mà: es fa servir PyTorch o Keras/TensorFlow. El que automatitzen, en ordre d'importància:
- Autograd (diferenciació automàtica): defineixes només el forward; el framework registra el graf d'operacions i genera el backward tot sol. L'autograd fa per tu, per a qualsevol arquitectura, exactament el backprop que acabes d'escriure — per això valia la pena escriure'l una vegada: ara saps què hi ha dins de la caixa.
- GPU: les multiplicacions de matrius es despatxen a maquinari massivament paral·lel canviant una línia (
.to("cuda")). - Optimitzadors avançats: Adam, momentum... — descens de gradient amb learning rate adaptatiu per paràmetre; el bucle és el mateix.
- Capes prefabricades: convolucions, atenció, normalització, dropout — els maons de les arquitectures de la taula anterior.
La nostra xarxa completa, en Keras, perquè vegis la correspondència:
# model = keras.Sequential([
# keras.layers.Dense(16, activation="relu"), # la nostra W1, b1 + ReLU
# keras.layers.Dense(1, activation="sigmoid"), # la nostra W2, b2 + sigmoide
# ])
# model.compile(optimizer="adam", loss="binary_crossentropy")
# model.fit(X_tr_e, y_tr, epochs=300, batch_size=64) # el nostre bucleQuatre línies que encapsulen tot el que hem construït. Fes-les servir — però ara sabent què executen.
Errors Comuns i Consells
- Oblidar les activacions no lineals. Una xarxa profunda sense elles és una regressió lineal cara (ho hem demostrat algebraicament). Si la teva xarxa no supera la lineal, revisa que les activacions hi siguin.
- Inicialitzar els pesos a zero. Totes les neurones d'una capa reben llavors el mateix gradient i aprenen el mateix per sempre (simetria no trencada). Inicialització aleatòria petita, sempre.
- No guardar les activacions del forward. El backward necessita H i Z1; recalcular-les duplica el cost, no tenir-les fa el backprop impossible. És el mateix principi de la memoïtzació de 01-03.
- log(0) a l'entropia creuada. Quan la sigmoide se satura, p arriba a 0.0 o 1.0 en coma flotant i la pèrdua dona NaN.
np.clip(p, 1e-9, 1-1e-9)et salva l'entrenament. - Dades sense estandarditzar. Tot el que hem dit a 05-03 sobre el descens de gradient aplica multiplicat: entrades grans saturen les sigmoides i descompensen els gradients des de la primera època.
- Consell: monitoritza sempre les DUES corbes de pèrdua (train i test). Train baixa + test puja = sobreajustament en directe: para aquí (early stopping). És el diagnòstic més barat i fiable que existeix.
Exercicis
-
El col·lapse lineal, verificat. Modifica
XarxaNeuronalperquè la capa oculta faci servir la identitat en lloc de ReLU (self.H = self.Z1, i al backward elimina la màscara). Entrena les dues versions 300 èpoques i compara l'exactitud en test amb la regressió logística de sklearn. Confirmes que la xarxa "lineal" no supera la logística mentre que la ReLU sí? -
Un pas de backprop a mà. Amb la xarxa mínima del forward manual (pesos donats, x=(1.0, 1.5), y=1) i α=0,5, calcula a mà els 9 gradients, actualitza els paràmetres i torna a fer el forward. Comprova que la nova p és més gran que 0,706 (la xarxa corregeix cap a la classe correcta).
-
Corbes de pèrdua i sobreajustament provocat. Entrena la xarxa amb
d_oculta=128sobre només els 200 primers lliuraments de l'entrenament durant 2000 èpoques, registrant totes dues pèrdues cada 50. Localitza l'època a partir de la qual la pèrdua de test comença a pujar mentre la de train continua baixant, i aplica early stopping: quina exactitud dona la xarxa aturada en aquest punt davant de la xarxa entrenada fins al final?
Solucions
Exercici 1:
La versió identitat s'estanca en una exactitud indistingible de LogisticRegression() — com hem demostrat, W2·(W1·x+b1)+b2 és una funció lineal de x, així que la seva frontera de decisió és un hiperplà, igual que la logística. La versió ReLU la supera en capturar les interaccions (distància×hora, zona congestionada) que l'hiperplà no pot expressar.
Exercici 2:
δ3 = p − y = −0.294
∂L/∂W2 = δ3·h = (−0.485, −0.338) ∂L/∂b2 = −0.294
δ1 = δ3·1.2·1 = −0.353 δ2 = δ3·(−0.7)·1 = 0.206
∂L/∂W1 = [[δ1·x1, δ2·x1], [δ1·x2, δ2·x2]]
= [[−0.353, 0.206], [−0.529, 0.309]]
∂L/∂b1 = (−0.353, 0.206)
Actualització (θ ← θ − 0.5·g):
W2 → (1.443, −0.531) b2 → −0.153
W1 → [[0.976, −0.503], [0.765, 0.745]] b1 → (0.276, 0.097)
Nou forward: z1 = 0.976 + 0.765·1.5 + 0.276 = 2.400 → h1 = 2.400
z2 = −0.503 + 0.745·1.5 + 0.097 = 0.712 → h2 = 0.712
z3 = 1.443·2.400 − 0.531·0.712 − 0.153 = 2.933
p = σ(2.933) ≈ 0.949 > 0.706 ✓Un sol pas i la probabilitat de retard puja de 0,706 a ≈0,95: cada paràmetre s'ha mogut en la direcció exacta que la regla de la cadena li ha assignat. (Amb α=0,5 el pas és gran i didàctic; en entrenament real, passos més petits i molts exemples alhora.)
Exercici 3:
xarxa = XarxaNeuronal(X_tr_e.shape[1], d_oculta=128)
Xp, yp = X_tr_e[:200], y_tr[:200]
for epoca in range(2000):
xarxa.forward(Xp); xarxa.backward(Xp, yp, alpha=0.1)
if epoca % 50 == 0:
print(epoca, round(xarxa.perdua(Xp, yp), 3),
round(xarxa.perdua(X_te_e, y_te), 3))Amb 128 neurones i només 200 exemples, la xarxa té capacitat de sobres per memoritzar: la pèrdua de train baixa cap a 0 indefinidament, mentre que la de test assoleix el seu mínim relativament aviat i després remunta — la corba en U de 05-01, dibuixada ara sobre l'eix del temps d'entrenament. La xarxa aturada al mínim de test (early stopping) dona millor exactitud que l'entrenada 2000 èpoques: entrenar més no és entrenar millor.
Conclusió
Ja no hi ha caixa negra: una xarxa neuronal són regressions logístiques apilades amb activacions no lineals (sense elles, col·lapse algebraic a la lineal de 05-03), entrenades amb el descens de gradient de 05-03 i amb els gradients calculats per retropropagació — la regla de la cadena organitzada com a programació dinàmica sobre el graf de càlcul, propagant la culpa capa a capa cap enrere. L'has calculat a mà, l'has vectoritzat en numpy, l'has entrenat amb mini-batches sobre el dataset canònic i has vist el sobreajustament dibuixar-se a les corbes de pèrdua. També saps quan no fer-la servir: en dades tabulars com les de Rutalia, un random forest sol plantar cara amb una fracció del cost, i les arquitectures profundes (CNN, RNN, transformers) brillen en imatges, seqüències i text, on l'autograd de PyTorch/Keras farà per tu el backward que ara entens per dins. Amb això tanquem l'aprenentatge supervisat. Queda l'última frontera del mòdul: i si no hi ha etiqueta? Ningú no ha marcat quines zones de la ciutat "es comporten igual" — cal descobrir-ho. A 05-05, clustering: aprenentatge no supervisat, on a més tancarem un cercle que vam deixar obert al mòdul 3 amb el MST de Kruskal.
Algorismes Avançats
Mòdul 1: Introducció als Algorismes Avançats
- Conceptes Bàsics i Notació
- Anàlisi de Complexitat
- Recursió i Programació Dinàmica
- Estructures de Dades Avançades
Mòdul 2: Algorismes d'Optimització
- Programació Lineal
- Algorismes d'Optimització Combinatòria
- Backtracking i Branch and Bound
- Algorismes Genètics
- Optimització de Colònia de Formigues
Mòdul 3: Algorismes en Grafs
- Representació de Grafs
- Cerca en Grafs: BFS i DFS
- Algorismes de Camins Mínims
- Arbres d'Expansió Mínima
- Algorismes de Flux Màxim
- Algorismes d'Aparellament en Grafs
Mòdul 4: Algorismes de Cerca i Ordenació
Mòdul 5: Algorismes d'Aprenentatge Automàtic
- Introducció a l'Aprenentatge Automàtic
- Algorismes de Classificació
- Algorismes de Regressió
- Xarxes Neuronals i Deep Learning
- Algorismes de Clustering
Mòdul 6: Casos d'Estudi i Aplicacions
- Optimització a la Indústria
- Aplicacions de Grafs a les Xarxes Socials
- Cerca i Ordenació en Grans Volums de Dades
- Aplicacions d'Aprenentatge Automàtic a la Vida Real
