Plongement de mots PyTorch

Le plongement de mots est l'une des techniques les plus fondamentales et les plus importantes du traitement du langage naturel.

Le plongement de mots transforme les symboles de mots discrets en vecteurs denses continus, permettant aux machines de comprendre et de traiter les données textuelles.

PyTorch fournitnn.Embeddingmodule pour implémenter cette fonctionnalité, servant de base à la construction de divers modèles NLP.


1. Concepts de base du plongement de mots

Dans un ordinateur, le texte est essentiellement une séquence d'entiers. Chaque mot se voit attribuer un ID d'index unique, mais cette représentation discrète pose un problème : des mots sémantiquement similaires peuvent être proches par le sens, mais leurs ID n'ont absolument aucun rapport.

Le plongement de mots résout ce problème en apprenant unematrice de plongement:

\[ E \in \mathbb{R}^{V \times D} \]

Où \(V\) est la taille du vocabulaire et \(D\) est la dimension de plongement. Chaque ID de mot correspond à une ligne de la matrice de plongement, et sa représentation vectorielle est obtenue via une opération de recherche :

\[ \text{embedding} = E[\text{word\_id}] \]

Les avantages du plongement de mots incluent :

  • Convertir des vecteurs one-hot creux de grande dimension en vecteurs denses de faible dimension, ce qui réduit considérablement le coût de calcul
  • Les mots sémantiquement similaires sont plus proches dans l'espace vectoriel, et la similarité entre mots peut être calculée via la similarité cosinus
  • Les vecteurs de plongement sont des paramètres apprenables et peuvent être ajustés automatiquement via la rétropropagation

2. nn.Embedding expliqué en détail

nn.Embeddingest la couche d'embedding de mots fournie par PyTorch, encapsulant la création de la matrice d'embedding et l'opération de recherche.

2.1 Utilisation de base

Exemple

import torch
import torch.nn as nn

# Créer une couche d'embedding
# num_embeddings : taille du vocabulaire (vocab_size)
# embedding_dim : dimension d'embedding (embedding_dim)
vocab_size = 10000
embedding_dim = 256

embedding = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)

# Afficher la forme de la matrice d'embedding
print(embedding.weight.shape)   # torch.Size([10000, 256])

# Indices de mots en entrée (LongTensor) pour obtenir des vecteurs d'embedding
word_ids = torch.tensor([0, 1, 2, 9999])  # Indices de mots arbitraires
embedded = embedding(word_ids)

print(embedded.shape)           # torch.Size([4, 256])
# Chaque ID de mot correspond à un vecteur de 256 dimensions

2.2 Détails des paramètres de nn.Embedding

Exemple

import torch.nn as nn

embedding = nn.Embedding(
    num_embeddings=10000,    # Taille du vocabulaire, doit être supérieure ou égale à la valeur d'index maximale dans l'entrée
    embedding_dim=256,       # Dimension du vecteur d'embedding, généralement 50, 100, 200, 300, etc.
    padding_idx=None,        # Index du mot de remplissage ; le vecteur d'embedding pour le mot de remplissage est entièrement composé de zéros
    max_norm=None,          # Norme maximale du vecteur d'embedding, utilisée pour la normalisation
    norm_type=2.0,          # Type de normalisation, généralement la norme L2
    scale_grad_by_freq=False,# Met à l'échelle les gradients en fonction de la fréquence des mots
    sparse=False,           # Indique s'il faut utiliser des gradients creux (économise la mémoire GPU, mais l'entraînement est plus lent)
    _weight=None,           # Poids prédéfinis, utilisés pour charger des embeddings pré-entraînés
)

# Afficher le nombre de paramètres
total_params = embedding.num_embeddings * embedding.embedding_dim
print(f"Nombre de paramètres de la couche d'embedding : {total_params:,}")
# 10000 * 256 = 2,560,000

