En tancar el mòdul de CNN vam deixar una porta oberta: les xarxes convolucionals dominen les imatges del catàleg de TecnoMarket, però dos dels seus problemes més valuosos —analitzar les ressenyes dels clients i predir la demanda diària— no són imatges. Són seqüències: dades on l'ordre importa i on cada element depèn dels anteriors. En aquesta lliçó descobriràs per què les xarxes denses i les CNN es queden curtes amb aquesta mena de dades, i coneixeràs l'arquitectura dissenyada específicament per a elles: la xarxa neuronal recurrent (RNN). N'entendràs la idea central (un estat ocult que actua com a memòria), la formulació matemàtica amb un exemple numèric a mà, les topologies típiques i la primera implementació en Keras, a més de la seva gran limitació, que motivarà la lliçó següent.

Contingut

  1. Dades seqüencials: quan l'ordre ho és tot
  2. Per què les denses i les CNN no modelen l'ordre
  3. La idea de recurrència: un estat ocult com a memòria
  4. Desplegament temporal d'una RNN
  5. La fórmula de la RNN amb un exemple numèric a mà
  6. Topologies: one-to-many, many-to-one, many-to-many
  7. SimpleRNN en Keras sobre una seqüència sintètica
  8. La limitació: memòria a curt termini

Dades seqüencials: quan l'ordre ho és tot

Una dada seqüencial és una col·lecció ordenada d'elements on la posició i el context importen. Si en remenes els elements, en destrueixes la informació.

Exemples a TecnoMarket:

Dada Elements de la seqüència Què passa si ho desordenes?
Ressenya de client Paraules: "no", "és", "gens", "dolent" "gens és dolent no" perd el sentit; pitjor encara: "no és gens dolent" (positiva) i "és dolent, no és gens bo" (negativa) fan servir gairebé les mateixes paraules
Demanda diària Vendes per dia: 120, 135, 128, 410... Perds la tendència, l'estacionalitat setmanal i el pic del Black Friday
Clics d'un usuari portada → mòbils → iPhone → cistella L'ordre distingeix "va comprar després de comparar" d'una navegació aleatòria
Àudio d'atenció telefònica Mostres de so en el temps La parla desordenada és soroll

Fixa't en el primer exemple: en català, "no és gens dolent" és un elogi. Un model que només compti paraules ("no", "dolent" → negativa!) fallarà sistemàticament. Necessitem un model que llegeixi en ordre i recordi el que ha llegit.

Per què les denses i les CNN no modelen l'ordre

Ja coneixes dues famílies d'arquitectures. Vegem per què cap de les dues no hi encaixa del tot:

  • Xarxa densa (com la 784-128-64-10 de MNIST):
    • Espera una entrada de mida fixa. Les ressenyes tenen longituds diferents (5 paraules o 200) i una sèrie de vendes creix cada dia.
    • Tracta cada posició d'entrada de manera independent: no hi ha res a l'arquitectura que digui que la paraula 3 va després de la paraula 2. Per a la xarxa, l'entrada és una "bossa" de números.
    • Si entrenes una densa per reconèixer "enviament ràpid" a les posicions 1-2, no sabrà reconèixer-ho a les posicions 7-8: hauria d'aprendre el patró a cada posició per separat (el mateix problema de l'explosió de paràmetres que vam veure a 03-01 amb les imatges).
  • CNN:
    • Resolen part del problema: els seus filtres amb pesos compartits detecten un patró local sigui on sigui (de fet, existeixen CNN 1D per a text). Però el seu "camp de visió" és local i de mida fixa: un filtre de mida 3 veu 3 paraules seguides.
    • Una dependència llarga —"El televisor que vaig comprar fa dos mesos i que va arribar amb la pantalla trencada no funciona"— exigeix connectar "televisor" amb "no funciona" a 15 paraules de distància. Apilar moltes capes convolucionals ho amplia, però de manera rígida i costosa.

El que necessitem és un mecanisme que processi els elements d'un en un, en ordre, i que arrossegui un resum del que ha vist fins al moment. Això és exactament la recurrència.

La idea de recurrència: un estat ocult com a memòria

