Vam tancar el mòdul 4 amb una pregunta de la Marta i el Diego: el predictor de devolucions ja està validat (AUC 0,84 amb la regressió logística), però pot un model llegir les ressenyes de ressenyes.csv o mirar les fotos de les incidències? Els algorismes clàssics necessiten que algú converteixi abans el text o la imatge en columnes numèriques amb significat; les xarxes neuronals, en canvi, aprenen elles mateixes aquesta conversió. Aquest mòdul explica com. En aquesta primera lliçó comencem per la peça bàsica: la neurona artificial, que resultarà ser una cosa que ja coneixes (la regressió logística de 04-04, vista amb uns altres ulls); el perceptró de Rosenblatt de 1958, que implementarem des de zero en numpy i entrenarem sobre les portes lògiques i sobre un problema de NovaMarket; el famós problema XOR, que una sola neurona no pot resoldre i que va contribuir a provocar el primer hivern de la IA (01-01); i la solució, apilar neurones en capes, que és el que anomenem xarxa neuronal. Acabarem veient, sense entrenar-la encara, com s'escriu aquesta mateixa xarxa en PyTorch. És important perquè tot el deep learning és aquesta idea repetida a escala: entendre bé una neurona i per què calen capes és entendre el 80 % del que ve després.

Contingut

  1. De la neurona biològica a la neurona artificial
  2. Anatomia d'una neurona artificial: entrades, pesos, biaix, suma i activació
  3. La regressió logística de 04-04 era una neurona
  4. El perceptró de Rosenblatt i la seva regla d'aprenentatge
  5. El perceptró en numpy: portes lògiques i comandes urgents de NovaMarket
  6. El problema XOR: el límit d'una neurona i la solució amb dues capes
  7. Què és una xarxa neuronal: capes d'entrada, ocultes i de sortida
  8. L'aproximador universal i la comparació model lineal vs xarxa
  9. Un cop d'ull a la destinació: la mateixa xarxa en PyTorch
  10. Errors Comuns i Consells
  11. Exercicis
  12. Conclusió

  1. De la neurona biològica a la neurona artificial

Una neurona biològica rep senyals d'altres neurones a través de les dendrites, les integra al cos cel·lular i, si el senyal acumulat supera un llindar, emet un impuls per l'axó cap a les següents. Les connexions (sinapsis) no són totes iguals: unes exciten i d'altres inhibeixen, i la seva força canvia amb l'experiència. El cervell humà té uns 86.000 milions de neurones connectades de manera massivament paral·lela.

L'any 1943, McCulloch i Pitts van proposar un model matemàtic mínim d'aquesta neurona: entrades binàries, una suma i un llindar. És la inspiració de tot el que segueix, però convé ser honest amb l'analogia:

Neurona biològica Neurona artificial Comentari
Les dendrites reben senyals Vector d'entrades x Anàleg raonable
Força de la sinapsi Pes w Anàleg raonable: és el que "s'aprèn"
Llindar de dispar Biaix b i funció d'activació Anàleg raonable
Impulsos elèctrics en el temps, química, plasticitat Un nombre real calculat de cop No hi ha analogia: la neurona artificial és infinitament més simple
Aprenentatge local, sense supervisor Descens del gradient amb etiquetes (05-03) El cervell no fa retropropagació

Així doncs, "xarxa neuronal" és un nom històric, no una afirmació que estiguem imitant el cervell. El que de debò importa és la matemàtica: una funció molt flexible construïda apilant peces molt simples.

  1. Anatomia d'una neurona artificial: entrades, pesos, biaix, suma i activació