Le nombre de paramètres dans la couche d'embedding = taille du vocabulaire × dimension d'embedding. Il s'agit d'une très grande matrice. Habituellement, la couche d'embedding d'un modèle NLP représente une grande proportion du nombre total de paramètres du modèle.

2.3 Indice de padding padding_idx

Lors du traitement de séquences de longueur variable, les séquences courtes doivent être complétées. L'utilisation depadding_idxpeut fixer le vecteur d'embedding du mot de padding à un vecteur nul, empêchant le contenu de padding d'affecter le modèle :

Exemple

import torch
import torch.nn as nn

# Définir padding_idx=0, ce qui signifie que l'indice 0 est le mot de padding
embedding = nn.Embedding(num_embeddings=10000, embedding_dim=128, padding_idx=0)

# Initialiser les poids
nn.init.uniform_(embedding.weight, -0.1, 0.1)

# Le vecteur d'embedding à l'indice 0 est entièrement nul
word_0 = embedding(torch.tensor([0]))
print(f"Embedding du mot de remplissage : {word_0}")   # Tous les zéros

# Les autres indices restent normaux
word_5 = embedding(torch.tensor([5]))
print(f"Embedding du mot 5 : {word_5}")     # Valeurs non nulles

2.4 Normalisation par norme maximale

Utilisationmax_normpeut limiter la norme des vecteurs d'embedding, en les empêchant de devenir trop grands pendant l'entraînement :

Exemple

import torch
import torch.nn as nn

# Définir la norme maximale à 1.0
embedding = nn.Embedding(num_embeddings=1000, embedding_dim=64, max_norm=1.0)

# Saisir des indices de mots arbitraires
ids = torch.tensor([1, 2, 3])
embedded = embedding(ids)

# Vérifier la norme L2 de chaque vecteur
norms = torch.norm(embedded, p=2, dim=1)
print(f"Normes de chaque vecteur : {norms}")   # Toutes les valeurs sont proches de 1.0

3. Chargement de plongements de mots pré-entraînés

L'utilisation de plongements de mots pré-entraînés peut considérablement améliorer les performances du modèle, en particulier lorsque les données d'entraînement sont rares. Les plongements de mots pré-entraînés courants incluent Word2Vec, GloVe, FastText, etc.

3.1 Différence entre l'entraînement à partir de zéro et le chargement de plongements pré-entraînés

Méthode Avantages Inconvénients Scénarios applicables
Initialisation aléatoire et entraînement Entièrement personnalisable, s'adapte à des tâches spécifiques Nécessite une grande quantité de données d'entraînement De nombreux mots spécifiques au domaine et suffisamment de données d'entraînement
Chargement de plongements pré-entraînés Tire parti des connaissances de corpus à grande échelle, entraînement rapide et bons résultats Couverture limitée du vocabulaire, ne peut pas gérer les mots hors vocabulaire Tâches générales, données d'entraînement limitées
Geler les embeddings préentraînés Entraînement rapide, faible utilisation de la mémoire GPU Les embeddings ne peuvent pas être affinés Ressources d'entraînement limitées, en se concentrant uniquement sur les couches supérieures du modèle
Affiner les embeddings préentraînés Peut s'adapter à des tâches spécifiques Entraînement plus lent, utilisation élevée de la mémoire GPU Taille de données modérée, certaines différences de domaine

3.2 Chargement des vecteurs de mots préentraînés GloVe

GloVe est un vecteur de mots préentraîné publié par l'université Stanford. Voici comment le charger :

Exemple

import torch
import torch.nn as nn
import numpy as np

# Simuler le chargement des vecteurs de mots GloVe (il faut en réalité télécharger les fichiers GloVe)
# Supposons qu'il existe déjà un fichier de vecteurs de mots au format : chaque ligne contient un mot suivi de ses vecteurs

