La xarxa de 05-02 té 497 paràmetres aleatoris i diu "0,55" a totes les comandes. Aquesta lliçó explica com aquests nombres passen d'aleatoris a útils, és a dir, com aprèn una xarxa. La idea la vam anunciar en tancar 03-04: aprendre és optimitzar, i quan la funció de pèrdua és suau no cal tantejar veïns a l'atzar com a l'ascens de turó, perquè podem calcular el pendent i baixar-hi. Veurem la pèrdua com un paisatge, la derivada com a pendent, el descens del gradient pas a pas amb un exemple d'una variable resolt a mà, el paper de la taxa d'aprenentatge, la regla de la cadena que permet repartir l'error cap enrere per les capes (la retropropagació), les variants per lots i minilots, els optimitzadors (SGD amb moment, Adam) i, per fi, les tècniques de regularització pròpies de les xarxes que vam deixar apuntades a 04-06: aturada anticipada, dropout, augment de dades i normalització per lots. En codi: un descens del gradient des de zero en numpy per a una neurona i el bucle d'entrenament complet en PyTorch de l'MLP de NovaMarket, avaluat amb AUC davant de la logística de 04-05. És important perquè aquest algorisme és el que ho entrena tot, des de la nostra xarxa de 497 paràmetres fins als grans models de llenguatge de 05-05.

Contingut

  1. La pèrdua com a paisatge: de l'ascens de turó a la brúixola
  2. La derivada com a pendent i la intuïció del gradient
  3. Descens del gradient pas a pas: un exemple d'una variable a mà
  4. La taxa d'aprenentatge: massa gran, massa petita
  5. Regla de la cadena i retropropagació
  6. Lots, minilots, èpoques i optimitzadors (SGD, moment, Adam)
  7. Regularització pròpia de les xarxes: aturada anticipada, dropout, augment de dades i normalització per lots
  8. Codi (a): descens del gradient des de zero en numpy
  9. Codi (b): entrenament complet de l'MLP de NovaMarket en PyTorch
  10. Errors Comuns i Consells
  11. Exercicis
  12. Conclusió

  1. La pèrdua com a paisatge: de l'ascens de turó a la brúixola

Recorda 04-01: la funció de pèrdua mesura, amb un nombre, quant s'equivoca el model sobre les dades d'entrenament amb els seus paràmetres actuals. Per a la neurona sigmoide amb entropia creuada binària (05-02), la pèrdua depèn dels pesos i del biaix: canvia els pesos i canvia la pèrdua. Imagina un paisatge en què cada punt del terra és una combinació de paràmetres i l'altura és la pèrdua: entrenar és baixar fins a la vall més fonda. Amb 2 paràmetres el paisatge es pot dibuixar; amb 497 no, però la geometria és la mateixa.

A 03-04 baixàvem "a cegues": l'ascens de turó provava veïns a l'atzar i es movia si milloraven, i el recuit simulat acceptava de vegades empitjorar. Funcionava per a rutes de repartiment, on no hi ha un pendent definit (permutar dues parades no és "una mica cap a l'esquerra"). Amb paràmetres continus i una pèrdua suau tenim una cosa millor: una brúixola que assenyala costa avall, el gradient. No cal provar veïns: es calcula la direcció de màxim descens i es fa un pas. Repetit milers de vegades, aquest pas és el descens del gradient.

  1. La derivada com a pendent i la intuïció del gradient

La derivada d'una funció en un punt és el seu pendent allà: quant puja la funció per cada unitat que avances. Si f(w) = (w − 3)² + 1 (una paràbola amb el mínim a w = 3), la seva derivada és f'(w) = 2·(w − 3). A w = 0 val −6 (la funció baixa dreta cap a la dreta); a w = 2,5 val −1 (baixa suau); a w = 3 val 0 (pla: el mínim); a w = 5 val +4 (puja). No cal que sàpigues calcular derivades per a aquest curs: n'hi ha prou amb la idea de "pendent en el punt" i amb saber que PyTorch les calcula per tu (secció 5).

Amb diversos paràmetres, el gradient és el vector de derivades parcials, una per paràmetre: el pendent de la pèrdua "si moc només w₁", "si moc només w₂", etc. Apunta en la direcció en què la pèrdua puja més de pressa; el seu oposat, costa avall. La regla del descens del gradient per a cada paràmetre θ és:

θ ← θ − η · ∂L/∂θ