Una RNN processa la seqüència pas a pas amb una única cel·la (un petit bloc de xarxa neuronal) que s'aplica repetidament. A cada pas de temps t, la cel·la rep dues coses:

  1. L'entrada actual x_t (la paraula d'avui, la venda d'avui).
  2. L'estat ocult h_{t-1}: un vector que resumeix tot el que s'ha processat fins al pas anterior.

I produeix un nou estat ocult h_t, que passa al pas següent. L'estat ocult és la memòria de la xarxa: un resum comprimit, de mida fixa, de tota la seqüència llegida fins ara.

Pensa en el comitè de compres de TecnoMarket del mòdul 1, però llegint una ressenya en veu alta paraula a paraula: després de cada paraula, el comitè actualitza una nota mental ("de moment sona positiu... compte, ha dit 'però'... ara es queixa de l'enviament"). Aquesta nota mental és h_t. Al final de la ressenya, la nota resumeix l'opinió completa.

Dues propietats clau:

  • La mateixa cel·la a tots els passos: els pesos no canvien d'un pas de temps a l'altre. És el paral·lelisme exacte de les CNN: on una CNN comparteix pesos en l'espai (el mateix filtre recorre tota la imatge), una RNN comparteix pesos en el temps (la mateixa cel·la recorre tota la seqüència). Per això una RNN pot processar seqüències de qualsevol longitud amb un nombre fix de paràmetres.
  • L'estat té mida fixa: triïs ressenyes de 10 o de 300 paraules, h_t és sempre un vector de, posem, 64 números. La xarxa aprèn a decidir què val la pena guardar en aquest resum.

Desplegament temporal d'una RNN

Encara que la RNN és una sola cel·la amb un bucle, per entendre-la (i per entrenar-la) convé "desenrotllar-la" en el temps, com si fos una xarxa profunda amb una capa per pas:

graph LR
    subgraph "RNN desplegada en el temps"
        x1["x₁: 'no'"] --> C1[Cel·la RNN]
        x2["x₂: 'és'"] --> C2[Cel·la RNN]
        x3["x₃: 'gens'"] --> C3[Cel·la RNN]
        x4["x₄: 'dolent'"] --> C4[Cel·la RNN]
        h0["h₀ = 0"] --> C1
        C1 -- "h₁" --> C2
        C2 -- "h₂" --> C3
        C3 -- "h₃" --> C4
        C4 -- "h₄" --> S["Sortida: sentiment"]
    end

Punts importants del diagrama:

  • Les quatre caixes "Cel·la RNN" són la mateixa cel·la amb els mateixos pesos, dibuixada quatre vegades.
  • L'estat inicial h₀ sol ser un vector de zeros: en començar, la xarxa no ha llegit res.
  • La informació flueix d'esquerra a dreta per la cadena d'estats: perquè "no" (pas 1) influeixi en la decisió final, el seu efecte ha de sobreviure a h₁ → h₂ → h₃ → h₄.

Aquest últim punt, que ara sembla un detall, serà la clau de la limitació que veurem al final.

La fórmula de la RNN amb un exemple numèric a mà

La cel·la RNN bàsica (la que Keras anomena SimpleRNN) calcula:

h_t = tanh(Wx · x_t + Wh · h_{t-1} + b)