def load_glove_embeddings(path, word2idx, embedding_dim=300):
    """
Charger les vecteurs de mots pré-entraînés GloVe
path: chemin vers le fichier de vecteurs de mots
word2idx: dictionnaire associant les mots aux indices
embedding_dim: dimension des vecteurs de mots
    """

    embeddings = np.random.randn(len(word2idx), embedding_dim).astype(np.float32)
    word_count = 0

    with open(path, 'r', encoding='utf-8') as f:
        for line in f:
            values = line.strip().split()
            word = values[0]
            if word in word2idx:
                word_idx = word2idx[word]
                embeddings[word_idx] = np.asarray(values[1:], dtype=np.float32)
                word_count += 1

    print(f"{word_count}/{len(word2idx)} vecteurs de mots chargés")
    return torch.from_numpy(embeddings)


# On suppose qu'il existe déjà un vocabulaire
word2idx = {'hello': 0, 'world': 1, 'example': 2, 'python': 3}
EMBED_DIM = 300

# Charger les embeddings pré-entraînés et créer une couche d'embedding
# pretrained_embeddings = load_glove_embeddings('glove.6B.300d.txt', word2idx, EMBED_DIM)
# embedding = nn.Embedding.from_pretrained(pretrained_embeddings, padding_idx=0)

# Exemple simplifié : utiliser une matrice pré-entraînée initialisée aléatoirement
pretrained_embeddings = torch.randn(len(word2idx), EMBED_DIM)
embedding = nn.Embedding.from_pretrained(pretrained_embeddings, padding_idx=0)

print(f"Forme de la couche d'embedding : {embedding.weight.shape}")

3.3 Gel et ajustement fin de la couche d'embedding

Selon les exigences de la tâche, vous pouvez choisir de geler ou d'affiner la couche d'embedding :

Exemple

import torch
import torch.nn as nn

embedding = nn.Embedding(num_embeddings=10000, embedding_dim=256)

# Méthode 1 : geler la couche d'embedding (ne participe pas à l'entraînement)
embedding.weight.requires_grad = False
# Pendant l'entraînement, seuls les paramètres avec embedding.weight.requires_grad = False ne seront pas mis à jour

# Méthode 2 : affiner la couche d'embedding (participe à l'entraînement)
embedding.weight.requires_grad = True

# Méthode 3 : geler une partie des vecteurs de mots (geler les mots très fréquents comme "the", "is")
# Supposons que les indices des mots à haute fréquence soient 0-99
embedding.weight.requires_grad = True
with torch.no_grad():
    embedding.weight[0:100] *= 0  # Ou attribuer des valeurs fixes

# Exclure les paramètres gelés dans l'optimiseur
optimizer = torch.optim.Adam(
    filter(lambda p: p.requires_grad, embedding.parameters()),
    lr=1e-3
)

4. Combinaison de la couche d'embedding avec RNN/LSTM

L'embedding de mots est la première couche d'un modèle NLP. Il convertit les identifiants de texte en vecteurs denses, qui sont ensuite transmis à des modèles de séquence tels que RNN et LSTM pour traitement.

4.1 Couche d'embedding + Classification de texte par LSTM

Exemple

import torch
import torch.nn as nn

class TextClassifier(nn.Module):
    """
Modèle de classification de texte avec couche d'embedding + LSTM
    """

    def __init__(self, vocab_size, embed_dim, hidden_size, num_classes, padding_idx=0):
        super().__init__()

        # Couche de plongement de mots
        self.embedding = nn.Embedding(
            num_embeddings=vocab_size,
            embedding_dim=embed_dim,
            padding_idx=padding_idx
        )

        # Couche LSTM
        self.lstm = nn.LSTM(
            input_size=embed_dim,
            hidden_size=hidden_size,
            num_layers=2,
            batch_first=True,
            bidirectional=True,
            dropout=0.3
        )

        # Classificateur
        self.fc = nn.Linear(hidden_size * 2, num_classes)

    def forward(self, x):
        # x: (batch_size, seq_len) - indices de mots
        embedded = self.embedding(x)  # (batch_size, seq_len, embed_dim)

        # Sortie LSTM
        output, (h_n, c_n) = self.lstm(embedded)

        # Prendre l'état caché du dernier pas de temps (concaténation bidirectionnelle)
        # Dernier état caché de la passe avant : h_n[-2]
        # Dernier état caché de la passe arrière : h_n[-1]
        h_combined = torch.cat([h_n[-2], h_n[-1]], dim=-1)

        # Classification
        logits = self.fc(h_combined)
        return logits