és a dir, "resta a cada paràmetre el seu pendent multiplicat per la taxa d'aprenentatge η". Si el pendent és negatiu (la pèrdua baixa en augmentar θ), la resta l'augmenta; si és positiu, el disminueix. Compara-la amb la regla del perceptró de 05-01, w ← w + η·e·x: té la mateixa forma, i de fet veurem que la regla del perceptró n'és un cas particular.

  1. Descens del gradient pas a pas: un exemple d'una variable a mà

Minimitzem f(w) = (w − 3)² + 1 des de w = 0 amb η = 0,1. A cada iteració: calculem el pendent 2(w − 3), i actualitzem w ← w − 0,1 · pendent.

Iteració w actual f(w) Pendent 2(w − 3) Pas −0,1 · pendent w nou
1 0 10 −6 +0,6 0,6
2 0,6 6,76 −4,8 +0,48 1,08
3 1,08 4,686 −3,84 +0,384 1,464
4 1,464 3,359 −3,072 +0,307 1,771
5 1,771 2,510 −2,458 +0,246 2,017
6 2,017 1,966 −1,966 +0,197 2,214

Tres coses que es veuen a la taula i que valen per a qualsevol xarxa: (1) la pèrdua baixa a cada pas (10 → 6,76 → 4,69 → ...); (2) els passos són grans lluny del mínim i petits a prop, perquè el pendent se suavitza en acostar-se a la vall, sense que ningú canviï η; (3) no arriba exactament al mínim (w = 3) sinó que s'hi acosta indefinidament: a la pràctica s'atura quan la millora és menyspreable o, millor, quan ho diu la validació (secció 7). En una xarxa l'única diferència és que en comptes d'un pendent n'hi ha 497 (un per paràmetre) i que la funció no és una paràbola sinó un paisatge amb moltes valls; l'algorisme baixa a la vall on cau, que pot no ser la més fonda, exactament el problema dels òptims locals de 03-04.

  1. La taxa d'aprenentatge: massa gran, massa petita

η és l'hiperparàmetre més important de l'entrenament. Amb la mateixa paràbola i el mateix punt de partida:

η w després d'1, 2, 3, 4, 5 iteracions Comportament
0,01 0,06; 0,119; 0,176; 0,233; 0,288 Massa petita: baixa, però després de 5 passos amb prou feines ha recorregut el 10 % del camí; en caldrien centenars
0,1 0,6; 1,08; 1,46; 1,77; 2,02 Adequada: convergeix suaument
0,5 3,0; 3,0; 3,0; 3,0; 3,0 Perfecta per a aquesta paràbola (hi arriba en un pas); casualitat de la forma quadràtica, no generalitzable
1,1 6,6; −1,32; 8,18; −3,22; 10,47 Massa gran: salta d'una banda a l'altra de la vall cada vegada més lluny; la pèrdua puja (10 → 14 → 20 → 28 → 40) i divergeix

Si dibuixessis la pèrdua per iteració: amb η petita, una corba que baixa a poc a poc i s'aplana abans d'arribar; amb η adequada, una caiguda ràpida que s'estabilitza; amb η gran, oscil·lacions o una corba que puja i explota (a PyTorch apareixen nan). No hi ha cap valor universal: depèn de l'escala de les dades (per això estandarditzem) i de l'arquitectura. Valors típics amb Adam (secció 6): entre 0,0001 i 0,01. A la pràctica es prova amb validació (04-06), i sovint es redueix durant l'entrenament (learning rate schedule): passos grans al principi, fins al final, la mateixa intuïció que la temperatura del recuit simulat.

  1. Regla de la cadena i retropropagació

Per aplicar θ ← θ − η·∂L/∂θ cal conèixer el pendent de la pèrdua respecte de cada pes, i en una xarxa el pes de la primera capa afecta la pèrdua a través d'una cadena llarga d'operacions. L'eina és la regla de la cadena: el pendent d'una composició de funcions és el producte dels pendents de cada baula. Si L depèn de p, p de z i z de w, aleshores ∂L/∂w = (∂L/∂p) · (∂p/∂z) · (∂z/∂w). Intuïció: si z puja 2 unitats per cada unitat de w, p puja 0,25 per unitat de z i L baixa 3 per unitat de p, aleshores L baixa 3 × 0,25 × 2 = 1,5 per unitat de w.

Vegem-ho al graf de còmput d'una neurona sigmoide amb pèrdua BCE (la nostra regressió logística):

flowchart LR
    x["x (entrades)"] --> Z["z = w·x + b"]
    w["w, b"] --> Z
    Z --> P["p = σ(z)"]
    P --> L["L = −[y·log p + (1−y)·log(1−p)]"]
    y["y (etiqueta)"] --> L
    L -. "∂L/∂p" .-> P
    P -. "∂p/∂z = p(1−p)" .-> Z
    Z -. "∂z/∂w = x,  ∂z/∂b = 1" .-> w

Les fletxes contínues són el pas cap endavant (calcular la predicció i la pèrdua); les discontínues, el pas cap enrere: des de la pèrdua, cada node rep el pendent acumulat, el multiplica pel seu pendent local i el passa a les seves entrades. Per a la parella sigmoide + BCE les dues primeres baules se simplifiquen d'una manera preciosa: ∂L/∂z = p − y (la probabilitat predita menys l'etiqueta). Per tant ∂L/∂w = (p − y)·x i ∂L/∂b = p − y. Compara-ho amb la regla del perceptró: e·x amb e = y − ŷ. És la mateixa regla, amb la probabilitat en lloc de l'esglaó, i ara sabem d'on surt: és el gradient de la pèrdua.

Exemple numèric: comanda x = (0,5; 1,0; 1) (import, dies i client nou estandarditzats), etiqueta y = 1 (es va retornar), pesos w = (0,4; 0,3; 0,9), b = −2.

  • Endavant: z = 0,2 + 0,3 + 0,9 − 2 = −0,6; p = σ(−0,6) = 0,354; pèrdua L = −log(0,354) = 1,037. La xarxa dona un 35 % a una cosa que va passar: malament.
  • Enrere: ∂L/∂z = p − y = −0,646; ∂L/∂w = −0,646 · x = (−0,323; −0,646; −0,646); ∂L/∂b = −0,646.
  • Pas amb η = 0,5: w ← (0,4; 0,3; 0,9) − 0,5·(−0,323; −0,646; −0,646) = (0,561; 0,623; 1,223); b ← −2 + 0,323 = −1,677.
  • Comprovació: ara z = 0,449, p = 0,61, L = 0,494. La pèrdua ha baixat d'1,037 a 0,494 per a aquesta comanda. Amb totes les comandes, el gradient que es fa servir és la mitjana dels gradients individuals.

En una xarxa amb capes ocultes la cadena és més llarga però idèntica en esperit: el pendent de la pèrdua respecte de la sortida de l'última capa es propaga a la penúltima multiplicant pels pesos i pel pendent de l'activació (1 o 0 en ReLU; ≤ 0,25 en sigmoide, i d'aquí l'esvaïment de 05-02), i així successivament fins a la primera. Aquest algorisme, que reutilitza els càlculs cap enrere capa a capa en lloc de refer-los per a cada pes, és la retropropagació (backpropagation), popularitzada el 1986 per Rumelhart, Hinton i Williams (01-01), i és el que va permetre entrenar les capes ocultes que resolien XOR. A PyTorch no l'escriuràs mai: cada tensor recorda les operacions que l'han produït (el graf de còmput), i L.backward() recorre aquest graf cap enrere i deixa a p.grad el pendent de cada paràmetre. És la diferenciació automàtica, i és la raó per la qual els frameworks de deep learning existeixen (07-03).

  1. Lots, minilots, èpoques i optimitzadors (SGD, moment, Adam)

Sobre quants exemples calculem el gradient abans de fer un pas?