On:

  • x_t: vector d'entrada al pas t.
  • h_{t-1}: estat ocult anterior.
  • Wx: matriu de pesos entrada→estat (s'aprèn).
  • Wh: matriu de pesos estat→estat (s'aprèn). És la matriu de la recurrència: decideix quant i com es conserva la memòria.
  • b: biaix (s'aprèn).
  • tanh: l'activació que ja coneixes de 02-02; manté l'estat entre -1 i 1, cosa que evita que la memòria creixi sense control pas rere pas.

Fem el càlcul a mà amb dimensions minúscules: entrada d'1 número, estat de 2 números.

Suposem aquests pesos ja entrenats:

Wx = [ 0.5 ]      Wh = [ 0.8  -0.2 ]      b = [ 0 ]
     [-0.3 ]           [ 0.1   0.6 ]          [ 0 ]

I la seqüència d'entrada x = [1, 0, 1] (tres passos de temps). Partim de h₀ = [0, 0].

Pas 1 (x₁ = 1):

z₁ = Wx·1 + Wh·[0,0] + b = [0.5, -0.3]
h₁ = tanh([0.5, -0.3]) = [0.462, -0.291]

Pas 2 (x₂ = 0; tota la informació ve de la memòria):

z₂ = Wx·0 + Wh·h₁
   = [0.8·0.462 + (-0.2)·(-0.291),  0.1·0.462 + 0.6·(-0.291)]
   = [0.428, -0.129]
h₂ = tanh([0.428, -0.129]) = [0.404, -0.128]

Pas 3 (x₃ = 1):

z₃ = Wx·1 + Wh·h₂
   = [0.5 + 0.8·0.404 + (-0.2)·(-0.128),  -0.3 + 0.1·0.404 + 0.6·(-0.128)]
   = [0.849, -0.336]
h₃ = tanh([0.849, -0.336]) = [0.690, -0.324]

Observa dues coses: al pas 2, tot i que l'entrada era 0, l'estat no es va buidar — la memòria Wh·h₁ va mantenir viva la informació del pas 1 (atenuada: de 0.462 a 0.404). I al pas 3, la sortida no és la mateixa que al pas 1 malgrat que l'entrada és idèntica (x=1): el context acumulat canvia el resultat. Això és exactament el que una densa no pot fer.

En numpy, el mateix càlcul generalitzat:

import numpy as np

def simple_rnn_pas_a_pas(x_seq, Wx, Wh, b):
    """Executa una SimpleRNN a mà sobre una seqüència.
    x_seq: (passos, dim_entrada), retorna tots els estats h_t."""
    h = np.zeros(Wh.shape[0])          # h0 = vector de zeros
    estats = []
    for x_t in x_seq:                  # un pas de temps per iteració
        h = np.tanh(Wx @ x_t + Wh @ h + b)   # la fórmula de la cel·la
        estats.append(h)
    return np.array(estats)

Wx = np.array([[0.5], [-0.3]])
Wh = np.array([[0.8, -0.2], [0.1, 0.6]])
b  = np.zeros(2)
x  = np.array([[1.0], [0.0], [1.0]])   # seqüència de 3 passos

print(simple_rnn_pas_a_pas(x, Wx, Wh, b))
# [[ 0.462 -0.291]
#  [ 0.404 -0.128]
#  [ 0.690 -0.324]]

El bucle for és literal: una RNN és un bucle sobre el temps amb els mateixos pesos a cada volta.

Recompte de paràmetres

Amb entrada de dimensió d i estat de dimensió u, la cel·la té d·u (Wx) + u·u (Wh) + u (b) paràmetres. Per a d=1, u=2: 2 + 4 + 2 = 8. Fixa't que no depèn de la longitud de la seqüència: 8 paràmetres processen igual 3 passos que 3.000, gràcies als pesos compartits en el temps.

Topologies: one-to-many, many-to-one, many-to-many

Segons quantes entrades i sortides tingui la seqüència, hi ha diverses maneres d'usar una RNN:

Topologia Entrada Sortida Exemple TecnoMarket
Many-to-one Seqüència Un valor Llegir una ressenya completa → una etiqueta (positiva/negativa)
One-to-many Un valor Seqüència Una foto de producte (vector CNN) → generar-ne la descripció paraula a paraula
Many-to-many (sincronitzada) Seqüència Seqüència de la mateixa longitud Per a cada paraula d'una ressenya, etiquetar si és nom de producte o no
Many-to-many (desfasada / seq2seq) Seqüència Seqüència d'una altra longitud Traduir una ressenya de l'anglès al català

En aquest mòdul treballarem sobretot la many-to-one: és la forma natural de l'anàlisi de sentiment (04-03) i de la predicció de demanda amb finestres (04-04): moltes observacions entren, una predicció surt. La generació de text (one-to-many) la desenvoluparem com a projecte a 07-02, i el seq2seq el veurem conceptualment a 04-03.

SimpleRNN en Keras sobre una seqüència sintètica

Comprovarem que una RNN aprèn de debò una dependència temporal. Tasca sintètica: donada una seqüència de 10 números, predir la suma dels 3 últims. Una tasca impossible sense saber quina posició ocupa cada número.

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers

# 1. Generem el dataset sintètic
rng = np.random.default_rng(42)
X = rng.uniform(0, 1, size=(2000, 10, 1))   # 2000 seqüències, 10 passos, 1 valor per pas
y = X[:, -3:, 0].sum(axis=1)                # objectiu: suma dels 3 últims passos

# 2. Model: SimpleRNN many-to-one + sortida de regressió
model = keras.Sequential([
    layers.Input(shape=(10, 1)),        # (passos, característiques per pas)
    layers.SimpleRNN(16),               # retorna només l'ÚLTIM estat h_10 (16 números)
    layers.Dense(1)                     # del resum final, a la predicció
])
model.compile(optimizer="adam", loss="mse", metrics=["mae"])
model.summary()

# 3. Entrenem
historial = model.fit(X, y, epochs=30, batch_size=32,
                      validation_split=0.2, verbose=0)
print(f"MAE final de validació: {historial.history['val_mae'][-1]:.3f}")
# Típic: ~0.05  (sobre sumes que ronden 1.5, un error molt petit)

Claus de lectura per a principiants:

  • La forma d'entrada d'una RNN en Keras sempre és (mostres, passos, característiques). Aquí: 2000 seqüències, de 10 passos, amb 1 número per pas. Aquest tercer eix despista al principi: encara que cada pas sigui un sol número, cal declarar-lo.
  • SimpleRNN(16) crea una cel·la amb estat de 16 dimensions. Paràmetres: 1·16 + 16·16 + 16 = 288 (comprova-ho al summary()).
  • Per defecte la capa retorna només l'últim estat h_10: perfecte per a many-to-one. (El paràmetre return_sequences=True, que retorna tots els estats, el farem servir a 04-02 per apilar capes.)
  • La xarxa aprèn sola a "ignorar" els 7 primers números i a "recordar" els 3 últims: ningú no li ha dit quines posicions importen.

La limitació: memòria a curt termini

Prova mental: canvia la tasca anterior per "predir la suma dels 3 primers números d'una seqüència de 100". Ara la informació rellevant ha de sobreviure 97 passos a l'estat ocult... i la SimpleRNN fracassarà.

Per què? L'entrenament d'una RNN fa servir la retropropagació a través del temps (BPTT, Backpropagation Through Time): conceptualment, és la mateixa retropropagació de 02-03 aplicada a la xarxa desplegada — el "repartiment de la culpa" recorre la cadena de cel·les cap enrere, pas a pas. I aquí reapareix un vell conegut: a cada pas cap enrere, el gradient es multiplica per Wh (i per la derivada de la tanh, que és com a molt 1). Després de molts passos:

  • Si aquests factors són < 1, el gradient s'esvaeix exponencialment: la culpa no arriba mai als primers passos, així que la xarxa no aprèn dependències llargues. És el vanishing gradient de 02-03, però al llarg del temps en lloc d'al llarg de capes.
  • Si són > 1, el gradient explota i l'entrenament es desestabilitza.

Conseqüència pràctica: una SimpleRNN recorda bé uns 5-10 passos; més enllà, la seva memòria es difumina. Per a les ressenyes de TecnoMarket ("El televisor que vaig comprar... [20 paraules] ...no funciona") o per a la demanda amb estacionalitat setmanal i anual, això és insuficient.

A 03-03 vam veure que les skip connections de ResNet van crear "dreceres" perquè el gradient fluís per xarxes molt profundes. Les seqüències necessiten la seva pròpia versió d'aquesta idea: una cel·la amb memòria protegida. És exactament el que fan les LSTM i GRU de la lliçó següent.

Errors Comuns i Consells

  • Passar les dades amb una forma incorrecta. L'error més freqüent en començar: expected ndim=3, found ndim=2. Keras exigeix (mostres, passos, característiques); si cada pas és un escalar, afegeix el tercer eix amb X.reshape(n, passos, 1) o X[..., np.newaxis].
  • Creure que hi ha una cel·la diferent per cada pas de temps. No: és la mateixa cel·la (mateixos Wx, Wh, b) aplicada en bucle. El desplegament és només una manera de dibuixar-la. Per això el nombre de paràmetres no depèn de la longitud de la seqüència.
  • Fer servir una RNN quan l'ordre no importa. Si les teves característiques són independents (edat del client, província, despesa mitjana), una xarxa densa és més simple i funciona millor. La RNN només aporta valor quan hi ha una dependència seqüencial real.
  • Esperar memòria llarga d'una SimpleRNN. Si la teva dependència rellevant és a més de ~10 passos, no t'hi barallis amb la SimpleRNN: és una limitació estructural, no d'hiperparàmetres. La solució arriba a 04-02.
  • Oblidar que la tanh satura. Si l'estat s'omple de valors ±1, els gradients per aquesta via tendeixen a zero (ho vam veure a 02-02). És una altra cara del mateix problema de memòria curta.

Exercicis

  1. A mà: amb els pesos de l'exemple (Wx = [0.5, -0.3]ᵀ, Wh = [[0.8, -0.2], [0.1, 0.6]], b = 0), calcula h₁ i h₂ per a la seqüència x = [0, 1]. Abans de calcular, raona: què valdrà h₁ i per què?
  2. Recompte de paràmetres: quants paràmetres té SimpleRNN(32) amb entrades de 8 característiques per pas? I si la seqüència passa de 20 a 200 passos de temps?
  3. Classificació de topologies: classifica aquests casos de TecnoMarket com a one-to-many, many-to-one o many-to-many: (a) predir la venda de demà a partir de les últimes 30 vendes diàries; (b) marcar, paraula a paraula, quines parts d'una ressenya mencionen l'enviament; (c) generar una resposta automàtica de disculpa a partir de la categoria de la queixa.

Solucions

  1. Amb x₁ = 0 i h₀ = [0,0]: z₁ = Wx·0 + Wh·[0,0] = [0,0], així que h₁ = tanh([0,0]) = [0, 0] — sense entrada i sense memòria prèvia, l'estat continua buit. Amb x₂ = 1: z₂ = Wx·1 + Wh·[0,0] = [0.5, -0.3], i per tant h₂ = [0.462, -0.291] (igual que el pas 1 de l'exemple, perquè el context previ era nul).
  2. Wx: 8·32 = 256, Wh: 32·32 = 1024, b: 32. Total: 1312 paràmetres. Amb 200 passos en lloc de 20: exactament els mateixos 1312 — els pesos es comparteixen en el temps; la longitud de la seqüència no afegeix paràmetres (encara que sí cost de còmput i més dificultat per al gradient).
  3. (a) Many-to-one: 30 valors entren, 1 predicció surt. (b) Many-to-many sincronitzada: una etiqueta per paraula. (c) One-to-many: una categoria entra, una seqüència de paraules surt (aquest tipus de generació la practicarem al projecte 07-02).

Conclusió

Les ressenyes i la demanda de TecnoMarket són seqüències, i les seqüències necessiten una arquitectura que respecti l'ordre: la RNN ho aconsegueix amb una única cel·la que s'aplica pas a pas, arrossegant un estat ocult que funciona com a memòria — pesos compartits en el temps, igual que les CNN els comparteixen en l'espai. N'has vist la fórmula (h_t = tanh(Wx·x_t + Wh·h_{t-1} + b)) funcionant a mà, les topologies i una SimpleRNN en Keras aprenent una dependència temporal real. Però també el seu taló d'Aquil·les: el gradient que viatja cap enrere a través del temps s'esvaeix, i amb ell la memòria a llarg termini. A la propera lliçó coneixerem les cel·les que van resoldre aquest problema i que fa dècades que impulsen les aplicacions reals de seqüències: LSTM i GRU.

Curs de Deep Learning

Mòdul 1: Introducció al Deep Learning

Mòdul 2: Fonaments de Xarxes Neuronals

Mòdul 3: Xarxes Neuronals Convolucionals (CNN)

Mòdul 4: Xarxes Neuronals Recurrents (RNN)

Mòdul 5: Tècniques Avançades en Deep Learning

Mòdul 6: Eines i Frameworks

Mòdul 7: Projectes Pràctics

Mòdul 8: Consideracions Ètiques i Futur del Deep Learning

© Copyright 2026. Tots els drets reservats