# Instancier le modèle
VOCAB_SIZE = 10000
EMBED_DIM = 128
HIDDEN_SIZE = 128
NUM_CLASSES = 2

model = TextClassifier(VOCAB_SIZE, EMBED_DIM, HIDDEN_SIZE, NUM_CLASSES)

# Entrée simulée : batch_size=4, longueur de séquence=10
batch_input = torch.randint(1, VOCAB_SIZE, (4, 10))
output = model(batch_input)

print(f"Forme de l'entrée : {batch_input.shape}")      # torch.Size([4, 10])
print(f"Forme de la sortie : {output.shape}")           # torch.Size([4, 2])

4.2 Utilisation de vecteurs de mots pré-entraînés

Exemple

import torch
import torch.nn as nn

# Supposons que les vecteurs de mots pré-entraînés ont été chargés
pretrained_vectors = torch.randn(10000, 300)  # Simuler des vecteurs pré-entraînés

# Créer une couche d'embedding et charger les poids pré-entraînés
embedding = nn.Embedding.from_pretrained(
    pretrained_vectors,
    padding_idx=0,
    freeze=False  # True : geler et ne pas entraîner, False : affiner
)

# Lors de l'utilisation de GloVe ou d'autres vecteurs de mots pré-entraînés, on gèle généralement d'abord pendant quelques époques, puis on dégèle pour affiner
# Pendant les premières époques, n'entraîner que les couches supérieures du modèle
embedding.weight.requires_grad = False

# Après plusieurs époques d'entraînement, dégeler la couche d'embedding pour affiner
# embedding.weight.requires_grad = True

5. Encodage positionnel

Contrairement aux RNN/LSTM, le modèle Transformer n'inclut pas d'information positionnelle. Un encodage positionnel doit être ajouté en plus pour permettre au modèle de percevoir l'ordre des mots dans une séquence.

5.1 Principe de l'encodage positionnel

L'encodage positionnel utilise les fonctions sinus et cosinus pour générer des vecteurs de position :

\[ \begin{aligned} PE_{(pos, 2i)} &= \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right) \\ PE_{(pos, 2i+1)} &= \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right) \end{aligned} \]

La particularité de cette méthode d'encodage est que les encodages à différentes positions peuvent être convertis les uns en les autres par des transformations linéaires, ce qui permet au modèle d'apprendre facilement les relations positionnelles.

5.2 Implémentation de l'encodage positionnel

Exemple

import torch
import torch.nn as nn
import math

class PositionalEncoding(nn.Module):
    """
Couche d'encodage positionnel
    """

    def __init__(self, d_model, max_len=5000):
        super().__init__()

        # Créer la matrice d'encodage positionnel
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)

        # Calculer le terme diviseur
        div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))

        # Utiliser sin pour les indices pairs et cos pour les indices impairs
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)

        # Ajouter une dimension de batch et l'enregistrer comme tampon qui ne participe pas au calcul du gradient
        pe = pe.unsqueeze(0)  # (1, max_len, d_model)
        self.register_buffer('pe', pe)

    def forward(self, x):
        """
        x: (batch_size, seq_len, d_model)
        """

        seq_len = x.size(1)
        # Découper l'encodage positionnel de la longueur correspondante et l'ajouter
        x = x + self.pe[:, :seq_len, :]
        return x


# Exemple d'utilisation
d_model = 256
max_len = 100

