PyDA Course
📖 Leçon 8 Avancé ⏱ 25 minutes ⚡ +100 XP ColabKagglenbviewerBinderDeepnoteGitHub python · fonction · génération-de-texte · boucle · nlp

Implémenter generate_text()

Enchaînez la boucle d'échantillonnage dans une fonction complète qui construit une séquence de mots à partir du modèle de bigrammes.

🎯 Ce que vous apprendrez :
  • Écrire une fonction generate_text(bigrams, start_word, length)
  • Enchaîner des appels sample_next() dans une boucle pour construire des séquences de mots
  • Gérer les impasses (aucun mot suivant connu) avec grâce
  • Contrôler la longueur de sortie et déboguer la génération avec des journaux

Rassemblons tout

Vous savez charger des données, tokeniser, compter les mots, construire des bigrammes, normaliser les probabilités et échantillonner le mot suivant. Vous combinez maintenant cela dans une seule fonction qui génère du texte : choisissez un mot de départ, échantillonnez le mot suivant, renvoyez-le en entrée, et répétez jusqu’à avoir produit assez de mots.

Les cellules ci-dessous réutilisent les fonctions load_corpus, tokenize, build_bigrams et normalize_bigrams des leçons 01 à 06 et la fonction sample_next de la leçon 07. Chaque page de leçon démarre une session Python vierge, alors exécutez d’abord cette cellule de mise en place :

python
import csv
import string
import random
from collections import defaultdict

with open("slm-corpus.csv", newline="") as f:
    reader = csv.DictReader(f)
    texts = [row["text"] for row in reader]

def load_corpus(path):
    with open(path, newline="") as f:
        reader = csv.DictReader(f)
        return [row["text"] for row in reader]

def tokenize(text):
    text = text.lower()
    for char in string.punctuation:
        text = text.replace(char, " ")
    return text.split()

def build_bigrams(tokens):
    bigrams = defaultdict(lambda: defaultdict(int))
    for i in range(len(tokens) - 1):
        bigrams[tokens[i]][tokens[i + 1]] += 1
    return dict(bigrams)

def normalize_bigrams(bigrams):
    normalized = {}
    for word, followers in bigrams.items():
        if not followers:
            continue
        total = sum(followers.values())
        normalized[word] = {w: c / total for w, c in followers.items()}
    return normalized

def sample_next(model, current_word):
    if current_word not in model:
        return None
    followers = model[current_word]
    words = list(followers.keys())
    weights = list(followers.values())
    return random.choices(words, weights=weights, k=1)[0]

model = normalize_bigrams(build_bigrams(tokenize(" ".join(texts))))

Concepts clés

La boucle de génération

La logique centrale est une simple boucle :

python
import random

def generate_text(model, start_word, length=20):
    word = start_word
    result = [word]

    for _ in range(length - 1):
        next_word = sample_next(model, word)
        if next_word is None:
            break  # dead end
        result.append(next_word)
        word = next_word

    return " ".join(result)

Commencez avec start_word, échantillonnez le mot suivant, ajoutez-le au résultat et définissez-le comme nouveau mot courant. Répétez length - 1 fois (le premier mot est déjà dans la liste).

Gérer les impasses

Quand sample_next() renvoie None (le mot courant n’a pas de mots suivants connus), vous avez trois options. La plus simple est de s’arrêter :

python
import random
random.seed(7)

word = "the"
result = [word]
for _ in range(10):
    next_word = sample_next(model, word)
    if next_word is None:
        break  # dead end — stop
    result.append(next_word)
    word = next_word

print(" ".join(result))

Cela produit une sortie plus courte mais dont l’exactitude est garantie. Pour une sortie plus longue, redémarrez depuis un mot courant :

python
import random
random.seed(7)

word = "the"
result = [word]
for _ in range(10):
    next_word = sample_next(model, word)
    if next_word is None:
        next_word = random.choice(["the", "and", "to", "of", "a"])  # restart
    result.append(next_word)
    word = next_word

print(" ".join(result))

Choisir un mot de départ

Le mot de départ façonne fortement la sortie. Commencer par « the » produit de l’anglais générique ; commencer par un mot rare peut produire une sortie inhabituelle :

python
def generate_from_random(model, length=20):
    start = random.choice(list(model.keys()))
    return generate_text(model, start, length)

Pour plus de contrôle, laissez l’utilisateur spécifier le mot de départ.

Tester avec une graine fixe

Déboguer la génération exige une sortie reproductible. Définissez la graine avant d’appeler :

python
random.seed(42)
print(generate_text(model, "the", length=10))
# Always produces the same output with seed 42

Une version plus robuste

Ajoutez des journaux pour tracer ce qui se passe :

python
def generate_text(model, start_word, length=20, verbose=False):
    word = start_word
    result = [word]

    for i in range(length - 1):
        next_word = sample_next(model, word)
        if verbose:
            print(f"  Step {i+1}: '{word}' → '{next_word}'")
        if next_word is None:
            if verbose:
                print(f"  Dead end at step {i+1}")
            break
        result.append(next_word)
        word = next_word

    return " ".join(result)

Avec verbose=True, vous pouvez regarder la génération pas à pas.

À quoi ressemble la sortie

En l’exécutant sur le corpus :

python
random.seed(123)
text = generate_text(model, "the", length=15)
print(text)

Cela pourrait produire quelque chose comme :

the old man had been a good teacher and he had a

La sortie ne sera pas grammaticalement parfaite, c’est un petit modèle avec seulement un contexte de bigrammes. Mais elle capture de vraies séquences de mots anglais parce que les probabilités de bigrammes proviennent de texte réel.

Essayez

Générez 5 textes différents de longueur 20, chacun commençant par un mot différent :

python
random.seed(42)
starts = ["the", "a", "he", "she", "it"]
for word in starts:
    text = generate_text(model, word, length=20)
    print(f"\n[{word}] {text}")

Points clés

  • generate_text() enchaîne les appels sample_next() dans une boucle pour construire des séquences de mots
  • Les impasses surviennent quand un mot n’a pas de mots suivants connus, gérez-les en vous arrêtant ou en redémarrant
  • Le choix du mot de départ affecte fortement la qualité de la sortie
  • Utilisez random.seed() et verbose=True pour le débogage

Défi pratique

Écrivez generate_until(model, start_word, stop_words) qui génère du texte jusqu’à atteindre un mot dans stop_words ou 50 mots. Utilisez-la pour générer du texte qui s’arrête aux mots de fin de phrase :

python
def generate_until(model, start_word, stop_words=None, max_length=50):
    if stop_words is None:
        stop_words = set()
    word = start_word
    result = [word]
    for _ in range(max_length - 1):
        next_word = sample_next(model, word)
        if next_word is None or next_word in stop_words:
            break
        result.append(next_word)
        word = next_word
    return " ".join(result)
Vérification rapide +10 XP

1. Quelle est la boucle centrale de la génération de texte ?

2. Comment le générateur sait-il quels mots peuvent suivre le mot courant ?

3. Qu'est-ce qui limite la longueur du texte généré ?

pythonfonctiongénération-de-textebouclenlp