single
Challenge: Construcción de un Clasificador de Texto con Transformers
Desliza para mostrar el menú
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import numpy as np # Simple tokenizer: splits text into lowercase words def tokenize(text): return text.lower().split() # Build vocabulary from training texts def build_vocab(texts): vocab = {} idx = 1 # 0 reserved for padding for text in texts: for word in tokenize(text): if word not in vocab: vocab[word] = idx idx += 1 return vocab # Convert text to sequence of word indices def text_to_sequence(text, vocab, max_len): seq = [vocab.get(word, 0) for word in tokenize(text)] if len(seq) < max_len: seq += [0] * (max_len - len(seq)) else: seq = seq[:max_len] return np.array(seq) # Sinusoidal positional encoding def positional_encoding(seq_len, d_model): pos = np.arange(seq_len)[:, np.newaxis] i = np.arange(d_model)[np.newaxis, :] angle_rates = 1 / np.power(10000, (2 * (i // 2)) / np.float32(d_model)) angle_rads = pos * angle_rates pe = np.zeros((seq_len, d_model)) pe[:, 0::2] = np.sin(angle_rads[:, 0::2]) pe[:, 1::2] = np.cos(angle_rads[:, 1::2]) return pe # Simple self-attention layer def self_attention(X): d_k = X.shape[-1] Q = X K = X V = X scores = Q @ K.T / np.sqrt(d_k) weights = softmax(scores) return weights @ V def softmax(x): e_x = np.exp(x - np.max(x, axis=-1, keepdims=True)) return e_x / np.sum(e_x, axis=-1, keepdims=True) # Feed-forward layer def feed_forward(X, W1, b1, W2, b2): return np.maximum(0, X @ W1 + b1) @ W2 + b2 # Minimal Transformer encoder block def transformer_encoder(X, pe, W1, b1, W2, b2): X_pe = X + pe attn_out = self_attention(X_pe) ff_out = feed_forward(attn_out, W1, b1, W2, b2) return ff_out # Classifier class TransformerClassifier: def __init__(self, vocab_size, d_model, max_len, num_classes): self.d_model = d_model self.max_len = max_len self.embeddings = np.random.randn(vocab_size + 1, d_model) * 0.01 self.W1 = np.random.randn(d_model, d_model) * 0.01 self.b1 = np.zeros(d_model) self.W2 = np.random.randn(d_model, d_model) * 0.01 self.b2 = np.zeros(d_model) self.Wc = np.random.randn(d_model, num_classes) * 0.01 self.bc = np.zeros(num_classes) self.pe = positional_encoding(max_len, d_model) def forward(self, x_seq): X = self.embeddings[x_seq] encoded = transformer_encoder(X, self.pe, self.W1, self.b1, self.W2, self.b2) pooled = np.mean(encoded, axis=0) # Simple mean pooling logits = pooled @ self.Wc + self.bc probs = softmax(logits) return probs # Example usage: texts = ["I love machine learning", "Transformers are powerful", "Deep learning is cool"] labels = [0, 1, 0] # 2 classes: 0, 1 vocab = build_vocab(texts) max_len = 6 d_model = 8 num_classes = 2 clf = TransformerClassifier(len(vocab), d_model, max_len, num_classes) # Predict class probabilities for a new text test_text = "I love transformers" test_seq = text_to_sequence(test_text, vocab, max_len) probs = clf.forward(test_seq) print("Predicted class probabilities:", probs)
Acabas de ver una implementación mínima de un clasificador de texto basado en Transformer utilizando únicamente numpy y funciones básicas de Python. Este código demuestra cómo se pueden ensamblar las partes esenciales de un Transformer para una tarea de clasificación de texto sin depender de bibliotecas de aprendizaje profundo.
El proceso comienza convirtiendo el texto sin procesar en un formato adecuado para el cálculo. La función tokenize divide el texto en palabras en minúsculas:
def tokenize(text):
return text.lower().split()
A continuación, build_vocab crea una asignación de cada palabra única a un entero único:
def build_vocab(texts):
vocab = {}
idx = 1 # 0 reserved for padding
for text in texts:
for word in tokenize(text):
if word not in vocab:
vocab[word] = idx
idx += 1
return vocab
La función text_to_sequence convierte cada oración de entrada en una secuencia de longitud fija de índices de palabras, rellenando o truncando según sea necesario:
def text_to_sequence(text, vocab, max_len):
seq = [vocab.get(word, 0) for word in tokenize(text)]
if len(seq) < max_len:
seq += [0] * (max_len - len(seq))
else:
seq = seq[:max_len]
return seq
Para dar al modelo conciencia del orden de las palabras, positional_encoding genera una matriz de valores sinusoidales que se pueden sumar a las incrustaciones de palabras. Este paso es crucial porque, a diferencia de las redes recurrentes, los Transformers no procesan secuencias en orden de forma inherente:
def positional_encoding(seq_len, d_model):
pos = np.arange(seq_len)[:, np.newaxis]
i = np.arange(d_model)[np.newaxis, :]
angle_rates = 1 / np.power(10000, (2 * (i // 2)) / np.float32(d_model))
angle_rads = pos * angle_rates
pe = np.zeros((seq_len, d_model))
pe[:, 0::2] = np.sin(angle_rads[:, 0::2])
pe[:, 1::2] = np.cos(angle_rads[:, 1::2])
return pe
El mecanismo de self-attention, implementado en self_attention, permite que el modelo valore la importancia de cada palabra en el contexto de toda la secuencia. Esto se logra calculando puntuaciones de similitud entre todos los pares de palabras, aplicando la función softmax para obtener los pesos de atención y luego creando una nueva representación para cada palabra como una suma ponderada de todas las palabras de la secuencia:
def self_attention(X):
d_k = X.shape[-1]
Q = X
K = X
V = X
scores = Q @ K.T / np.sqrt(d_k)
weights = softmax(scores)
return weights @ V
def softmax(x):
e_x = np.exp(x - np.max(x, axis=-1, keepdims=True))
return e_x / np.sum(e_x, axis=-1, keepdims=True)
Después de la self-attention, una red feed-forward simple (feed_forward) procesa aún más la secuencia:
def feed_forward(X, W1, b1, W2, b2):
return np.maximum(0, X @ W1 + b1) @ W2 + b2
La función transformer_encoder combina estos pasos, aplicando codificación posicional, self-attention y la red feed-forward en secuencia:
def transformer_encoder(X, pe, W1, b1, W2, b2):
X_pe = X + pe
attn_out = self_attention(X_pe)
ff_out = feed_forward(attn_out, W1, b1, W2, b2)
return ff_out
La clase TransformerClassifier integra todos los componentes. Inicializa aleatoriamente las incrustaciones de palabras, los pesos de las capas y las codificaciones posicionales. En el método forward, busca las incrustaciones para cada token, aplica el bloque codificador, agrupa las salidas promediando a lo largo de la secuencia y pasa el resultado por una capa lineal final para producir probabilidades de clase:
class TransformerClassifier:
def __init__(self, vocab_size, d_model, max_len, num_classes):
self.d_model = d_model
self.max_len = max_len
self.embeddings = np.random.randn(vocab_size + 1, d_model) * 0.01
self.W1 = np.random.randn(d_model, d_model) * 0.01
self.b1 = np.zeros(d_model)
self.W2 = np.random.randn(d_model, d_model) * 0.01
self.b2 = np.zeros(d_model)
self.Wc = np.random.randn(d_model, num_classes) * 0.01
self.bc = np.zeros(num_classes)
self.pe = positional_encoding(max_len, d_model)
def forward(self, x_seq):
X = self.embeddings[x_seq]
encoded = transformer_encoder(X, self.pe, self.W1, self.b1, self.W2, self.b2)
pooled = np.mean(encoded, axis=0) # Simple mean pooling
logits = pooled @ self.Wc + self.bc
probs = softmax(logits)
return probs
Puedes ver un ejemplo al final del código donde se utiliza un pequeño conjunto de textos de entrenamiento para construir el vocabulario y configurar el clasificador. Cuando introduces una nueva oración, el modelo predice las probabilidades de clase, mostrando cómo funciona el flujo mínimo de un Transformer desde el texto sin procesar hasta la predicción:
texts = ["I love machine learning", "Transformers are powerful", "Deep learning is cool"]
labels = [0, 1, 0] # 2 classes: 0, 1
vocab = build_vocab(texts)
max_len = 6
d_model = 8
num_classes = 2
clf = TransformerClassifier(len(vocab), d_model, max_len, num_classes)
test_text = "I love transformers"
test_seq = text_to_sequence(test_text, vocab, max_len)
probs = clf.forward(test_seq)
print("Predicted class probabilities:", probs)
Desliza para comenzar a programar
Construcción paso a paso de un clasificador de texto mínimo basado en Transformer:
- Función
tokenizeque recibe una cadena y devuelve una lista de palabras en minúsculas; - Función
build_vocabque recibe una lista de textos y devuelve un diccionario que asigna a cada palabra única un índice entero (comenzando desde 1, reservando el 0 para padding); - Función
text_to_sequenceque convierte un texto en una lista de índices de palabras utilizando un vocabulario y rellena/trunca a una longitud fija.
Prueba tus funciones con:
tokenize("Hello World!")debe devolver["hello", "world!"];build_vocab(["Hello world", "world of NLP"])debe devolver un diccionario que asigne palabras a índices únicos (por ejemplo,{"hello": 1, "world": 2, "of": 3, "nlp": 4});text_to_sequence("Hello NLP", vocab, 4)debe devolver una lista de 4 índices, rellenando con ceros si es necesario.
Solución
¡Gracias por tus comentarios!
single
Pregunte a AI
Pregunte a AI
Pregunte lo que quiera o pruebe una de las preguntas sugeridas para comenzar nuestra charla