Variant Exemples per pas Passos per època (1.799 comandes d'entrenament) Avantatges Inconvenients
Per lots (batch) Tots 1 Gradient exacte, descens suau Lent per pas; no cap a la memòria amb milions d'imatges
Estocàstic (SGD pur) 1 1.799 Barat per pas; el soroll ajuda a escapar d'òptims locals pobres Molt sorollós; no aprofita el paral·lelisme de la GPU
Minilots 32-512 (aquí 64) 29 L'equilibri: gradient raonable, eficient en GPU, una mica de soroll útil Un hiperparàmetre més (la mida de lot)

Una època és una passada completa per les dades d'entrenament (com al perceptró). Amb minilots de 64 i 1.799 comandes, cada època són 29 passos de gradient; 100 èpoques, 2.900 passos. Les dades es barregen a l'inici de cada època perquè els lots canviïn.

L'optimitzador és la regla que converteix el gradient en actualització. La bàsica és la de la secció 2 (SGD). Dues millores gairebé universals:

  • Moment (momentum): en lloc de moure's només segons el gradient actual, s'acumula una "velocitat" (mitjana mòbil dels gradients recents). Com una bola que roda: travessa petits sotracs, accelera en pendents llargs i esmorteeix les oscil·lacions en valls estretes. torch.optim.SGD(..., momentum=0.9).
  • Adam (adaptive moment estimation): moment més una taxa d'aprenentatge adaptada a cada paràmetre (els que reben gradients grans es frenen, els que reben gradients petits s'acceleren). És robust a l'elecció d'η i és l'optimitzador per defecte per començar. torch.optim.Adam(..., lr=0.001).

Al nostre MLP, la pèrdua de validació després d'1, 5, 10, 20 i 30 èpoques va ser: SGD (η = 0,01) 0,72 → 0,60 → 0,53 → 0,47 → 0,45; SGD amb moment 0,55 → 0,45 → 0,42 → 0,34 → 0,33; Adam (η = 0,001) 0,73 → 0,48 → 0,39 → 0,34 → 0,33. Tots tres van al mateix lloc; moment i Adam hi arriben molt abans.

  1. Regularització pròpia de les xarxes: aturada anticipada, dropout, augment de dades i normalització per lots

Una xarxa amb centenars de paràmetres per cada pocs exemples sobreajusta amb facilitat (04-06). A més de la regularització L2 (que aquí s'anomena weight decay i és un argument de l'optimitzador), el deep learning hi afegeix quatre tècniques pròpies:

Tècnica Què fa Per què funciona Quan
Aturada anticipada (early stopping) Avaluar la pèrdua en un conjunt de validació al final de cada època i aturar-se quan porta k èpoques sense millorar (paciència), recuperant els pesos de la millor època Al principi la xarxa aprèn el que és general; amb més èpoques comença a memoritzar el soroll d'entrenament i la validació empitjora Sempre. És gratis i substitueix "triar el nombre d'èpoques"
Dropout Durant l'entrenament, a cada pas, apagar a l'atzar una fracció p (0,1-0,5) de les neurones d'una capa; en predicció es fan servir totes Cap neurona no pot dependre que una altra de concreta hi sigui; la xarxa aprèn trets redundants i robustos, com un ensamblatge de moltes subxarxes Capes denses grans; al nostre MLP petit ajuda poc
Augment de dades Crear exemples nous transformant els existents: girar, retallar o canviar la brillantor de fotos; substituir sinònims en text Més exemples "gratis" que ensenyen les invariàncies correctes (un paquet danyat ho està encara que la foto estigui girada) Imatges i àudio sobretot (05-04)
Normalització per lots (batch norm) Estandarditzar la sortida de cada capa amb la mitjana i la desviació del minilot (més dos paràmetres apresos) Manté les activacions en un rang sa capa a capa; permet taxes d'aprenentatge més grans i actua com a regularitzador suau Xarxes profundes, sobretot convolucionals

Amb això queda tancat el que 04-06 va deixar apuntat. Al codi de la secció 9 farem servir aturada anticipada i dropout; l'augment de dades i la normalització per lots apareixen a 05-04.

  1. Codi (a): descens del gradient des de zero en numpy

Entrenem una neurona sigmoide (regressió logística) sobre 3.000 comandes de NovaMarket amb tres columnes, escrivint el gradient a mà segons la secció 5:

import numpy as np
from novamarket_ml import generar_comandes_ml

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

def perdua_bce(p, y):
    eps = 1e-9                                       # evita log(0)
    return -np.mean(y * np.log(p + eps) + (1 - y) * np.log(1 - p + eps))

comandes = generar_comandes_ml(3000, 42)
cols = ["import_comanda", "dies_lliurament", "client_nou"]
X = comandes[cols].values.astype(float)
y = comandes["retornat"].values.astype(float)
X = (X - X.mean(axis=0)) / X.std(axis=0)             # estandarditzar (imprescindible)

w = np.zeros(3); b = 0.0                             # punt de partida
taxa = 0.5
print(f"{'època':>5} {'pèrdua':>8} {'w_import':>10} {'w_dies':>8} {'w_nou':>8} {'b':>7}")
for epoca in range(1, 201):
    z = X @ w + b                                    # 1. endavant: suma ponderada de les 3.000 comandes
    p = sigmoide(z)                                  #    probabilitats
    grad_z = p - y                                   # 2. enrere: dL/dz = p - y per a cada comanda
    grad_w = X.T @ grad_z / len(y)                   #    dL/dw = mitjana de (p - y) * x
    grad_b = grad_z.mean()                           #    dL/db = mitjana de (p - y)
    w -= taxa * grad_w                               # 3. pas costa avall
    b -= taxa * grad_b
    if epoca in (1, 2, 3, 5, 10, 20, 50, 100, 200):
        L = perdua_bce(sigmoide(X @ w + b), y)
        print(f"{epoca:5d} {L:8.4f} {w[0]:10.3f} {w[1]:8.3f} {w[2]:8.3f} {b:7.3f}")

Sortida:

època   pèrdua   w_import   w_dies    w_nou       b
    1   0.6250      0.064    0.031    0.056  -0.168
    2   0.5727      0.120    0.058    0.104  -0.315
    3   0.5322      0.170    0.082    0.147  -0.444
    5   0.4753      0.252    0.124    0.220  -0.659
   10   0.4040      0.399    0.202    0.351  -1.031
   20   0.3568      0.577    0.311    0.516  -1.436
   50   0.3310      0.811    0.490    0.751  -1.900
  100   0.3266      0.934    0.603    0.880  -2.132
  200   0.3261      0.987    0.653    0.936  -2.233

Lectura: amb w = 0 la pèrdua inicial és ln 2 = 0,693 (la xarxa diu 0,5 a tot); en 10 èpoques ha baixat a 0,40 i després s'aplana a 0,326: hem arribat al fons de la vall (aquesta pèrdua és convexa, així que és el mínim global). Els pesos convergeixen a (0,99; 0,65; 0,94) i b = −2,23; LogisticRegression sense regularització sobre les mateixes dades dona (0,996; 0,661; 0,945) i −2,248: hem reproduït fit amb vint línies. És descens per lots (les 3.000 comandes a cada pas); amb taxa = 0.01 després de 200 èpoques la pèrdua encara era a 0,51 (massa lenta), i amb taxa = 20 oscil·la entre 0,47 i 0,66 sense convergir (massa gran), tal com prediu la secció 4.

  1. Codi (b): entrenament complet de l'MLP de NovaMarket en PyTorch

Ara la xarxa 21 → 16 → 8 → 1 de 05-02, amb tot el que hem après: minilots, Adam, dropout, aturada anticipada amb conjunt de validació i avaluació final amb AUC en el mateix test de 04-05.

9.1 Dades: entrenament, validació i test

import numpy as np, torch, torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score
from novamarket_ml import generar_comandes_ml, embrutar_comandes, preparar_comandes, crear_preparacio

X, y = preparar_comandes(embrutar_comandes(generar_comandes_ml(3000, 42), 42))
Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)   # test: com a 04-05
Xtr2, Xval, ytr2, yval = train_test_split(Xtr, ytr, test_size=0.2, random_state=42, stratify=ytr)  # validació
prep = crear_preparacio().fit(Xtr2)                  # el preprocessament s'ajusta NOMÉS amb entrenament

def a_tensor(Xd, yd):
    return (torch.tensor(prep.transform(Xd), dtype=torch.float32),
            torch.tensor(yd.values, dtype=torch.float32).unsqueeze(1))   # y com a columna (n x 1)

Xtr_t, ytr_t = a_tensor(Xtr2, ytr2)
Xval_t, yval_t = a_tensor(Xval, yval)
Xte_t, yte_t = a_tensor(Xte, yte)
print(Xtr_t.shape, Xval_t.shape, Xte_t.shape)        # torch.Size([1799, 21]) torch.Size([450, 21]) torch.Size([750, 21])

El test són les mateixes 750 comandes de 04-05 (mateixa llavor), així que l'AUC serà comparable amb el 0,844 de la logística. La validació (450 comandes) serveix per a l'aturada anticipada; el test es mira una sola vegada al final (04-06).

9.2 Xarxa, pèrdua, optimitzador i bucle

def crear_xarxa(dropout=0.2):
    return nn.Sequential(
        nn.Linear(21, 16), nn.ReLU(), nn.Dropout(dropout),
        nn.Linear(16, 8),  nn.ReLU(), nn.Dropout(dropout),
        nn.Linear(8, 1))                              # SENSE sigmoide: la posa BCEWithLogitsLoss

def avaluar(xarxa, X_t, y_t, perdua_fn):
    xarxa.eval()                                     # mode avaluació: desactiva el dropout
    with torch.no_grad():                            # sense graf de còmput: més ràpid
        logits = xarxa(X_t)
        L = perdua_fn(logits, y_t).item()
        prob = torch.sigmoid(logits).numpy().ravel() # ara sí, sigmoide per llegir probabilitats
    return L, roc_auc_score(y_t.numpy().ravel(), prob)

def entrenar(xarxa, epoques=300, taxa=0.001, lot=64, paciencia=15, llavor=0):
    torch.manual_seed(llavor)
    carregador = DataLoader(TensorDataset(Xtr_t, ytr_t), batch_size=lot, shuffle=True)  # minilots barrejats
    perdua_fn = nn.BCEWithLogitsLoss()                # sigmoide + entropia creuada binària, estable
    opt = torch.optim.Adam(xarxa.parameters(), lr=taxa)
    millor_val, millor_estat, sense_millora, historial = float("inf"), None, 0, []
    for ep in range(1, epoques + 1):
        xarxa.train()                                 # mode entrenament: dropout actiu
        suma = 0.0
        for xb, yb in carregador:                     # 29 lots per època
            opt.zero_grad()                           # 1. esborrar gradients del pas anterior
            L = perdua_fn(xarxa(xb), yb)              # 2. endavant: predicció i pèrdua del lot
            L.backward()                              # 3. enrere: retropropagació -> p.grad de cada paràmetre
            opt.step()                                # 4. Adam actualitza els 497 paràmetres
            suma += L.item() * len(xb)
        L_train = suma / len(Xtr_t)
        L_val, auc_val = avaluar(xarxa, Xval_t, yval_t, perdua_fn)
        historial.append((ep, L_train, L_val, auc_val))
        if L_val < millor_val - 1e-4:                 # millora la validació?
            millor_val, sense_millora = L_val, 0
            millor_estat = {k: v.clone() for k, v in xarxa.state_dict().items()}   # desar els millors pesos
        else:
            sense_millora += 1
            if sense_millora >= paciencia:            # aturada anticipada
                print(f"Aturada anticipada a l'època {ep}; millor època: {ep - paciencia}")
                break
    xarxa.load_state_dict(millor_estat)               # recuperar els pesos de la millor època
    return historial

torch.manual_seed(42)
xarxa = crear_xarxa(dropout=0.2)
historial = entrenar(xarxa)
for ep, ltr, lval, auc in historial:
    if ep in (1, 2, 3, 5, 10, 20, 30, 40, 50, 60, 70, 75, 80, 90):
        print(f"època {ep:3d}  pèrdua train {ltr:.4f}  pèrdua val {lval:.4f}  AUC val {auc:.3f}")

L_test, auc_test = avaluar(xarxa, Xte_t, yte_t, nn.BCEWithLogitsLoss())
print("Pèrdua test:", round(L_test, 4), " AUC test:", round(auc_test, 3))

Sortida (varia lleugerament amb la llavor i la versió de PyTorch):

Aturada anticipada a l'època 90; millor època: 75
Època Pèrdua train Pèrdua val AUC val Comentari
1 0,7508 0,7316 0,544 Punt de partida: gairebé atzar
2 0,7220 0,7087 0,474
3 0,6953 0,6765 0,397 Encara "sap" menys que una moneda
5 0,5529 0,5067 0,553 Comença la baixada ràpida
10 0,3909 0,3962 0,740
20 0,3556 0,3581 0,801
30 0,3431 0,3367 0,825
40 0,3385 0,3291 0,833
50 0,3335 0,3286 0,831 S'aplana
60 0,3282 0,3257 0,834
70 0,3185 0,3270 0,833
75 0,3226 0,3241 0,835 Millor època (mínim de validació)
80 0,3190 0,3248 0,836 La validació ja no millora
90 0,3206 0,3248 0,835 15 èpoques sense millorar: aturada
Pèrdua test: 0.3324  AUC test: 0.834

