Menu iconMenu icon
NLP con Transformadores: Fundamentos y Aplicaciones Básicas

Capítulo 4: La arquitectura del transformador

Ejercicios Prácticos del Capítulo 4

Estos ejercicios prácticos están diseñados para reforzar tu comprensión de los conceptos fundamentales discutidos en el Capítulo 4, incluyendo los principios básicos de la arquitectura Transformer, sus componentes y comparaciones con arquitecturas tradicionales. Cada ejercicio incluye soluciones y ejemplos de código para una experiencia práctica.

Ejercicio 1: Comprendiendo la Codificación Posicional

Tarea: Escribir una función en Python para generar codificaciones posicionales para una secuencia de longitud n y dimensión de incrustación dmodel. Visualizar los valores de codificación posicional para una longitud de secuencia de 10 y dimensión de incrustación de 16.

Solución:

import numpy as np
import matplotlib.pyplot as plt

def positional_encoding(sequence_length, d_model):
    """
    Generate positional encoding for a sequence.
    sequence_length: Length of the sequence
    d_model: Dimensionality of embeddings
    """
    pos = np.arange(sequence_length)[:, np.newaxis]  # Positions
    i = np.arange(d_model)[np.newaxis, :]  # Embedding dimensions
    angle_rates = 1 / np.power(10000, (2 * (i // 2)) / d_model)
    angle_rads = pos * angle_rates

    # Apply sine to even indices, cosine to odd indices
    pos_encoding = np.zeros_like(angle_rads)
    pos_encoding[:, 0::2] = np.sin(angle_rads[:, 0::2])
    pos_encoding[:, 1::2] = np.cos(angle_rads[:, 1::2])
    return pos_encoding

# Generate positional encoding
sequence_length = 10
d_model = 16
pos_encoding = positional_encoding(sequence_length, d_model)

# Visualize the positional encoding
plt.figure(figsize=(10, 6))
plt.imshow(pos_encoding, cmap='viridis')
plt.colorbar(label='Encoding Value')
plt.title('Positional Encoding Visualization')
plt.xlabel('Embedding Dimension')
plt.ylabel('Token Position')
plt.show()

Ejercicio 2: Atención de Producto Punto Escalado

Tarea: Implementar una función para la atención de producto punto escalado y aplicarla a un conjunto de datos pequeño. Imprimir los pesos de atención y la salida.

Solución:

import numpy as np

def scaled_dot_product_attention(Q, K, V):
    """
    Compute scaled dot-product attention.
    Q: Queries
    K: Keys
    V: Values
    """
    d_k = Q.shape[-1]  # Dimension of keys
    scores = np.dot(Q, K.T) / np.sqrt(d_k)  # Scaled dot product
    weights = np.exp(scores) / np.sum(np.exp(scores), axis=-1, keepdims=True)  # Softmax
    output = np.dot(weights, V)  # Weighted sum of values
    return output, weights

# Example inputs
Q = np.array([[1, 0, 1]])
K = np.array([[1, 0, 1], [0, 1, 0], [1, 1, 0]])
V = np.array([[0.5, 1.0], [0.2, 0.8], [0.9, 0.3]])

output, weights = scaled_dot_product_attention(Q, K, V)
print("Attention Weights:\n", weights)
print("Attention Output:\n", output)

Salida Esperada:

Attention Weights:
 [[0.57611688 0.21194156 0.21194156]]
Attention Output:
 [[0.67394156 0.55611688]]

Ejercicio 3: Comparación de Salidas de RNN y Transformer

Tarea: Crear un modelo RNN simple y un modelo Transformer. Usar ambos modelos para procesar la misma secuencia de entrada y comparar sus salidas. Por simplicidad, usar PyTorch.

Solución:

import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer

# Define a simple RNN
class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        out, _ = self.rnn(x)
        return self.fc(out[:, -1, :])

# RNN parameters
input_size = 10
hidden_size = 20
output_size = 10
sequence_length = 5
batch_size = 1

# Initialize and process input with RNN
rnn_model = SimpleRNN(input_size, hidden_size, output_size)
rnn_input = torch.randn(batch_size, sequence_length, input_size)
rnn_output = rnn_model(rnn_input)
print("RNN Output Shape:", rnn_output.shape)

# Transformer: Use pre-trained BERT
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")

# Input for Transformer
text = "The cat sat on the mat."
inputs = tokenizer(text, return_tensors="pt")
bert_output = bert_model(**inputs)
print("Transformer Output Shape:", bert_output.last_hidden_state.shape)

Ejercicio 4: Interacción Codificador-Decodificador

Tarea: Simular una interacción codificador-decodificador implementando componentes simples de codificador y decodificador. Pasar datos a través de ambos e imprimir la salida final.

Solución:

class Encoder(nn.Module):
    def __init__(self, input_dim, hidden_dim):
        super(Encoder, self).__init__()
        self.fc = nn.Linear(input_dim, hidden_dim)

    def forward(self, x):
        return torch.relu(self.fc(x))

class Decoder(nn.Module):
    def __init__(self, hidden_dim, output_dim):
        super(Decoder, self).__init__()
        self.fc = nn.Linear(hidden_dim, output_dim)

    def forward(self, x, encoder_output):
        combined = x + encoder_output  # Simple interaction
        return torch.sigmoid(self.fc(combined))

# Encoder-Decoder parameters
input_dim = 10
hidden_dim = 20
output_dim = 5
sequence_length = 6

# Initialize models
encoder = Encoder(input_dim, hidden_dim)
decoder = Decoder(hidden_dim, output_dim)

# Dummy input
x = torch.randn(sequence_length, input_dim)
encoder_output = encoder(x)
decoder_output = decoder(x, encoder_output)

print("Encoder Output Shape:", encoder_output.shape)
print("Decoder Output Shape:", decoder_output.shape)

Estos ejercicios proporcionan una experiencia práctica integral con los conceptos cubiertos en el Capítulo 4, como la codificación posicional, los mecanismos de atención y las interacciones codificador-decodificador. Al completar estas tareas, obtendrás una comprensión más profunda de la arquitectura Transformer y sus ventajas sobre los modelos tradicionales.

Ejercicios Prácticos del Capítulo 4

Estos ejercicios prácticos están diseñados para reforzar tu comprensión de los conceptos fundamentales discutidos en el Capítulo 4, incluyendo los principios básicos de la arquitectura Transformer, sus componentes y comparaciones con arquitecturas tradicionales. Cada ejercicio incluye soluciones y ejemplos de código para una experiencia práctica.

Ejercicio 1: Comprendiendo la Codificación Posicional

Tarea: Escribir una función en Python para generar codificaciones posicionales para una secuencia de longitud n y dimensión de incrustación dmodel. Visualizar los valores de codificación posicional para una longitud de secuencia de 10 y dimensión de incrustación de 16.

Solución:

import numpy as np
import matplotlib.pyplot as plt

def positional_encoding(sequence_length, d_model):
    """
    Generate positional encoding for a sequence.
    sequence_length: Length of the sequence
    d_model: Dimensionality of embeddings
    """
    pos = np.arange(sequence_length)[:, np.newaxis]  # Positions
    i = np.arange(d_model)[np.newaxis, :]  # Embedding dimensions
    angle_rates = 1 / np.power(10000, (2 * (i // 2)) / d_model)
    angle_rads = pos * angle_rates

    # Apply sine to even indices, cosine to odd indices
    pos_encoding = np.zeros_like(angle_rads)
    pos_encoding[:, 0::2] = np.sin(angle_rads[:, 0::2])
    pos_encoding[:, 1::2] = np.cos(angle_rads[:, 1::2])
    return pos_encoding

# Generate positional encoding
sequence_length = 10
d_model = 16
pos_encoding = positional_encoding(sequence_length, d_model)

# Visualize the positional encoding
plt.figure(figsize=(10, 6))
plt.imshow(pos_encoding, cmap='viridis')
plt.colorbar(label='Encoding Value')
plt.title('Positional Encoding Visualization')
plt.xlabel('Embedding Dimension')
plt.ylabel('Token Position')
plt.show()

Ejercicio 2: Atención de Producto Punto Escalado

Tarea: Implementar una función para la atención de producto punto escalado y aplicarla a un conjunto de datos pequeño. Imprimir los pesos de atención y la salida.

Solución:

import numpy as np

def scaled_dot_product_attention(Q, K, V):
    """
    Compute scaled dot-product attention.
    Q: Queries
    K: Keys
    V: Values
    """
    d_k = Q.shape[-1]  # Dimension of keys
    scores = np.dot(Q, K.T) / np.sqrt(d_k)  # Scaled dot product
    weights = np.exp(scores) / np.sum(np.exp(scores), axis=-1, keepdims=True)  # Softmax
    output = np.dot(weights, V)  # Weighted sum of values
    return output, weights

# Example inputs
Q = np.array([[1, 0, 1]])
K = np.array([[1, 0, 1], [0, 1, 0], [1, 1, 0]])
V = np.array([[0.5, 1.0], [0.2, 0.8], [0.9, 0.3]])

output, weights = scaled_dot_product_attention(Q, K, V)
print("Attention Weights:\n", weights)
print("Attention Output:\n", output)

Salida Esperada:

Attention Weights:
 [[0.57611688 0.21194156 0.21194156]]
Attention Output:
 [[0.67394156 0.55611688]]

Ejercicio 3: Comparación de Salidas de RNN y Transformer

Tarea: Crear un modelo RNN simple y un modelo Transformer. Usar ambos modelos para procesar la misma secuencia de entrada y comparar sus salidas. Por simplicidad, usar PyTorch.

Solución:

import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer

# Define a simple RNN
class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        out, _ = self.rnn(x)
        return self.fc(out[:, -1, :])

# RNN parameters
input_size = 10
hidden_size = 20
output_size = 10
sequence_length = 5
batch_size = 1

# Initialize and process input with RNN
rnn_model = SimpleRNN(input_size, hidden_size, output_size)
rnn_input = torch.randn(batch_size, sequence_length, input_size)
rnn_output = rnn_model(rnn_input)
print("RNN Output Shape:", rnn_output.shape)

# Transformer: Use pre-trained BERT
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")

# Input for Transformer
text = "The cat sat on the mat."
inputs = tokenizer(text, return_tensors="pt")
bert_output = bert_model(**inputs)
print("Transformer Output Shape:", bert_output.last_hidden_state.shape)

Ejercicio 4: Interacción Codificador-Decodificador

Tarea: Simular una interacción codificador-decodificador implementando componentes simples de codificador y decodificador. Pasar datos a través de ambos e imprimir la salida final.

Solución:

class Encoder(nn.Module):
    def __init__(self, input_dim, hidden_dim):
        super(Encoder, self).__init__()
        self.fc = nn.Linear(input_dim, hidden_dim)

    def forward(self, x):
        return torch.relu(self.fc(x))

class Decoder(nn.Module):
    def __init__(self, hidden_dim, output_dim):
        super(Decoder, self).__init__()
        self.fc = nn.Linear(hidden_dim, output_dim)

    def forward(self, x, encoder_output):
        combined = x + encoder_output  # Simple interaction
        return torch.sigmoid(self.fc(combined))

# Encoder-Decoder parameters
input_dim = 10
hidden_dim = 20
output_dim = 5
sequence_length = 6

# Initialize models
encoder = Encoder(input_dim, hidden_dim)
decoder = Decoder(hidden_dim, output_dim)

# Dummy input
x = torch.randn(sequence_length, input_dim)
encoder_output = encoder(x)
decoder_output = decoder(x, encoder_output)

print("Encoder Output Shape:", encoder_output.shape)
print("Decoder Output Shape:", decoder_output.shape)

Estos ejercicios proporcionan una experiencia práctica integral con los conceptos cubiertos en el Capítulo 4, como la codificación posicional, los mecanismos de atención y las interacciones codificador-decodificador. Al completar estas tareas, obtendrás una comprensión más profunda de la arquitectura Transformer y sus ventajas sobre los modelos tradicionales.

Ejercicios Prácticos del Capítulo 4

Estos ejercicios prácticos están diseñados para reforzar tu comprensión de los conceptos fundamentales discutidos en el Capítulo 4, incluyendo los principios básicos de la arquitectura Transformer, sus componentes y comparaciones con arquitecturas tradicionales. Cada ejercicio incluye soluciones y ejemplos de código para una experiencia práctica.

Ejercicio 1: Comprendiendo la Codificación Posicional

Tarea: Escribir una función en Python para generar codificaciones posicionales para una secuencia de longitud n y dimensión de incrustación dmodel. Visualizar los valores de codificación posicional para una longitud de secuencia de 10 y dimensión de incrustación de 16.

Solución:

import numpy as np
import matplotlib.pyplot as plt

def positional_encoding(sequence_length, d_model):
    """
    Generate positional encoding for a sequence.
    sequence_length: Length of the sequence
    d_model: Dimensionality of embeddings
    """
    pos = np.arange(sequence_length)[:, np.newaxis]  # Positions
    i = np.arange(d_model)[np.newaxis, :]  # Embedding dimensions
    angle_rates = 1 / np.power(10000, (2 * (i // 2)) / d_model)
    angle_rads = pos * angle_rates

    # Apply sine to even indices, cosine to odd indices
    pos_encoding = np.zeros_like(angle_rads)
    pos_encoding[:, 0::2] = np.sin(angle_rads[:, 0::2])
    pos_encoding[:, 1::2] = np.cos(angle_rads[:, 1::2])
    return pos_encoding

# Generate positional encoding
sequence_length = 10
d_model = 16
pos_encoding = positional_encoding(sequence_length, d_model)

# Visualize the positional encoding
plt.figure(figsize=(10, 6))
plt.imshow(pos_encoding, cmap='viridis')
plt.colorbar(label='Encoding Value')
plt.title('Positional Encoding Visualization')
plt.xlabel('Embedding Dimension')
plt.ylabel('Token Position')
plt.show()

Ejercicio 2: Atención de Producto Punto Escalado

Tarea: Implementar una función para la atención de producto punto escalado y aplicarla a un conjunto de datos pequeño. Imprimir los pesos de atención y la salida.

Solución:

import numpy as np

def scaled_dot_product_attention(Q, K, V):
    """
    Compute scaled dot-product attention.
    Q: Queries
    K: Keys
    V: Values
    """
    d_k = Q.shape[-1]  # Dimension of keys
    scores = np.dot(Q, K.T) / np.sqrt(d_k)  # Scaled dot product
    weights = np.exp(scores) / np.sum(np.exp(scores), axis=-1, keepdims=True)  # Softmax
    output = np.dot(weights, V)  # Weighted sum of values
    return output, weights

# Example inputs
Q = np.array([[1, 0, 1]])
K = np.array([[1, 0, 1], [0, 1, 0], [1, 1, 0]])
V = np.array([[0.5, 1.0], [0.2, 0.8], [0.9, 0.3]])

output, weights = scaled_dot_product_attention(Q, K, V)
print("Attention Weights:\n", weights)
print("Attention Output:\n", output)

Salida Esperada:

Attention Weights:
 [[0.57611688 0.21194156 0.21194156]]
Attention Output:
 [[0.67394156 0.55611688]]

Ejercicio 3: Comparación de Salidas de RNN y Transformer

Tarea: Crear un modelo RNN simple y un modelo Transformer. Usar ambos modelos para procesar la misma secuencia de entrada y comparar sus salidas. Por simplicidad, usar PyTorch.

Solución:

import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer

# Define a simple RNN
class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        out, _ = self.rnn(x)
        return self.fc(out[:, -1, :])

# RNN parameters
input_size = 10
hidden_size = 20
output_size = 10
sequence_length = 5
batch_size = 1

# Initialize and process input with RNN
rnn_model = SimpleRNN(input_size, hidden_size, output_size)
rnn_input = torch.randn(batch_size, sequence_length, input_size)
rnn_output = rnn_model(rnn_input)
print("RNN Output Shape:", rnn_output.shape)

# Transformer: Use pre-trained BERT
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")

# Input for Transformer
text = "The cat sat on the mat."
inputs = tokenizer(text, return_tensors="pt")
bert_output = bert_model(**inputs)
print("Transformer Output Shape:", bert_output.last_hidden_state.shape)

Ejercicio 4: Interacción Codificador-Decodificador

Tarea: Simular una interacción codificador-decodificador implementando componentes simples de codificador y decodificador. Pasar datos a través de ambos e imprimir la salida final.

Solución:

class Encoder(nn.Module):
    def __init__(self, input_dim, hidden_dim):
        super(Encoder, self).__init__()
        self.fc = nn.Linear(input_dim, hidden_dim)

    def forward(self, x):
        return torch.relu(self.fc(x))

class Decoder(nn.Module):
    def __init__(self, hidden_dim, output_dim):
        super(Decoder, self).__init__()
        self.fc = nn.Linear(hidden_dim, output_dim)

    def forward(self, x, encoder_output):
        combined = x + encoder_output  # Simple interaction
        return torch.sigmoid(self.fc(combined))

# Encoder-Decoder parameters
input_dim = 10
hidden_dim = 20
output_dim = 5
sequence_length = 6

# Initialize models
encoder = Encoder(input_dim, hidden_dim)
decoder = Decoder(hidden_dim, output_dim)

# Dummy input
x = torch.randn(sequence_length, input_dim)
encoder_output = encoder(x)
decoder_output = decoder(x, encoder_output)

print("Encoder Output Shape:", encoder_output.shape)
print("Decoder Output Shape:", decoder_output.shape)

Estos ejercicios proporcionan una experiencia práctica integral con los conceptos cubiertos en el Capítulo 4, como la codificación posicional, los mecanismos de atención y las interacciones codificador-decodificador. Al completar estas tareas, obtendrás una comprensión más profunda de la arquitectura Transformer y sus ventajas sobre los modelos tradicionales.

Ejercicios Prácticos del Capítulo 4

Estos ejercicios prácticos están diseñados para reforzar tu comprensión de los conceptos fundamentales discutidos en el Capítulo 4, incluyendo los principios básicos de la arquitectura Transformer, sus componentes y comparaciones con arquitecturas tradicionales. Cada ejercicio incluye soluciones y ejemplos de código para una experiencia práctica.

Ejercicio 1: Comprendiendo la Codificación Posicional

Tarea: Escribir una función en Python para generar codificaciones posicionales para una secuencia de longitud n y dimensión de incrustación dmodel. Visualizar los valores de codificación posicional para una longitud de secuencia de 10 y dimensión de incrustación de 16.

Solución:

import numpy as np
import matplotlib.pyplot as plt

def positional_encoding(sequence_length, d_model):
    """
    Generate positional encoding for a sequence.
    sequence_length: Length of the sequence
    d_model: Dimensionality of embeddings
    """
    pos = np.arange(sequence_length)[:, np.newaxis]  # Positions
    i = np.arange(d_model)[np.newaxis, :]  # Embedding dimensions
    angle_rates = 1 / np.power(10000, (2 * (i // 2)) / d_model)
    angle_rads = pos * angle_rates

    # Apply sine to even indices, cosine to odd indices
    pos_encoding = np.zeros_like(angle_rads)
    pos_encoding[:, 0::2] = np.sin(angle_rads[:, 0::2])
    pos_encoding[:, 1::2] = np.cos(angle_rads[:, 1::2])
    return pos_encoding

# Generate positional encoding
sequence_length = 10
d_model = 16
pos_encoding = positional_encoding(sequence_length, d_model)

# Visualize the positional encoding
plt.figure(figsize=(10, 6))
plt.imshow(pos_encoding, cmap='viridis')
plt.colorbar(label='Encoding Value')
plt.title('Positional Encoding Visualization')
plt.xlabel('Embedding Dimension')
plt.ylabel('Token Position')
plt.show()

Ejercicio 2: Atención de Producto Punto Escalado

Tarea: Implementar una función para la atención de producto punto escalado y aplicarla a un conjunto de datos pequeño. Imprimir los pesos de atención y la salida.

Solución:

import numpy as np

def scaled_dot_product_attention(Q, K, V):
    """
    Compute scaled dot-product attention.
    Q: Queries
    K: Keys
    V: Values
    """
    d_k = Q.shape[-1]  # Dimension of keys
    scores = np.dot(Q, K.T) / np.sqrt(d_k)  # Scaled dot product
    weights = np.exp(scores) / np.sum(np.exp(scores), axis=-1, keepdims=True)  # Softmax
    output = np.dot(weights, V)  # Weighted sum of values
    return output, weights

# Example inputs
Q = np.array([[1, 0, 1]])
K = np.array([[1, 0, 1], [0, 1, 0], [1, 1, 0]])
V = np.array([[0.5, 1.0], [0.2, 0.8], [0.9, 0.3]])

output, weights = scaled_dot_product_attention(Q, K, V)
print("Attention Weights:\n", weights)
print("Attention Output:\n", output)

Salida Esperada:

Attention Weights:
 [[0.57611688 0.21194156 0.21194156]]
Attention Output:
 [[0.67394156 0.55611688]]

Ejercicio 3: Comparación de Salidas de RNN y Transformer

Tarea: Crear un modelo RNN simple y un modelo Transformer. Usar ambos modelos para procesar la misma secuencia de entrada y comparar sus salidas. Por simplicidad, usar PyTorch.

Solución:

import torch
import torch.nn as nn
from transformers import BertModel, BertTokenizer

# Define a simple RNN
class SimpleRNN(nn.Module):
    def __init__(self, input_size, hidden_size, output_size):
        super(SimpleRNN, self).__init__()
        self.rnn = nn.RNN(input_size, hidden_size, batch_first=True)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        out, _ = self.rnn(x)
        return self.fc(out[:, -1, :])

# RNN parameters
input_size = 10
hidden_size = 20
output_size = 10
sequence_length = 5
batch_size = 1

# Initialize and process input with RNN
rnn_model = SimpleRNN(input_size, hidden_size, output_size)
rnn_input = torch.randn(batch_size, sequence_length, input_size)
rnn_output = rnn_model(rnn_input)
print("RNN Output Shape:", rnn_output.shape)

# Transformer: Use pre-trained BERT
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
bert_model = BertModel.from_pretrained("bert-base-uncased")

# Input for Transformer
text = "The cat sat on the mat."
inputs = tokenizer(text, return_tensors="pt")
bert_output = bert_model(**inputs)
print("Transformer Output Shape:", bert_output.last_hidden_state.shape)

Ejercicio 4: Interacción Codificador-Decodificador

Tarea: Simular una interacción codificador-decodificador implementando componentes simples de codificador y decodificador. Pasar datos a través de ambos e imprimir la salida final.

Solución:

class Encoder(nn.Module):
    def __init__(self, input_dim, hidden_dim):
        super(Encoder, self).__init__()
        self.fc = nn.Linear(input_dim, hidden_dim)

    def forward(self, x):
        return torch.relu(self.fc(x))

class Decoder(nn.Module):
    def __init__(self, hidden_dim, output_dim):
        super(Decoder, self).__init__()
        self.fc = nn.Linear(hidden_dim, output_dim)

    def forward(self, x, encoder_output):
        combined = x + encoder_output  # Simple interaction
        return torch.sigmoid(self.fc(combined))

# Encoder-Decoder parameters
input_dim = 10
hidden_dim = 20
output_dim = 5
sequence_length = 6

# Initialize models
encoder = Encoder(input_dim, hidden_dim)
decoder = Decoder(hidden_dim, output_dim)

# Dummy input
x = torch.randn(sequence_length, input_dim)
encoder_output = encoder(x)
decoder_output = decoder(x, encoder_output)

print("Encoder Output Shape:", encoder_output.shape)
print("Decoder Output Shape:", decoder_output.shape)

Estos ejercicios proporcionan una experiencia práctica integral con los conceptos cubiertos en el Capítulo 4, como la codificación posicional, los mecanismos de atención y las interacciones codificador-decodificador. Al completar estas tareas, obtendrás una comprensión más profunda de la arquitectura Transformer y sus ventajas sobre los modelos tradicionales.