Una neurona artificial rep n entrades numèriques x₁, ..., xₙ i produeix una sortida en tres passos:

  1. Suma ponderada: multiplica cada entrada pel seu pes i hi suma un biaix: z = w₁·x₁ + w₂·x₂ + ... + wₙ·xₙ + b. En notació vectorial, z = w · x + b. Els pesos diuen quant i en quin sentit influeix cada entrada; el biaix desplaça el punt en què la neurona "s'activa" (sense ell, la frontera de decisió passaria obligatòriament per l'origen).
  2. Funció d'activació: transforma z en la sortida a = f(z). Les dues històriques són:
    • Esglaó (Heaviside): f(z) = 1 si z > 0, 0 altrament. La neurona "dispara" o no. És la de McCulloch-Pitts i del perceptró.
    • Sigmoide: σ(z) = 1 / (1 + e^(−z)). Versió suau de l'esglaó: dona un nombre entre 0 i 1 interpretable com a probabilitat i, com que no té salts, permetrà calcular pendents (05-03).
  3. Sortida: a s'utilitza com a predicció o es passa a altres neurones.
flowchart LR
    x1["x₁"] -->|w₁| S(("Σ + b"))
    x2["x₂"] -->|w₂| S
    x3["x₃"] -->|w₃| S
    S -->|z| F["f(z)"]
    F --> a["a = sortida"]

Exemple numèric amb tres entrades: x = (2, 0, 1), pesos w = (0,5; −1; 0,8), biaix b = −1. Aleshores z = 0,5·2 + (−1)·0 + 0,8·1 − 1 = 0,8. Amb esglaó la sortida és 1 (perquè 0,8 > 0); amb sigmoide, σ(0,8) = 1 / (1 + e^(−0,8)) ≈ 0,69.

Tot el que una neurona sola pot fer és traçar una frontera lineal: en dues dimensions, una recta (w₁x₁ + w₂x₂ + b = 0); en tres, un pla; en 21, un hiperplà. A un costat de la frontera diu "1", a l'altre "0". Guarda't aquesta idea: és la clau del problema XOR.

  1. La regressió logística de 04-04 era una neurona

Rellegeix la definició de la regressió logística a 04-04: "calcula la suma ponderada z = a + b₁·x₁ + ... + bₙ·xₙ i la passa per la sigmoide". És exactament una neurona amb activació sigmoide; només canvia la notació (els coeficients ara s'anomenen pesos, l'ordenada s'anomena biaix). Comprovem-ho amb els coeficients que vam obtenir aleshores per al predictor de devolucions amb quatre columnes:

import numpy as np

def esglao(z):
    return np.where(z > 0, 1, 0)

def sigmoide(z):
    return 1 / (1 + np.exp(-z))

# Comanda de 04-04: 250 €, 1 article, 5 dies de lliurament, client nou
x = np.array([250.0, 1, 5, 1])
# Coeficients que va aprendre LogisticRegression a 04-04 (ara els anomenem pesos)
w = np.array([0.012, -0.2012, 0.3069, 1.8821])
b = -4.906                                    # l'ordenada es el biaix

z = w @ x + b                                 # suma ponderada (producte escalar + biaix)
print("z =", round(z, 3), " sigmoide =", round(sigmoide(z), 3), " esglao =", esglao(z))

Sortida:

z = 1.309  sigmoide = 0.787  esglao = 1

El mateix 0,79 (amb la diferència d'arrodoniment dels coeficients) que vam calcular aleshores amb predict_proba. Conclusió important: ja has entrenat una neurona al mòdul 4, i el fit de LogisticRegression va fer el que farà l'entrenament d'una xarxa: buscar els pesos que minimitzen una pèrdua. La diferència entre "una neurona" i "una xarxa" no és a la peça sinó en quantes peces hi ha i com es connecten.

  1. El perceptró de Rosenblatt i la seva regla d'aprenentatge

L'any 1958 Frank Rosenblatt va construir el perceptró: una neurona amb activació esglaó i, sobretot, la primera regla d'aprenentatge que ajustava els pesos a partir d'exemples (01-01). La regla és d'una senzillesa sorprenent. Per a cada exemple (x, y) del conjunt d'entrenament:

  1. Calcula la predicció ŷ = esglaó(w · x + b).
  2. Calcula l'error e = y − ŷ (val 0 si encerta, +1 si havia de dir 1 i ha dit 0, −1 a l'inrevés).
  3. Actualitza: w ← w + η · e · x i b ← b + η · e, on η (eta) és la taxa d'aprenentatge, un nombre petit com ara 0,1.

Llegeix-ho amb calma: si encerta no toca res; si havia de donar 1 i ha donat 0, empeny els pesos en la direcció de x (perquè la pròxima vegada z sigui més gran); si havia de donar 0 i ha donat 1, els empeny en la direcció contrària. Es recorre el conjunt diverses vegades (cada passada completa s'anomena època) fins que no quedi cap error. Rosenblatt va demostrar el teorema de convergència del perceptró: si les dades són linealment separables (existeix una recta o hiperplà que separa les classes sense error), la regla en troba un en un nombre finit de passos. Aquest "si" és la trampa que veurem a la secció 6.

  1. El perceptró en numpy: portes lògiques i comandes urgents de NovaMarket

5.1 Implementació

import numpy as np

def entrenar_perceptro(X, y, taxa=0.1, epoques=10):
    """Regla de Rosenblatt. Retorna pesos, biaix i errors comesos a cada epoca."""
    w = np.zeros(X.shape[1])          # un pes per columna, comencem a zero
    b = 0.0
    historial = []
    for ep in range(epoques):
        errors = 0
        for xi, yi in zip(X, y):      # exemple a exemple
            pred = 1 if xi @ w + b > 0 else 0
            error = yi - pred          # 0, +1 o -1
            if error != 0:
                w = w + taxa * error * xi
                b = b + taxa * error
                errors += 1
        historial.append(errors)
        if errors == 0:                # una epoca sense errors: hem convergit
            break
    return w, b, historial

Explicació línia a línia: X és una matriu amb un exemple per fila; w comença a zeros; a cada època recorrem els exemples, calculem z = xi @ w + b (@ és el producte escalar de numpy) i apliquem l'esglaó (> 0); si hi ha error, corregim pesos i biaix amb la regla; desem quants errors hi ha hagut a l'època i parem tan bon punt una època acaba neta.

5.2 Portes lògiques AND i OR

Les portes lògiques són el "hola món" històric de les neurones: dues entrades binàries i una sortida.

X_portes = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y_and = np.array([0, 0, 0, 1])
y_or  = np.array([0, 1, 1, 1])

for nom, y in [("AND", y_and), ("OR", y_or)]:
    w, b, hist = entrenar_perceptro(X_portes, y, taxa=0.1, epoques=20)
    pred = esglao(X_portes @ w + b)
    print(f"{nom}: w={w.round(2)} b={b:.2f} errors per epoca={hist} prediccio={pred}")

Sortida:

AND: w=[0.2 0.1] b=-0.20 errors per epoca=[1, 3, 3, 2, 1, 0] prediccio=[0 0 0 1]
OR: w=[0.1 0.1] b=0.00 errors per epoca=[1, 2, 1, 0] prediccio=[0 1 1 1]

Seguim a mà la traça de l'AND per veure la regla en acció (només es mostren els passos en què hi ha hagut error; η = 0,1):

Època Entrada x y z = w·x + b Predicció Error w després d'actualitzar b després d'actualitzar
1 (1, 1) 1 0,0 0 +1 (0,1; 0,1) 0,1
2 (0, 0) 0 0,1 1 −1 (0,1; 0,1) 0,0
2 (0, 1) 0 0,1 1 −1 (0,1; 0,0) −0,1
2 (1, 1) 1 0,0 0 +1 (0,2; 0,1) 0,0
3 (0, 1) 0 0,1 1 −1 (0,2; 0,0) −0,1
3 (1, 0) 0 0,1 1 −1 (0,1; 0,0) −0,2
3 (1, 1) 1 −0,1 0 +1 (0,2; 0,1) −0,1
4 (1, 0) 0 0,1 1 −1 (0,1; 0,1) −0,2
4 (1, 1) 1 0,0 0 +1 (0,2; 0,2) −0,1
5 (0, 1) 0 0,1 1 −1 (0,2; 0,1) −0,2
6 (totes) 0 (0,2; 0,1) −0,2

Amb w = (0,2; 0,1) i b = −0,2, la frontera és la recta 0,2·x₁ + 0,1·x₂ − 0,2 = 0: només el punt (1, 1) queda al costat positiu (z = 0,1), els altres tres donen z ≤ 0. Fixa't que la solució no és única (w = (1, 1), b = −1,5 també serveix): el perceptró troba una recta separadora, no la millor.

5.3 Comandes urgents de NovaMarket

Un cas menys de joguina. Al magatzem de Zaragoza, una comanda es considera urgent si cal treure-la en el torn següent; depèn de les hores que falten fins al compromís de lliurament i de la distància fins a la destinació (com més lluny, abans cal sortir). Simulem 200 comandes amb la regla oculta "urgent si hores < 12 + distancia/10", que és lineal, i comprovem que el perceptró la descobreix:

rng = np.random.default_rng(42)
n = 200
hores = rng.uniform(2, 72, n)          # hores fins al compromis de lliurament
distancia = rng.uniform(5, 400, n)     # km des del magatzem de Zaragoza
urgent = (hores < 12 + distancia / 10).astype(int)   # regla oculta (lineal)
X = np.column_stack([hores, distancia])
print("Proporcio d'urgents:", urgent.mean())

X_esc = (X - X.mean(axis=0)) / X.std(axis=0)   # estandarditzar, com a 04-03
w, b, hist = entrenar_perceptro(X_esc, urgent, taxa=0.1, epoques=100)
print("w =", w.round(3), " b =", round(b, 3), " epoques:", len(hist), " errors/epoca:", hist)
print("Encert:", (esglao(X_esc @ w + b) == urgent).mean())

# On es la frontera en unitats originals? Aillem les hores per a tres distancies
mh, md = X.mean(axis=0); sh, sd = X.std(axis=0)
for d in (50, 200, 350):
    h_lim = mh - sh / w[0] * (w[1] * (d - md) / sd + b)
    print(f"distancia {d} km -> urgent si falten menys de {h_lim:.1f} h (regla real: {12 + d/10:.1f} h)")

Sortida:

Proporcio d'urgents: 0.475
w = [-0.862  0.479]  b = -0.2  epoques: 11  errors/epoca: [16, 9, 9, 3, 8, 6, 6, 5, 6, 4, 0]
Encert: 1.0
distancia 50 km -> urgent si falten menys de 17.3 h (regla real: 17.0 h)
distancia 200 km -> urgent si falten menys de 31.8 h (regla real: 32.0 h)
distancia 350 km -> urgent si falten menys de 46.3 h (regla real: 47.0 h)

Lectura: en 11 èpoques el perceptró classifica les 200 comandes sense error, i la recta que ha trobat coincideix gairebé exactament amb la regla oculta. El pes de hores és negatiu (més hores, menys urgent) i el de distancia positiu, com era d'esperar. Dues observacions pràctiques: (1) el nombre d'errors per època no baixa de manera monòtona (16, 9, 9, 3, 8...): la regla corregeix un exemple i en desajusta d'altres; només garanteix que al final convergeix; (2) hem estandarditzat les entrades: sense escalar, amb taxa=0.01, després de 200 èpoques continuava cometent uns 16 errors per època, perquè un pas de 0,1 en el pes de distancia (que va de 5 a 400) mou la frontera moltíssim més que el mateix pas en hores. La lliçó de 04-03 (escalar) és encara més important en xarxes.

  1. El problema XOR: el límit d'una neurona i la solució amb dues capes

Provem ara la porta XOR (o exclusiu: 1 si les entrades són diferents):

y_xor = np.array([0, 1, 1, 0])
w, b, hist = entrenar_perceptro(X_portes, y_xor, taxa=0.1, epoques=20)
print("XOR: errors per epoca =", hist)
print("Prediccio:", esglao(X_portes @ w + b))

Sortida:

XOR: errors per epoca = [2, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
Prediccio: [1 1 0 0]

No convergeix mai. No és cap error del codi: dibuixa els quatre punts al pla; els que valen 1, (0,1) i (1,0), són a cantonades oposades, i els que valen 0, (0,0) i (1,1), a les altres dues. No existeix cap recta que deixi els dos uns a un costat i els dos zeros a l'altre. Com que una neurona només sap traçar rectes, XOR queda fora del seu abast. Això és el que Minsky i Papert van demostrar amb rigor a Perceptrons (1969) i, combinat amb la manca d'un mètode per entrenar diverses capes, va congelar la recerca en xarxes durant quinze anys (01-01).

La solució es coneixia en teoria: encadenar neurones. XOR es pot escriure com "(x₁ OR x₂) AND NOT (x₁ AND x₂)". Cada peça és lineal, així que dues neurones en una primera capa (una calcula OR, l'altra AND) i una tercera que les combina ho resolen:

x₁ x₂ h₁ = esglaó(x₁ + x₂ − 0,5) (OR) h₂ = esglaó(x₁ + x₂ − 1,5) (AND) y = esglaó(h₁ − h₂ − 0,5) XOR real
0 0 esglaó(−0,5) = 0 esglaó(−1,5) = 0 esglaó(−0,5) = 0 0
0 1 esglaó(0,5) = 1 esglaó(−0,5) = 0 esglaó(0,5) = 1 1
1 0 esglaó(0,5) = 1 esglaó(−0,5) = 0 esglaó(0,5) = 1 1
1 1 esglaó(1,5) = 1 esglaó(0,5) = 1 esglaó(−0,5) = 0 0
def xarxa_xor(x1, x2):
    h1 = esglao(x1 + x2 - 0.5)       # neurona OR   (pesos 1,1; biaix -0,5)
    h2 = esglao(x1 + x2 - 1.5)       # neurona AND  (pesos 1,1; biaix -1,5)
    return esglao(h1 - h2 - 0.5)     # neurona de sortida: OR i no AND (pesos 1,-1; biaix -0,5)

for x1 in (0, 1):
    for x2 in (0, 1):
        print(x1, x2, "->", xarxa_xor(x1, x2))

Sortida: 0 0 -> 0, 0 1 -> 1, 1 0 -> 1, 1 1 -> 0. La clau conceptual: la primera capa transforma les entrades en un nou espai (h₁, h₂) en què el problema que és linealment separable (els punts passen a ser (0,0)→0, (1,0)→1, (1,0)→1, (1,1)→0, i una recta els separa). Això és el que farà cada capa oculta d'una xarxa: reescriure les dades fins que l'última neurona pugui decidir amb una recta. El que l'any 1969 no se sabia era com trobar aquests pesos automàticament en lloc de fer-ho a mà; la resposta, la retropropagació, va arribar als anys 80 i la veurem a 05-03.

  1. Què és una xarxa neuronal: capes d'entrada, ocultes i de sortida

Una xarxa neuronal (en la seva forma bàsica, perceptró multicapa o MLP) és un conjunt de neurones organitzades en capes, on la sortida de cada capa és l'entrada de la següent:

  • Capa d'entrada: no calcula res; són les característiques (les 21 columnes del predictor de devolucions, els 256 píxels d'una foto de 16×16, etc.).
  • Capes ocultes: una o més capes de neurones, cadascuna connectada a totes les sortides de la capa anterior (per això s'anomenen "denses" o "totalment connectades"). Aprenen representacions intermèdies, com h₁ i h₂ en el XOR.
  • Capa de sortida: produeix la predicció; una neurona amb sigmoide per a classificació binària, tantes com classes per a multiclasse, una de lineal per a regressió (els detalls a 05-02).
flowchart LR
    subgraph E["Capa d'entrada"]
        x1["x₁"]; x2["x₂"]; x3["x₃"]
    end
    subgraph H["Capa oculta"]
        h1["h₁"]; h2["h₂"]; h3["h₃"]; h4["h₄"]
    end
    subgraph S["Capa de sortida"]
        y["ŷ"]
    end
    x1 --> h1 & h2 & h3 & h4
    x2 --> h1 & h2 & h3 & h4
    x3 --> h1 & h2 & h3 & h4
    h1 & h2 & h3 & h4 --> y

Els paràmetres de la xarxa són tots els pesos i biaixos: al diagrama, 3×4 + 4 = 16 a la capa oculta i 4×1 + 1 = 5 a la de sortida, 21 en total. Els hiperparàmetres (04-01) són les decisions de disseny: quantes capes, quantes neurones, quina activació. Un detall essencial: si les capes ocultes no tinguessin funció d'activació (o fos lineal), apilar capes no serviria de res, perquè una composició de funcions lineals és una altra funció lineal i tornaríem a una sola recta. La no linealitat de l'activació és el que dona poder a les capes.

  1. L'aproximador universal i la comparació model lineal vs xarxa

Un resultat teòric de finals dels 80 (Cybenko, Hornik) diu que una xarxa amb una sola capa oculta prou ampla i activació no lineal pot aproximar qualsevol funció contínua raonable amb la precisió que es vulgui. És el teorema d'aproximació universal. Intuïció: cada neurona oculta amb sigmoide és un "esglaó suau" col·locat en algun lloc de l'espai d'entrada; sumant molts esglaons de diferents alçades i posicions es pot dibuixar qualsevol corba, igual que amb prou blocs rectangulars s'aproxima qualsevol silueta. Dos matisos importants que el teorema no diu: no diu quantes neurones calen (pot ser un nombre enorme) ni com trobar els pesos (això és l'entrenament). A la pràctica, xarxes més profundes (diverses capes) aproximen el mateix amb moltes menys neurones que una única capa molt ampla, i d'aquí ve el "deep" de deep learning (05-04).

Aspecte Model lineal (regressió logística) Xarxa neuronal (MLP)
Frontera de decisió Un hiperplà Qualsevol forma (corbes, regions desconnectades)
Característiques Les que li donis; les interaccions cal crear-les a mà (04-03) Les capes ocultes aprenen combinacions i interaccions
Paràmetres Un per columna + biaix (22 al predictor de devolucions) Centenars, milers o milions
Interpretabilitat Alta: un coeficient per variable Baixa: els pesos ocults no tenen significat directe
Dades necessàries Poques Moltes (més paràmetres → més risc de sobreajust, 04-06)
Entrenament Ràpid, òptim únic (pèrdua convexa) Més lent, molts òptims locals, requereix cura (05-03)
On brilla Dades tabulars, poques dades, necessitat d'explicar Text, imatges, àudio, senyals; dades abundants

El Diego, llegint la taula, fa la pregunta correcta: "aleshores per a les devolucions continuem amb la logística?". Probablement sí, i a 05-03 ho comprovarem amb números; la xarxa es justifica en els problemes on la logística no pot ni començar: les ressenyes i les fotos.

  1. Un cop d'ull a la destinació: la mateixa xarxa en PyTorch

Perquè vegis cap on anem, així es defineix en PyTorch la xarxa de dues capes que resol XOR (dues entrades → dues neurones ocultes → una sortida), amb sigmoide en lloc d'esglaó perquè a 05-03 es pugui entrenar:

import torch
import torch.nn as nn

torch.manual_seed(0)
xarxa = nn.Sequential(
    nn.Linear(2, 2),      # capa oculta: 2 entrades -> 2 neurones (pesos 2x2 + 2 biaixos)
    nn.Sigmoid(),         # activacio de la capa oculta
    nn.Linear(2, 1),      # capa de sortida: 2 -> 1 (pesos 1x2 + 1 biaix)
    nn.Sigmoid())         # sortida entre 0 i 1
print(xarxa)
print("Parametres:", sum(p.numel() for p in xarxa.parameters()))

# Sense entrenar, PyTorch ha posat pesos aleatoris. Copiem a ma la solucio de la seccio 6,
# multiplicada per 20 perque la sigmoide es comporti gairebe com un esglao:
with torch.no_grad():
    xarxa[0].weight[:] = 20 * torch.tensor([[1.0, 1.0], [1.0, 1.0]])   # OR i AND
    xarxa[0].bias[:]   = 20 * torch.tensor([-0.5, -1.5])
    xarxa[2].weight[:] = 20 * torch.tensor([[1.0, -1.0]])              # OR i no AND
    xarxa[2].bias[:]   = 20 * torch.tensor([-0.5])

X_t = torch.tensor(X_portes, dtype=torch.float32)
print(xarxa(X_t).detach().numpy().round(3).ravel())

Sortida:

Sequential(
  (0): Linear(in_features=2, out_features=2, bias=True)
  (1): Sigmoid()
  (2): Linear(in_features=2, out_features=1, bias=True)
  (3): Sigmoid()
)
Parametres: 9
[0. 1. 1. 0.]

nn.Sequential encadena capes; nn.Linear(2, 2) és una capa densa que calcula z = W·x + b per a 2 neurones (4 pesos + 2 biaixos); nn.Sigmoid() aplica l'activació; en total 9 paràmetres (4 + 2 + 2 + 1). Amb els pesos posats a mà, la xarxa reprodueix XOR. El que no hem fet, i és l'objectiu de la resta del mòdul, és que la xarxa trobi tota sola aquests pesos: això exigeix triar bé l'arquitectura i les activacions (05-02) i entrenar amb descens del gradient i retropropagació (05-03).

Errors Comuns i Consells

  • Creure que "xarxa neuronal" implica imitar el cervell. És una funció matemàtica flexible amb un nom històric. Evita explicar-li-ho així al Diego: genera expectatives equivocades (recorda la sobreexpectativa de 01-01).
  • Oblidar el biaix. Sense b, la frontera passa per l'origen i molts problemes trivials (com AND) no es poden resoldre. En PyTorch nn.Linear l'inclou per defecte (bias=True).
  • Entrenar el perceptró sense escalar les entrades. Com hem vist amb distancia (5-400) davant de hores (2-72), la convergència se'n ressent o no arriba. Estandarditza sempre.
  • Esperar que el perceptró convergeixi amb dades no separables. Si hi ha solapament entre classes (el que és normal en dades reals, com les devolucions), la regla oscil·la per sempre. Per això es va abandonar l'esglaó a favor d'activacions suaus i d'una pèrdua que es minimitza (05-03).
  • Apilar capes sense activació no lineal. És un error clàssic quan es comença amb PyTorch: nn.Sequential(nn.Linear(2, 2), nn.Linear(2, 1)) és matemàticament una sola neurona lineal i no resol XOR per moltes capes que hi afegeixis.
  • Confondre paràmetres amb hiperparàmetres. Els pesos i biaixos els aprèn la xarxa; el nombre de capes i neurones el tries tu (i s'ajusta amb validació, 04-06).

Exercicis

Exercici 1. Entrena el perceptró amb entrenar_perceptro sobre la porta NAND (y = [1, 1, 1, 0]). Després construeix XOR sense capa AND, fent servir la identitat XOR = (x₁ OR x₂) AND (x₁ NAND x₂): escriu una funció xarxa_xor_2(x1, x2) amb les neurones OR i NAND a la primera capa i una neurona AND a la sortida (pesos i biaixos a mà) i comprova la taula de veritat.

Exercici 2. Canvia la regla oculta de les comandes urgents per una de no lineal: urgent = ((hores < 24) != (distancia > 200)).astype(int) (un XOR "continu": urgent si falta poc temps o és lluny, però no totes dues coses). Entrena el perceptró 100 èpoques sobre les entrades estandarditzades. Convergeix? Quin encert màxim assoleix? Explica amb la secció 6 per què.

Exercici 3. Una xarxa densa té 21 entrades, una capa oculta de 8 neurones i una sortida. (a) Calcula a mà quants paràmetres té. (b) Explica per què, si la capa oculta no tingués activació, la xarxa equivaldria a una regressió logística de 22 paràmetres. (c) Comprova (a) definint-la amb nn.Sequential i sum(p.numel() for p in xarxa.parameters()).

Solucions

Solució 1. El perceptró aprèn NAND en 4 èpoques (amb taxa=0.1 obté w = (−0,2; −0,1), b = 0,2, que dona z = 0,2; 0,1; 0,0; −0,11, 1, 1, 0). La xarxa:

def xarxa_xor_2(x1, x2):
    h1 = esglao(x1 + x2 - 0.5)           # OR
    h2 = esglao(-x1 - x2 + 1.5)          # NAND: 1 llevat de quan totes dues son 1
    return esglao(h1 + h2 - 1.5)         # AND de les dues ocultes

Taula: (0,0) → h = (0, 1) → 0; (0,1) → (1, 1) → 1; (1,0) → (1, 1) → 1; (1,1) → (1, 0) → 0. Qualsevol descomposició de XOR en funcions lineals encadenades serveix; el que no serveix és cap funció lineal sola.

Solució 2. No convergeix: després de 100 èpoques continua cometent entre 75 i 90 errors per època (de 200) i l'encert final oscil·la al voltant del 40-60 % segons a quina època paris, és a dir, no millor que tirar una moneda (els urgents són el 50,5 %). La raó és la de la secció 6: les quatre "cantonades" (poc temps/a prop → 0, poc temps/lluny → 1, molt temps/a prop → 1, molt temps/lluny → 0) formen un XOR i cap recta separa les dues regions d'urgents de les dues de no urgents. Caldria una capa oculta que, com en el XOR, transformés primer les entrades (per exemple, dues neurones que detectessin "poc temps" i "lluny") i una sortida que les combinés.

Solució 3. (a) Capa oculta: 21 × 8 pesos + 8 biaixos = 176; sortida: 8 × 1 + 1 = 9; total 185. (b) Sense activació, la sortida seria W₂·(W₁·x + b₁) + b₂ = (W₂·W₁)·x + (W₂·b₁ + b₂), és a dir, una única suma ponderada de les 21 entrades amb un biaix: 21 pesos efectius + 1 biaix, seguida de la sigmoide final. Els 185 paràmetres estarien "malbaratats" representant una funció que només té 22 graus de llibertat. (c) nn.Sequential(nn.Linear(21, 8), nn.Sigmoid(), nn.Linear(8, 1), nn.Sigmoid()) dona 185. Aquesta serà, amb més neurones i una altra activació, la xarxa que construirem a 05-02 per al predictor de devolucions.

Conclusió

En aquesta lliçó hem après que la neurona artificial és una suma ponderada més un biaix passada per una funció d'activació (esglaó o sigmoide), que només sap traçar fronteres lineals, i que la regressió logística de 04-04 ja era una neurona amb sigmoide: la diferència entre el mòdul 4 i aquest no és a la peça sinó en quantes se n'apilen. Hem implementat el perceptró de Rosenblatt en numpy amb la seva regla w ← w + η·e·x, l'hem vist convergir en AND, OR i en les comandes urgents de NovaMarket (recuperant la regla oculta gairebé exacta), i fracassar en XOR, el límit que Minsky i Papert van assenyalar l'any 1969. La sortida és apilar neurones en capes: la capa oculta transforma les entrades en un espai on el problema sí que és separable, i per això una xarxa amb activacions no lineals és un aproximador universal. Hem tancat amb la mateixa xarxa escrita en PyTorch amb nn.Sequential, amb els pesos posats a mà.

Queden dues preguntes que aquest mòdul respondrà. Primera: com es dissenya una xarxa real, amb quantes capes i neurones, quines activacions i quina sortida segons el problema? És el tema de la lliçó següent, Arquitectura de Xarxes Neuronals, on construirem en PyTorch l'MLP del predictor de devolucions sobre les 21 columnes del mòdul 4 i en comptarem els paràmetres. Segona: com troba la xarxa els seus pesos sense que ningú els hi escrigui a mà? Aquesta és la de 05-03.

Fonaments d'Intel·ligència Artificial (IA)

Mòdul 1: Introducció a la Intel·ligència Artificial

Mòdul 2: Principis Bàsics de la IA

Mòdul 3: Algorismes en IA

Mòdul 4: Aprenentatge Automàtic (Machine Learning)

Mòdul 5: Xarxes Neuronals i Deep Learning

Mòdul 6: Lògica i Sistemes Experts

Mòdul 7: Eines i Llenguatges de Programació en IA

Mòdul 8: Projectes i Casos d'Estudi

Mòdul 9: Exercicis i Pràctiques

Mòdul 10: Recursos Addicionals

© Copyright 2026. Tots els drets reservats