pos_encoding = PositionalEncoding(d_model, max_len)

# Entrée simulée : batch_size=4, seq_len=20, d_model=256
x = torch.randn(4, 20, d_model)
x = pos_encoding(x)

print(f"Forme de l'entrée : {x.shape}")   # torch.Size([4, 20, 256])

L'encodage positionnel est un composant clé de l'architecture Transformer. Il permet au modèle de distinguer les mots à différentes positions, même si leurs vecteurs d'embedding sont identiques.

5.3 Encodage positionnel apprenable

Outre l'encodage positionnel fixe, on peut également utiliser un encodage positionnel apprenable :

Exemple

import torch
import torch.nn as nn

class LearnablePositionalEncoding(nn.Module):
    """
Encodage positionnel apprenable
    """

    def __init__(self, d_model, max_len=5000):
        super().__init__()
        self.pos_embedding = nn.Embedding(max_len, d_model)

    def forward(self, x):
        batch_size, seq_len, d_model = x.size(1), x.size(1), x.size(2)
        # Créer les indices de position [0, 1, 2, ..., seq_len-1]
        positions = torch.arange(seq_len, device=x.device).unsqueeze(0).expand(batch_size, -1)
        pos_encoded = self.pos_embedding(positions)
        return x + pos_encoded


# Exemple d'utilisation
pos_encoding = LearnablePositionalEncoding(d_model=256, max_len=100)
x = torch.randn(4, 20, 256)
x = pos_encoding(x)
print(f"Forme de sortie : {x.shape}")   # torch.Size([4, 20, 256])

6. Techniques avancées pour les couches d'embedding

6.1 Réduction de l'utilisation de la mémoire GPU

Lorsque le vocabulaire est très grand, la couche d'embedding consomme beaucoup de mémoire GPU. Les techniques suivantes peuvent être utilisées pour optimiser :

Exemple

import torch
import torch.nn as nn

# Technique 1 : Utiliser des gradients creux (sparse=True)
embedding = nn.Embedding(
    num_embeddings=100000,
    embedding_dim=256,
    sparse=True  # Les gradients sont stockés dans un format sparse, ce qui économise de la mémoire GPU
)

# Technique 2 : utiliser la quantification
# Convertir float32 en int8 ou float16
embedding_int8 = embedding.to(torch.int8)

# Technique 3 : geler les vecteurs de mots rarement utilisés
# Dans un vocabulaire à grande échelle, n'entraîner que les mots à haute fréquence ; garder les mots à basse fréquence gelés
embedding = nn.Embedding(num_embeddings=100000, embedding_dim=256)
embedding.weight.requires_grad = True

# Geler les vecteurs de mots dont l'indice est supérieur à 50000
with torch.no_grad():
    embedding.weight[50000:] *= 0
embedding.weight.requires_grad = False

# N'entraîner que les mots à haute fréquence
embedding.weight[1:50000].requires_grad = True

6.2 Traitement des mots hors vocabulaire (OOV)

Les mots absents du vocabulaire (mots hors vocabulaire / OOV) peuvent apparaître dans l'ensemble de test et nécessitent un traitement particulier :

Exemple

import torch
import torch.nn as nn

class EmbeddingWithOOV(nn.Module):
    """
Couche de plongement prenant en charge les mots hors vocabulaire
    """

    def __init__(self, vocab_size, embed_dim, oov_idx=Aucune):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size + 1, embed_dim, padding_idx=0)
        self.oov_idx = oov_idx if oov_idx is not None else vocab_size  # Utiliser le dernier indice comme OOV

    def forward(self, x):
        # Remplacer les indices des mots OOV par oov_idx
        oov_mask = x >= self.vocab_size
        x = x.clone()
        x[oov_mask] = self.oov_idx
        return self.embedding(x)


