En aquest tema, explorarem les xarxes neuronals recurrents (RNN) avançades, específicament les unitats de memòria a llarg termini (LSTM) i les unitats recurrents acoblades (GRU). Aquestes arquitectures estan dissenyades per abordar les limitacions de les RNN tradicionals, especialment en la gestió de dependències a llarg termini.

Introducció a les LSTM

Què són les LSTM?

Les LSTM són una variant de les RNN dissenyada per recordar informació durant períodes de temps més llargs. Això es fa mitjançant una estructura de cel·la especial que pot mantenir i actualitzar informació a través de múltiples passos temporals.

Components de les LSTM

Les LSTM tenen tres components principals:

  1. Porta d'entrada (Input Gate): Controla quanta informació de l'entrada actual ha de ser guardada en l'estat de la cel·la.
  2. Porta d'oblit (Forget Gate): Decideix quanta informació de l'estat de la cel·la anterior ha de ser oblidada.
  3. Porta de sortida (Output Gate): Determina quina part de l'estat de la cel·la ha de ser utilitzada per calcular la sortida actual.

Funcionament de les LSTM

  1. Porta d'oblit:

    f_t = σ(W_f * [h_{t-1}, x_t] + b_f)
    

    On σ és la funció sigmoide, W_f són els pesos associats a la porta d'oblit, h_{t-1} és l'estat ocult anterior, x_t és l'entrada actual i b_f és el biaix.

  2. Porta d'entrada:

    i_t = σ(W_i * [h_{t-1}, x_t] + b_i)
    

    On W_i són els pesos associats a la porta d'entrada i b_i és el biaix.

  3. Actualització de l'estat de la cel·la:

    C_t = f_t * C_{t-1} + i_t * tanh(W_C * [h_{t-1}, x_t] + b_C)
    

    On C_{t-1} és l'estat de la cel·la anterior, W_C són els pesos associats a l'actualització de l'estat de la cel·la i b_C és el biaix.

  4. Porta de sortida:

    o_t = σ(W_o * [h_{t-1}, x_t] + b_o)
    

    On W_o són els pesos associats a la porta de sortida i b_o és el biaix.

  5. Sortida de l'estat ocult:

    h_t = o_t * tanh(C_t)
    

Exemple de codi amb LSTM en PyTorch

import torch
import torch.nn as nn

class LSTMModel(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers):
        super(LSTMModel, self).__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_size, 1)

    def forward(self, x):
        h_0 = torch.zeros(num_layers, x.size(0), hidden_size)
        c_0 = torch.zeros(num_layers, x.size(0), hidden_size)
        out, _ = self.lstm(x, (h_0, c_0))
        out = self.fc(out[:, -1, :])
        return out

# Paràmetres
input_size = 10
hidden_size = 20
num_layers = 2

# Model
model = LSTMModel(input_size, hidden_size, num_layers)

Introducció a les GRU

Què són les GRU?

Les GRU són una altra variant de les RNN que simplifiquen l'estructura de les LSTM combinant les portes d'entrada i d'oblit en una sola porta de "reset" i una porta de "update".

Components de les GRU

  1. Porta de reset (Reset Gate): Decideix quanta informació de l'estat ocult anterior ha de ser oblidada.
  2. Porta d'actualització (Update Gate): Controla quanta informació de l'estat ocult anterior ha de ser guardada i quanta de l'estat actual ha de ser actualitzada.

Funcionament de les GRU

  1. Porta de reset:

    r_t = σ(W_r * [h_{t-1}, x_t] + b_r)
    
  2. Porta d'actualització:

    z_t = σ(W_z * [h_{t-1}, x_t] + b_z)
    
  3. Càlcul de l'estat candidat:

    n_t = tanh(W_n * [r_t * h_{t-1}, x_t] + b_n)
    
  4. Actualització de l'estat ocult:

    h_t = (1 - z_t) * n_t + z_t * h_{t-1}
    

Exemple de codi amb GRU en PyTorch

import torch
import torch.nn as nn

class GRUModel(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers):
        super(GRUModel, self).__init__()
        self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_size, 1)

    def forward(self, x):
        h_0 = torch.zeros(num_layers, x.size(0), hidden_size)
        out, _ = self.gru(x, h_0)
        out = self.fc(out[:, -1, :])
        return out

# Paràmetres
input_size = 10
hidden_size = 20
num_layers = 2

# Model
model = GRUModel(input_size, hidden_size, num_layers)

Comparació entre LSTM i GRU

Característica LSTM GRU
Complexitat Més complexa (3 portes) Menys complexa (2 portes)
Capacitat de memòria Pot recordar informació durant més temps Pot recordar informació durant menys temps
Temps d'entrenament Més llarg Més curt
Rendiment Pot ser millor en tasques amb dependències llargues Pot ser millor en tasques amb dependències curtes

Exercici Pràctic

Exercici

Implementa una xarxa LSTM per predir la següent paraula en una seqüència de text utilitzant PyTorch. Utilitza un conjunt de dades de text senzill per entrenar la teva xarxa.

Solució

import torch
import torch.nn as nn
import torch.optim as optim

# Conjunt de dades de text senzill
text = "hello world hello pytorch hello deep learning"
chars = list(set(text))
char_to_idx = {char: idx for idx, char in enumerate(chars)}
idx_to_char = {idx: char for idx, char in enumerate(chars)}

# Paràmetres
input_size = len(chars)
hidden_size = 128
num_layers = 2
seq_length = 5
learning_rate = 0.01
num_epochs = 500

# Preparació de les dades
def prepare_data(text, seq_length):
    X = []
    Y = []
    for i in range(0, len(text) - seq_length):
        seq_in = text[i:i + seq_length]
        seq_out = text[i + seq_length]
        X.append([char_to_idx[char] for char in seq_in])
        Y.append(char_to_idx[seq_out])
    return torch.tensor(X), torch.tensor(Y)

X, Y = prepare_data(text, seq_length)

# Model
class LSTMTextModel(nn.Module):
    def __init__(self, input_size, hidden_size, num_layers):
        super(LSTMTextModel, self).__init__()
        self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
        self.fc = nn.Linear(hidden_size, input_size)

    def forward(self, x):
        h_0 = torch.zeros(num_layers, x.size(0), hidden_size)
        c_0 = torch.zeros(num_layers, x.size(0), hidden_size)
        out, _ = self.lstm(x, (h_0, c_0))
        out = self.fc(out[:, -1, :])
        return out

model = LSTMTextModel(input_size, hidden_size, num_layers)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

# Entrenament
for epoch in range(num_epochs):
    for i in range(len(X)):
        inputs = torch.nn.functional.one_hot(X[i], num_classes=input_size).float().unsqueeze(0)
        targets = Y[i].unsqueeze(0)

        outputs = model(inputs)
        loss = criterion(outputs, targets)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

    if (epoch + 1) % 100 == 0:
        print(f'Epoch [{epoch + 1}/{num_epochs}], Loss: {loss.item():.4f}')

# Predicció
def predict(model, char, seq_length):
    input_seq = [char_to_idx[char]]
    for _ in range(seq_length):
        input_tensor = torch.nn.functional.one_hot(torch.tensor(input_seq), num_classes=input_size).float().unsqueeze(0)
        output = model(input_tensor)
        _, predicted_idx = torch.max(output, 1)
        input_seq.append(predicted_idx.item())
    return ''.join([idx_to_char[idx] for idx in input_seq])

print(predict(model, 'h', 10))

Conclusió

En aquesta secció, hem explorat les arquitectures LSTM i GRU, les seves diferències i com implementar-les utilitzant PyTorch. Les LSTM i les GRU són eines poderoses per treballar amb dades seqüencials i tenen aplicacions en àrees com el processament del llenguatge natural i la predicció de sèries temporals. A la següent secció, veurem com aplicar aquestes tècniques en aplicacions pràctiques.

© Copyright 2024. Tots els drets reservats