Explicació de les peces clau del bucle:

  • DataLoader(..., batch_size=64, shuffle=True) lliura les 1.799 comandes en 29 minilots diferents a cada època.
  • El trio zero_grad()backward()step() és el cor: sense zero_grad() els gradients s'acumulen d'un lot al següent (un error clàssic); backward() és la retropropagació de la secció 5, automàtica; step() aplica la regla d'Adam.
  • xarxa.train() / xarxa.eval() commuten el dropout: apagada aleatòria en entrenar, totes les neurones en avaluar. Per això la pèrdua d'entrenament de la taula és una mica pessimista (es calcula amb neurones apagades) i pot quedar per sobre de la de validació a les primeres èpoques.
  • L'aturada anticipada ha triat l'època 75 mirant només la validació; sense ella, què hauria passat? Ho veuràs a l'exercici 2: continuant 300 èpoques sense dropout, la pèrdua d'entrenament baixa a 0,22-0,23 però la de validació puja a 0,39-0,40 i l'AUC de test cau per sota de 0,78. Sobreajust de manual.

9.3 Resultat i lectura per a NovaMarket

L'AUC en test és 0,834; repetint amb cinc llavors diferents s'obté entre 0,834 i 0,842 (mitjana 0,838). La regressió logística de 04-05 donava 0,844 i el bosc ajustat de 04-06, 0,838. La xarxa no guanya: empata dins del soroll amb més complexitat, menys interpretabilitat i més decisions a prendre. La Marta ho explica al Diego sense embuts: per al predictor de devolucions, dades tabulars amb 2.249 exemples i una "veritat" força lineal, la logística continua sent l'elecció (04-06: a igualtat de rendiment, el model més simple). Això és l'esperable i convé saber-ho: en tabular petit, les xarxes rarament baten els models clàssics ben ajustats. La xarxa es justifica allà on ells no arriben, i cap allà anem a 05-04 i 05-05: les fotos d'incidències i les ressenyes.

Errors Comuns i Consells

  • Oblidar opt.zero_grad(). Els gradients se sumen al pas anterior i l'entrenament es descontrola. És l'error número u en escriure el bucle a mà.
  • Sigmoide a la xarxa i BCEWithLogitsLoss alhora. Doble sigmoide: la xarxa aprèn malament i les "probabilitats" queden comprimides. O sigmoide + BCELoss, o res + BCEWithLogitsLoss (recomanat).
  • No commutar train()/eval(). Avaluar amb dropout actiu dona resultats pitjors i aleatoris; entrenar en mode eval anul·la el dropout.
  • Taxa d'aprenentatge a ull. Si la pèrdua no baixa o dona nan, és el primer que cal tocar (abaixar). Si baixa molt a poc a poc, apujar o fer servir Adam. Prova-la amb validació com qualsevol hiperparàmetre.
  • Triar el nombre d'èpoques mirant el test. Contamina l'única mirada final (04-06). Per a això hi ha la validació i l'aturada anticipada.
  • Comparar amb la logística en una divisió de dades diferent. El nostre test és exactament el de 04-05 (mateixa llavor): només així el 0,834 es pot comparar amb el 0,844.
  • Concloure "les xarxes són dolentes" per aquest resultat. És un problema tabular petit i gairebé lineal; el veredicte canvia amb text i imatges (05-04, 05-05).

Exercicis

Exercici 1. Calcula a mà un pas de descens del gradient per a la neurona de la secció 5 amb x = (1; 0; 2), y = 0 (no es va retornar), w = (0,5; −1; 0,8), b = −1 i η = 0,5: obtén z, p, la pèrdua, ∂L/∂z, ∂L/∂w, ∂L/∂b, els nous paràmetres i la nova pèrdua. Comprova que ha baixat.

Exercici 2. Entrena la xarxa de la secció 9 sense dropout (crear_xarxa(0.0)) i sense aturada anticipada (paciencia=10**6, epoques=300), avaluant en mode eval la pèrdua d'entrenament, la de validació i l'AUC de test a les èpoques 10, 30, 50, 100, 200 i 300 (amb avaluar, i sense recuperar el "millor estat"). Descriu la corba i explica en quina època hauries d'haver parat.

Exercici 3. Al codi de la secció 8, canvia taxa a 0,01, a 5 i a 20, i anota la pèrdua a les èpoques 1, 5, 20, 50 i 200. Classifica cada taxa segons la taula de la secció 4 i explica per què la pèrdua amb taxa = 20 no baixa encara que tampoc no explota.

Solucions

Solució 1. z = 0,5·1 + (−1)·0 + 0,8·2 − 1 = 1,1; p = σ(1,1) = 0,750; pèrdua L = −log(1 − 0,75) = 1,386 (dona un 75 % a una devolució que no va passar). ∂L/∂z = p − y = 0,75; ∂L/∂w = 0,75·x = (0,75; 0; 1,5); ∂L/∂b = 0,75. Nous paràmetres: w = (0,5 − 0,375; −1 − 0; 0,8 − 0,75) = (0,125; −1; 0,05), b = −1 − 0,375 = −1,375. Nova passada: z = 0,125 + 0,1 − 1,375 = −1,15, p = 0,240, L = −log(0,76) = 0,275. La pèrdua ha baixat d'1,386 a 0,275; el pes de la segona columna no canvia perquè la seva entrada era 0 (∂L/∂w₂ = 0,75·0).

Solució 2. A la nostra execució (els valors exactes varien amb la llavor): època 10, train 0,36 / val 0,39 / AUC test 0,78; època 30, 0,30 / 0,33 / 0,83; època 50, 0,29 / 0,32 / 0,83; època 100, 0,27 / 0,33 / 0,82; època 200, 0,24-0,25 / 0,35 / 0,78-0,80; època 300, 0,22-0,23 / 0,39-0,40 / 0,73-0,78. La pèrdua d'entrenament baixa sense parar (la xarxa memoritza), la de validació toca fons cap a l'època 50 i després puja, i l'AUC de test la segueix: de 0,83 cau per sota de 0,78. S'hauria d'haver parat al voltant de l'època 50, que és justament el que l'aturada anticipada amb paciència fa tota sola. És la corba de sobreajust de 04-06 vista a l'eix "èpoques" en lloc de "complexitat".

Solució 3. taxa = 0,01: pèrdues 0,692; 0,686; 0,666; 0,629; 0,507. Baixa sempre però després de 200 èpoques és lluny del 0,326: massa petita. taxa = 5: 0,345; 0,328; 0,326; 0,326; 0,326: convergeix en 20 èpoques, adequada (fins i tot millor que 0,5 per a aquest problema tan suau). taxa = 20: 0,587; 0,470; 0,600; 0,660; 0,609: massa gran, salta d'una banda a l'altra de la vall sense assentar-se. No explota perquè la pèrdua logarítmica amb sigmoide creix a poc a poc (els salts grans en w saturen la sigmoide i el pendent es fa petit, cosa que frena el pas següent), a diferència de la paràbola de la secció 4, el pendent de la qual creix amb la distància i sí que divergeix.

Conclusió

Hem vist com aprèn una xarxa. La pèrdua és un paisatge i, a diferència de l'ascens de turó de 03-04, ara en coneixem el pendent: la derivada, i amb diversos paràmetres el gradient. El descens del gradient fa passos θ ← θ − η·∂L/∂θ (l'hem seguit a mà en una paràbola: 10 → 6,76 → 4,69 → ... ), la taxa d'aprenentatge decideix si el descens és lent, adequat o divergent, i la regla de la cadena permet calcular el pendent de cada pes multiplicant pendents locals cap enrere pel graf de còmput: això és la retropropagació, que PyTorch fa tota sola amb backward(). Hem entès els minilots i les èpoques, què milloren el moment i Adam, i hem tancat la llista pendent de 04-06 amb l'aturada anticipada, el dropout, l'augment de dades i la normalització per lots. En codi, hem reproduït fit de la regressió logística amb vint línies de numpy (pèrdua 0,693 → 0,326) i hem entrenat l'MLP de NovaMarket en PyTorch amb el bucle zero_gradbackwardstep: AUC en test 0,834, empatat amb la logística (0,844) i el bosc (0,838). Per al predictor de devolucions, la Marta es queda amb la logística.

Aquest empat és la millor introducció a la lliçó següent. Si les xarxes no guanyen a les taules de comandes, on guanyen, i per què? A Deep Learning i les seves Aplicacions veurem què fa "profund" el deep learning i per què va enlairar-se el 2012, i les arquitectures que incorporen l'estructura de les dades: les convolucionals, que provarem sobre les fotos d'incidències de NovaMarket (paquet danyat o correcte), les recurrents per a seqüències, els autoencoders per a anomalies i la transferència d'aprenentatge, la idea de no entrenar des de zero.

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