# Utiliser des techniques de hachage pour gérer des vocabulaires plus grands
class HashEmbedding(nn.Module):
    """
Couche d'embedding utilisant le hachage pour gérer des vocabulaires de taille arbitraire
    """

    def __init__(self, num_buckets, embed_dim):
        super().__init__()
        self.num_buckets = num_buckets
        self.embedding = nn.Embedding(num_buckets, embed_dim)

    def forward(self, x):
        # Hacher les indices de mots dans des buckets
        # Utiliser la méthode de hachage du dictionnaire Python
        hashed = torch.remainder(x, self.num_buckets)
        return self.embedding(hashed)

6.3 Embedding de sous-mots

Pour les langues morphologiquement riches (comme l'allemand et le russe), l'embedding de sous-mots peut gérer efficacement le problème des mots hors vocabulaire :

Exemple

import torch
import torch.nn as nn

class SubwordEmbedding(nn.Module):
    """
Exemple simplifié d'embedding de sous-mots
En pratique, des algorithmes tels que BPE et WordPiece devraient être utilisés pour la tokenisation
    """

    def __init__(self, vocab_size, embed_dim, char_dim=50):
        super().__init__()
        # Embedding au niveau des caractères
        self.char_embedding = nn.Embedding(vocab_size, char_dim)
        # Embedding au niveau des mots
        self.word_embedding = nn.Embedding(vocab_size, embed_dim - char_dim)
        # LSTM au niveau des caractères pour combiner les vecteurs de caractères
        self.char_lstm = nn.LSTM(
            char_dim, char_dim,
            batch_first=True, bidirectional=True
        )

    def forward(self, word_ids, char_ids):
        """
word_ids : indices de mots (batch_size, seq_len)
char_ids : indices de caractères (batch_size, seq_len, max_word_len)
        """

        # Plongement de mots
        word_emb = self.word_embedding(word_ids)

        # Plongement de caractères + LSTM
        batch_size, seq_len, max_word_len = char_ids.shape
        char_ids_flat = char_ids.view(-1, max_word_len)  # (batch_size * seq_len, max_word_len)
        char_emb = self.char_embedding(char_ids_flat)    # (batch_size * seq_len, max_word_len, char_dim)

        char_output, (h_n, _) = self.char_lstm(char_emb)
        # Prendre les états cachés finaux bidirectionnels et les concaténer
        char_rep = torch.cat([h_n[-2], h_n[-1]], dim=-1)  # (batch_size * seq_len, char_dim * 2)

        char_rep = char_rep.view(batch_size, seq_len, -1)  # (batch_size, seq_len, char_dim * 2)

        # Concaténer l'embedding de mot et la représentation de caractères
        combined = torch.cat([word_emb, char_rep], dim=-1)
        return combined

7. Exemple pratique complet : modèle de classification de texte

Ci-dessous se trouve un exemple complet de modèle de classification de texte, comprenant une couche d'embedding, un LSTM et un classifieur :

Exemple

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# ── Paramètres de configuration ──────────────────────────────────────
VOCAB_SIZE = 10000
EMBED_DIM = 128
HIDDEN_SIZE = 128
NUM_LAYERS = 2
NUM_CLASSES = 5
DROPOUT = 0.3
MAX_LEN = 200

# ── Définition du modèle ──────────────────────────────────────
class TextClassificationModel(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_size,
                 num_layers, num_classes, dropout=0.3, padding_idx=0):
        super().__init__()

        # Couche d'embedding
        self.embedding = nn.Embedding(
            vocab_size,
            embed_dim,
            padding_idx=padding_idx
        )

        # BiLSTM
        self.lstm = nn.LSTM(
            embed_dim,
            hidden_size,
            num_layers=num_layers,
            batch_first=True,
            bidirectional=True,
            dropout=dropout if num_layers > 1 else 0
        )

        # Classificateur
        self.dropout = nn.Dropout(dropout)
        self.fc = nn.Linear(hidden_size * 2, num_classes)

    def forward(self, x):
        # x: (batch_size, seq_len)
        embedded = self.embedding(x)  # (batch_size, seq_len, embed_dim)

        # LSTM
        output, (h_n, c_n) = self.lstm(embedded)

        # Concaténer les états cachés finaux bidirectionnels
        h_forward = h_n[-2]   # (batch_size, hidden_size)
        h_backward = h_n[-1]  # (batch_size, hidden_size)
        h_combined = torch.cat([h_forward, h_backward], dim=-1)

        # Classification
        dropped = self.dropout(h_combined)
        logits = self.fc(droped)
        return logits


# ── Jeu de données ──────────────────────────────────────
class TextDataset(Dataset):
    def __init__(self, texts, labels, vocab, max_len=200):
        self.texts = texts
        self.labels = labels
        self.vocab = vocab
        self.max_len = max_len

    def __len__(self):
        return len(self.texts)

    def __getitem__(self, idx):
        text = self.texts[idx][:self.max_len]
        # Convertir les mots en indices, les mots inconnus représentés par 1 (<UNK>)
        ids = [self.vocab.get(word, 1) for word in text]
        # Compléter avec des zéros jusqu'à une longueur fixe
        if len(ids) < self.max_len:
            ids += [0] * (self.max_len - len(ids))
        return torch.tensor(ids), torch.tensor(self.labels[idx])


# ── Fonction d'entraînement ──────────────────────────────────────
def train_epoch(model, loader, optimizer, criterion, device):
    model.train()
    total_loss = 0
    correct = 0
    total = 0

    for inputs, labels in loader:
        inputs, labels = inputs.to(device), labels.to(device)

        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        total_loss += loss.item() * inputs.size(0)
        _, predicted = outputs.max(1)
        correct += predicted.eq(labels).sum().item()
        total += labels.size(0)

    return total_loss / total, correct / total


# ── Initialisation et entraînement ──────────────────────────────────
# Supposons qu'un vocabulaire existe déjà
word2idx = {'<PAD>': 0, '<UNK>': 1}  # Le vocabulaire doit être construit en fonction du corpus réel

model = TextClassificationModel(
    VOCAB_SIZE, EMBED_DIM, HIDDEN_SIZE,
    NUM_LAYERS, NUM_CLASSES, DROPOUT
).to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=1e-3)

# Entraînement simulé
print("Début de l'entraînement...")
for epoch in range(10):
    train_loss, train_acc = train_epoch(model, None, optimizer, criterion, device)
    print(f"Epoch {epoch+1}: Loss={train_loss:.4f}, Acc={train_acc:.4f}")

8. Référence rapide de l’API

8.1 Opérations courantes sur nn.Embedding

Opération Code
Créer une couche d'embedding nn.Embedding(num_embeddings, embedding_dim)
Charger des embeddings pré-entraînés nn.Embedding.from_pretrained(weights)
Table de recherche pour obtenir les embeddings embedding(word_ids)
Geler les embeddings embedding.weight.requires_grad = False
Obtenir les vecteurs d'embedding embedding.weight[idx]

8.2 Ressources de vecteurs de mots pré-entraînés

Ressource Dimension Fonctionnalités
GloVe 50, 100, 200, 300 Statistiques de cooccurrence de mots, entraînement rapide
Word2Vec 50-500 Entraîné par Google, couverture étendue
FastText 300 Prend en charge les sous-mots, efficace pour gérer les OOV
BERT 768+ Selon le contexte, meilleures performances

8.3 Recommandations pour le choix d'une couche d'embedding

数据量小(< 10K)
    -> 使用预训练词向量(GloVe/FastText)+ 冻结或微调

数据量中等(10K ~ 100K)
    -> 使用预训练词向量 + 微调

数据量大(> 100K)
    -> 可考虑从头训练,或使用大规模预训练模型

领域差异大
    -> 使用领域相关预训练模型或增量训练

资源受限
    -> 冻结嵌入层,使用较小的嵌入维度
Autres extensions