single
Défi : Construire un Classificateur de Texte avec un Transformer
Glissez pour afficher le menu
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)
Vous venez de découvrir une implémentation minimale d’un classificateur de texte basé sur un Transformer, utilisant uniquement numpy et les fonctionnalités de base de Python. Ce code illustre comment assembler les éléments essentiels d’un Transformer pour une tâche de classification de texte, sans recourir à des bibliothèques de deep learning.
Le processus commence par la conversion du texte brut en un format adapté au calcul. La fonction tokenize découpe le texte en mots minuscules :
def tokenize(text):
return text.lower().split()
Ensuite, build_vocab crée une correspondance entre chaque mot unique et un entier unique :
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 fonction text_to_sequence transforme chaque phrase d’entrée en une séquence d’indices de mots de longueur fixe, en complétant ou tronquant si nécessaire :
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
Pour donner au modèle la notion d’ordre des mots, positional_encoding génère une matrice de valeurs sinusoïdales à ajouter aux embeddings de mots. Cette étape est cruciale car, contrairement aux réseaux récurrents, les Transformers ne traitent pas intrinsèquement les séquences dans l’ordre :
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
Le mécanisme d’auto-attention, implémenté dans self_attention, permet au modèle de pondérer l’importance de chaque mot dans le contexte de toute la séquence. Cela se fait en calculant des scores de similarité entre toutes les paires de mots, en appliquant la fonction softmax pour obtenir des poids d’attention, puis en créant une nouvelle représentation pour chaque mot comme somme pondérée de tous les mots de la séquence :
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)
Après l’auto-attention, un réseau feed-forward simple (feed_forward) traite davantage la séquence :
def feed_forward(X, W1, b1, W2, b2):
return np.maximum(0, X @ W1 + b1) @ W2 + b2
La fonction transformer_encoder combine ces étapes, en appliquant l’encodage positionnel, l’auto-attention et le réseau feed-forward à la suite :
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 classe TransformerClassifier regroupe l’ensemble. Elle initialise des embeddings de mots aléatoires, les poids des couches et les encodages positionnels. Dans la méthode forward, elle récupère les embeddings pour chaque token, applique le bloc encodeur, effectue un pooling par moyenne sur la séquence, puis passe le résultat dans une couche linéaire finale pour produire les probabilités de classe :
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
Un exemple est présenté à la fin du code, où un petit ensemble de textes d’entraînement est utilisé pour construire le vocabulaire et initialiser le classificateur. Lorsqu’une nouvelle phrase est saisie, le modèle prédit les probabilités de classe, illustrant ainsi le fonctionnement du pipeline Transformer minimal, du texte brut à la prédiction :
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)
Glissez pour commencer à coder
Construire un classificateur de texte minimal basé sur Transformer étape par étape :
- Écrire une fonction
tokenizequi prend une chaîne de caractères et retourne une liste de mots en minuscules ; - Écrire une fonction
build_vocabqui prend une liste de textes et retourne un dictionnaire associant chaque mot unique à un indice entier (commencer l'indexation à 1, réserver 0 pour le padding) ; - Écrire une fonction
text_to_sequencequi convertit un texte en une liste d'indices de mots à l'aide d'un vocabulaire et effectue un padding/troncature à une longueur fixe.
Testez vos fonctions avec :
tokenize("Hello World!")doit retourner["hello", "world!"];build_vocab(["Hello world", "world of NLP"])doit retourner un dictionnaire associant les mots à des indices uniques (par exemple,{"hello": 1, "world": 2, "of": 3, "nlp": 4}) ;text_to_sequence("Hello NLP", vocab, 4)doit retourner une liste de 4 indices, complétée par des zéros si nécessaire.
Solution
Merci pour vos commentaires !
single
Demandez à l'IA
Demandez à l'IA
Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion