CoolFace
Apppublic

may24c/insertion-paragraphe

sourceHugging Faceupdated 6mo agoView on Hugging Face
0likes
scripts.html2145 linesDownload Raw Back to templates
1<!-- web/templates/documentation.html -->2{% extends "base.html" %}3 4{% block title %}Documentation - Scripts du projet{% endblock %}5{% block page_theme %}docs-theme{% endblock %}6 7{% block content %}8<div class="page-header">9    <h1> Scripts du projet</h1>10    <p>L'ensemble des scripts utilisés pour la collecte, le traitement et l'entraînement ainsi que l'evaluation des modèles</p>11</div>12 13<div class="content-section">14    <h2> Scripts de collecte des données</h2>15    16    <!-- Script 1 : Téléchargement arXiv -->17    <details class="script-details">18        <summary>19            <span class="script-icon"></span>20            <span class="script-title">download_arxiv_pdfs.py</span>21            <span class="script-badge">arXiv</span>22        </summary>23        <div class="script-content">24            <p><strong>Objectif :</strong> Télécharger des articles scientifiques depuis arXiv (catégorie cs.CL)</p>25            <pre><code class="language-python">import requests26import os27import feedparser28 29# Dossier où on va sauvegarder les PDFs30SAVE_DIR = "arxiv_pdfs"31os.makedirs(SAVE_DIR, exist_ok=True)  # Crée le dossier s'il n'existe pas32 33# Requête pour récupérer des articles en informatique (cs.CL = Computation and Language)34QUERY = "cat:cs.CL"35MAX_RESULTS = 650  # Nombre max d'articles qu'on veut36 37# Construction de l'URL pour interroger l'API arXiv38url = (39    "http://export.arxiv.org/api/query?"40    f"search_query={QUERY}&start=0&max_results={MAX_RESULTS}"41)42 43# On parse le résultat (format Atom/RSS)44feed = feedparser.parse(url)45 46# Pour chaque article trouvé47for entry in feed.entries:48 49    # On récupère l'ID de l'article (ex: 2401.12345)50    arxiv_id = entry.id.split("/")[-1]51    52    # Le deuxième lien est celui du PDF53    pdf_url = entry.links[1].href54 55    # Chemin où sauvegarder le fichier56    out_path = os.path.join(SAVE_DIR, arxiv_id + ".pdf")57 58    # Si le fichier existe déjà, on passe (évite de retélécharger)59    if os.path.exists(out_path):60        continue61 62    print("Téléchargement :", arxiv_id)63 64    # Téléchargement du PDF65    r = requests.get(pdf_url)66    67    # Sauvegarde en binaire (mode 'wb')68    with open(out_path, "wb") as f:69        f.write(r.content)</code></pre>70        </div>71    </details>72    73    <!-- Script 2 : Extraction texte arXiv -->74    <details class="script-details">75        <summary>76            <span class="script-icon"></span>77            <span class="script-title">extract_arxiv_texts.py</span>78            <span class="script-badge">arXiv</span>79        </summary>80        <div class="script-content">81            <p><strong>Objectif :</strong> Convertir les PDF arXiv en fichiers texte exploitables</p>82            <pre><code class="language-python">import os83import fitz   # PyMuPDF - bibliothèque pour lire les PDFs84import sys85 86# Dossiers d'entrée et de sortie87PDF_DIR = "arxiv_pdfs"88OUT_DIR = "arxiv_texts"89 90# Création du dossier de sortie91os.makedirs(OUT_DIR, exist_ok=True)92 93 94def extract_text(pdf_path):95    """Extrait le texte d'un PDF"""96    97    try:98        # On ouvre le PDF99        doc = fitz.open(pdf_path)100        101        # Vérification que le PDF est valide102        if doc.is_closed or doc.needs_pass:103            print(f"  ⚠ Document invalide ou protégé: {pdf_path}")104            return None105            106        full_text = []107        108        # On parcourt chaque page du PDF109        for page_num in range(len(doc)):110            try:111                page = doc[page_num]112                text = page.get_text()  # Extraction du texte de la page113                114                # On ne garde que les pages non vides115                if text and text.strip():116                    full_text.append(text)117                    118            except Exception as e:119                print(f"  ⚠ Erreur sur la page {page_num} de {os.path.basename(pdf_path)}: {str(e)}")120                continue121        122        # On ferme le document123        doc.close()124        125        # Si on n'a rien extrait, on retourne None126        if not full_text:127            print(f"  Aucun texte extrait de: {os.path.basename(pdf_path)}")128            return None129            130        # On retourne tout le texte en une seule chaîne131        return "\n".join(full_text)132        133    except Exception as e:134        print(f"   Erreur lors de l'extraction de {os.path.basename(pdf_path)}: {str(e)}")135        return None136 137 138def extract_text_alternative(pdf_path):139    """Méthode alternative si la première échoue"""140    141    try:142        doc = fitz.open(pdf_path)143        full_text = []144        145        for page_num in range(len(doc)):146            try:147                page = doc[page_num]148                149                # Première méthode : extraction en texte brut150                text = page.get_text("text")151                152                # Si ça ne marche pas, on essaye mot par mot153                if not text or not text.strip():154                    text = page.get_text("words")  # Extraction mot par mot155                    if text:156                        # On reconstruit le texte à partir des mots157                        text = " ".join([word[4] for word in text if len(word) > 4])158                159                if text and text.strip():160                    full_text.append(text)161                    162            except Exception as e:163                print(f" Erreur méthode alternative page {page_num}: {str(e)}")164                continue165                166        doc.close()167        168        if full_text:169            return "\n".join(full_text)170        return None171        172    except Exception as e:173        print(f" Erreur méthode alternative: {str(e)}")174        return None175 176 177def process_pdfs():178    """Traite tous les PDFs du répertoire"""179    180    # Compteurs pour les statistiques181    total_pdfs = 0182    success_count = 0183    failed_count = 0184    corrupted_count = 0185    186    # On liste tous les fichiers PDF187    pdf_files = [f for f in os.listdir(PDF_DIR) if f.endswith(".pdf")]188    total_pdfs = len(pdf_files)189    190    print(f"\n {total_pdfs} fichiers PDF trouvés dans {PDF_DIR}\n")191    192    # On traite chaque fichier un par un193    for i, file in enumerate(pdf_files, 1):194        pdf_path = os.path.join(PDF_DIR, file)195        196        print(f" [{i}/{total_pdfs}] Traitement: {file}")197        198        # Essai de la méthode standard199        txt = extract_text(pdf_path)200        201        # Si ça échoue, on essaye la méthode alternative202        if txt is None:203            print(f"   Tentative avec méthode alternative...")204            txt = extract_text_alternative(pdf_path)205            if txt is not None:206                print(f"  Réussi avec méthode alternative!")207        208        # Si on a réussi à extraire du texte209        if txt is not None and txt.strip():210            out_path = os.path.join(OUT_DIR, file.replace(".pdf", ".txt"))211            212            try:213                # Sauvegarde du texte dans un fichier .txt214                with open(out_path, "w", encoding="utf8") as f:215                    f.write(txt)216                    217                print(f" Sauvegardé: {os.path.basename(out_path)} ({len(txt)} caractères)")218                success_count += 1219                220            except Exception as e:221                print(f"  Erreur sauvegarde: {str(e)}")222                failed_count += 1223        else:224            # Si l'extraction a échoué225            try:226                file_size = os.path.getsize(pdf_path)227                if file_size < 1000:228                    print(f" Fichier suspect (taille: {file_size} octets)")229                else:230                    print(f" Échec extraction")231                corrupted_count += 1232            except:233                corrupted_count += 1234    235    # Rapport final236    print(f"\n" + "="*50)237    print(f" RAPPORT FINAL")238    print(f"="*50)239    print(f"Total PDFs trouvés: {total_pdfs}")240    print(f"Extractions réussies: {success_count}")241    print(f"Échecs extraction: {failed_count}")242    print(f"Fichiers corrompus: {corrupted_count}")243    print(f"Textes sauvegardés dans: {OUT_DIR}")244    print(f"="*50)245 246 247if __name__ == "__main__":248    249    # Vérification que le dossier source existe250    if not os.path.exists(PDF_DIR):251        print(f" Erreur: Le répertoire {PDF_DIR} n'existe pas!")252        sys.exit(1)253    254    print(" Début du traitement des PDFs arXiv")255    print(f" Source: {PDF_DIR}")256    print(f" Destination: {OUT_DIR}")257    258    # On lance le traitement259    process_pdfs()260    261    print("\n Traitement terminé!")</code></pre>262        </div>263    </details>264    265    <!-- Script 3 : Téléchargement PMC -->266    <details class="script-details">267        <summary>268            <span class="script-icon"></span>269            <span class="script-title">download_pmc_xml.py</span>270            <span class="script-badge">PMC</span>271        </summary>272        <div class="script-content">273            <p><strong>Objectif :</strong> Télécharger des articles biomédicaux depuis PubMed Central</p>274            <pre><code class="language-python">import requests275import os276import time277 278# Dossier où on va sauvegarder les fichiers XML279SAVE_DIR = "pmc_xml"280os.makedirs(SAVE_DIR, exist_ok=True)281 282# ==========================================283# ÉTAPE 1 : Recherche des IDs des articles284# ==========================================285 286# URL de recherche des articles PMC287search_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esearch.fcgi"288 289# Paramètres de la requête290params = {291    "db": "pmc",                    # Base de données : PubMed Central292    "term": "open access[filter]",  # On veut uniquement les articles en libre accès293    "retmax": 650                   # Nombre max d'articles294}295 296print("Recherche des IDs PMC...")297 298# On envoie la requête299r = requests.get(search_url, params=params, timeout=30)300r.raise_for_status()  # Lève une erreur si la requête a échoué301 302# On extrait les IDs de la réponse XML303ids = []304for line in r.text.split("<Id>")[1:]:305    ids.append(line.split("</Id>")[0])306 307print(len(ids), "IDs trouvés")308 309# ==========================================310# ÉTAPE 2 : Téléchargement des fichiers XML311# ==========================================312 313# Pour chaque ID trouvé314for pmc_id in ids:315 316    # URL pour récupérer le contenu de l'article317    fetch_url = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi"318 319    params = {320        "db": "pmc",321        "id": pmc_id,322        "retmode": "xml"   # On veut le format XML323    }324 325    out_path = os.path.join(SAVE_DIR, pmc_id + ".xml")326 327    # Si le fichier existe déjà, on passe (évite de retélécharger)328    if os.path.exists(out_path):329        print("Déjà présent :", pmc_id)330        continue331 332    print("Téléchargement PMC", pmc_id)333 334    try:335        # Téléchargement du fichier XML336        r = requests.get(fetch_url, params=params, timeout=30)337 338        if r.status_code != 200:339            print("  -> erreur HTTP", r.status_code)340            continue341 342        # Sauvegarde du fichier343        with open(out_path, "w", encoding="utf-8") as f:344            f.write(r.text)345 346    except requests.exceptions.RequestException as e:347        print("  -> erreur réseau pour", pmc_id)348        print("     ", e)349        continue350 351    # Pause pour éviter de surcharger le serveur352    time.sleep(1)</code></pre>353        </div>354    </details>355</div>356 357<div class="content-section">358    <h2> Scripts de génération des datasets</h2>359    360    <!-- Script 4 : Génération dataset Wikipédia -->361    <details class="script-details">362        <summary>363            <span class="script-icon"></span>364            <span class="script-title">generate_wiki.py</span>365            <span class="script-badge">Wikipédia</span>366        </summary>367        <div class="script-content">368            <p><strong>Objectif :</strong> Générer des exemples d'insertion à partir des articles Wikipédia</p>369            <pre><code class="language-python">import os370import json371import random372from tqdm import tqdm373 374# Configuration375INPUT_DIR = "extracted"        # Dossier où sont les articles Wikipédia extraits376OUTPUT_FILE = "wiki_dataset.jsonl"  # Fichier de sortie377 378MIN_PARAGRAPHS = 5             # Minimum de paragraphes pour garder un article379MAX_SAMPLES_PER_ARTICLE = 1    # Nombre d'exemples par article380 381 382def clean_paragraphs(text):383    """Nettoie le texte en gardant uniquement les paragraphes valides"""384    385    # On découpe par sauts de ligne386    paragraphs = text.split("\n")387    388    # On ne garde que les paragraphes non vides et assez longs389    paragraphs = [390        p.strip()391        for p in paragraphs392        if len(p.strip()) > 80  # On ignore les paragraphes trop courts393    ]394    395    return paragraphs396 397 398def generate_examples_from_text(title, text):399    """400    À partir d'un article, génère des exemples où on retire un paragraphe401    et on demande au modèle de le réinsérer402    """403    404    # On nettoie le texte405    paragraphs = clean_paragraphs(text)406    407    # Si l'article n'a pas assez de paragraphes, on l'ignore408    if len(paragraphs) < MIN_PARAGRAPHS:409        return []410    411    examples = []412    413    # On génère un exemple par article (ou plusieurs selon config)414    for _ in range(min(MAX_SAMPLES_PER_ARTICLE, len(paragraphs))):415        416        # On choisit un paragraphe au hasard à retirer417        idx = random.randint(0, len(paragraphs) - 1)418        419        # Le paragraphe qu'on va retirer420        removed = paragraphs[idx]421        422        # L'article sans ce paragraphe423        truncated = paragraphs[:idx] + paragraphs[idx + 1:]424        425        # On crée l'exemple426        example = {427            "source": "wikipedia",               # Source des données428            "title": title,                      # Titre de l'article429            "article_truncated": truncated,      # Article sans le paragraphe430            "paragraph_to_insert": removed,      # Paragraphe à insérer431            "true_position": idx                 # Position correcte (0-based)432        }433        434        examples.append(example)435    436    return examples437 438 439def process_wiki_dump():440    """Traitement principal"""441    442    # On récupère tous les fichiers d'articles Wikipédia443    all_files = []444    445    for root, _, files in os.walk(INPUT_DIR):446        for file in files:447            all_files.append(os.path.join(root, file))448    449    print(len(all_files), "fichiers trouvés")450    451    # On mélange pour avoir des articles variés452    random.shuffle(all_files)453    454    article_count = 0  # Compteur d'articles traités455    456    with open(OUTPUT_FILE, "w", encoding="utf-8") as out:457        458        # On parcourt tous les fichiers459        for file_path in tqdm(all_files):460            461            # On s'arrête quand on a assez d'articles462            if article_count >= MAX_ARTICLES:463                break464            465            # On ouvre le fichier466            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:467                468                # Chaque ligne est un article au format JSON469                for line in f:470                    471                    if article_count >= MAX_ARTICLES:472                        break473                    474                    line = line.strip()475                    if not line:476                        continue477                    478                    try:479                        article = json.loads(line)480                    except json.JSONDecodeError:481                        continue482                    483                    # On génère les exemples pour cet article484                    examples = generate_examples_from_text(485                        article.get("title", ""),486                        article.get("text", "")487                    )488                    489                    # Si l'article a donné des exemples, on le compte490                    if examples:491                        article_count += 1492                    493                    # On écrit les exemples dans le fichier de sortie494                    for ex in examples:495                        out.write(json.dumps(ex, ensure_ascii=False) + "\n")496    497    print("Articles utilisés :", article_count)498 499 500if __name__ == "__main__":501    process_wiki_dump()</code></pre>502        </div>503    </details>504    505    <!-- Script 5 : Génération dataset arXiv -->506    <details class="script-details">507        <summary>508            <span class="script-icon"></span>509            <span class="script-title">generate_arxiv.py</span>510            <span class="script-badge">arXiv</span>511        </summary>512        <div class="script-content">513            <p><strong>Objectif :</strong> Générer des exemples d'insertion à partir des articles arXiv</p>514            <pre><code class="language-python">import os515import json516import random517 518# Configuration519INPUT_DIR = "arxiv_texts"          # Dossier avec les fichiers .txt extraits520OUTPUT_FILE = "arxiv_dataset.jsonl" # Fichier de sortie521 522MIN_PARAGRAPHS = 5                  # Minimum de paragraphes par article523MAX_SAMPLES_PER_ARTICLE = 1         # Nombre d'exemples par article524 525 526def clean_paragraphs(text):527    """Nettoie le texte en paragraphes valides"""528    529    # On découpe par sauts de ligne530    paragraphs = text.split("\n")531    532    # On nettoie et on filtre533    paragraphs = [534        p.strip()535        for p in paragraphs536        if len(p.strip()) > 80  # On garde les paragraphes suffisamment longs537    ]538    539    return paragraphs540 541 542def generate_examples_from_text(title, text):543    """544    Génère des exemples d'insertion à partir d'un article545    Principe : on retire un paragraphe au hasard, et on garde l'article sans ce paragraphe546    """547    548    # On nettoie le texte549    paragraphs = clean_paragraphs(text)550    551    # Si l'article n'a pas assez de paragraphes, on l'ignore552    if len(paragraphs) < MIN_PARAGRAPHS:553        return []554    555    examples = []556    557    # On génère un exemple par article (ou plus selon config)558    for _ in range(min(MAX_SAMPLES_PER_ARTICLE, len(paragraphs))):559        560        # On choisit un paragraphe au hasard561        idx = random.randint(0, len(paragraphs) - 1)562        563        # Le paragraphe qu'on va retirer564        removed = paragraphs[idx]565        566        # L'article sans ce paragraphe567        truncated = paragraphs[:idx] + paragraphs[idx + 1:]568        569        # Construction de l'exemple570        example = {571            "source": "arxiv",                  # Source des données572            "title": title,                     # Titre de l'article573            "article_truncated": truncated,     # Article modifié574            "paragraph_to_insert": removed,     # Paragraphe retiré575            "true_position": idx                # Position originale576        }577        578        examples.append(example)579    580    return examples581 582 583# On récupère tous les fichiers .txt584all_files = [f for f in os.listdir(INPUT_DIR) if f.endswith(".txt")]585 586print(len(all_files), "fichiers trouvés")587 588# On mélange pour avoir une bonne diversité589random.shuffle(all_files)590 591article_count = 0  # Compteur592 593with open(OUTPUT_FILE, "w", encoding="utf8") as out:594    595    # On parcourt chaque fichier596    for file in all_files:597        598        # On s'arrête quand on a assez d'articles599        if article_count >= MAX_ARTICLES:600            break601        602        path = os.path.join(INPUT_DIR, file)603        604        # Lecture du texte de l'article605        with open(path, encoding="utf8", errors="ignore") as f:606            text = f.read()607        608        # Le titre est le nom du fichier sans l'extension609        title = file.replace(".txt", "")610        611        # Génération des exemples612        examples = generate_examples_from_text(title, text)613        614        # Si l'article a donné des exemples, on le compte615        if examples:616            article_count += 1617        618        # Écriture dans le fichier de sortie619        for ex in examples:620            out.write(json.dumps(ex, ensure_ascii=False) + "\n")621 622print("Articles utilisés :", article_count)</code></pre>623        </div>624    </details>625    626    <!-- Script 6 : Génération dataset PMC -->627    <details class="script-details">628        <summary>629            <span class="script-icon"></span>630            <span class="script-title">generate_pmc.py</span>631            <span class="script-badge">PMC</span>632        </summary>633        <div class="script-content">634            <p><strong>Objectif :</strong> Générer des exemples d'insertion à partir des articles PMC</p>635            <pre><code class="language-python">import os636import json637import random638import xml.etree.ElementTree as ET639 640# Configuration641INPUT_DIR = "pmc_xml"                # Dossier avec les fichiers XML de PMC642OUTPUT_FILE = "pmc_dataset.jsonl"    # Fichier de sortie643 644MIN_PARAGRAPHS = 5                   # Minimum de paragraphes par article645MAX_SAMPLES_PER_ARTICLE = 1          # Nombre d'exemples par article646 647 648def extract_paragraphs(xml_path):649    """650    Extrait les paragraphes d'un fichier XML PMC651    Les paragraphes sont dans les balises &lt;p&gt;652    """653    654    # On parse le fichier XML655    tree = ET.parse(xml_path)656    root = tree.getroot()657    658    paragraphs = []659    660    # On cherche toutes les balises &lt;p&gt; (paragraphe)661    for p in root.findall(".//p"):662        # On récupère le texte complet de la balise663        txt = "".join(p.itertext()).strip()664        665        # On garde les paragraphes suffisamment longs666        if len(txt) > 80:667            paragraphs.append(txt)668    669    return paragraphs670 671 672# On récupère tous les fichiers XML673all_files = [f for f in os.listdir(INPUT_DIR) if f.endswith(".xml")]674 675print(len(all_files), "fichiers trouvés")676 677# On mélange pour avoir des articles variés678random.shuffle(all_files)679 680article_count = 0681 682with open(OUTPUT_FILE, "w", encoding="utf8") as out:683    684    # On parcourt chaque fichier685    for file in all_files:686        687        # On s'arrête quand on a assez d'articles688        if article_count >= MAX_ARTICLES:689            break690        691        path = os.path.join(INPUT_DIR, file)692        693        try:694            # Extraction des paragraphes695            paragraphs = extract_paragraphs(path)696        except Exception as e:697            print(f"Erreur avec {file}: {e}")698            continue699        700        # On ignore les articles trop courts701        if len(paragraphs) < MIN_PARAGRAPHS:702            continue703        704        # Le titre est le nom du fichier sans extension705        title = file.replace(".xml", "")706        707        examples = []708        709        # Génération des exemples710        for _ in range(min(MAX_SAMPLES_PER_ARTICLE, len(paragraphs))):711            712            # On choisit un paragraphe au hasard713            idx = random.randint(0, len(paragraphs) - 1)714            715            # Le paragraphe qu'on retire716            removed = paragraphs[idx]717            718            # L'article sans ce paragraphe719            truncated = paragraphs[:idx] + paragraphs[idx + 1:]720            721            # Construction de l'exemple722            example = {723                "source": "pmc",                     # Source724                "title": title,                      # Titre725                "article_truncated": truncated,      # Article modifié726                "paragraph_to_insert": removed,      # Paragraphe à insérer727                "true_position": idx                 # Position originale728            }729            730            examples.append(example)731        732        # Si l'article a donné des exemples, on le compte733        if examples:734            article_count += 1735        736        # Écriture dans le fichier de sortie737        for ex in examples:738            out.write(json.dumps(ex, ensure_ascii=False) + "\n")739 740print("Articles utilisés :", article_count)</code></pre>741        </div>742    </details>743</div>744 745<div class="content-section">746    <h2> Scripts de traitement</h2>747    748    <!-- Script 7 : Shuffle dataset -->749    <details class="script-details">750        <summary>751            <span class="script-icon"></span>752            <span class="script-title">shuffle_dataset.py</span>753            <span class="script-badge">Traitement</span>754        </summary>755        <div class="script-content">756            <p><strong>Objectif :</strong> Mélanger aléatoirement les exemples du dataset</p>757            <pre><code class="language-python">import json758import random759 760# Fichier d'entrée (dataset fusionné) et fichier de sortie761INPUT_FILE = "dataset_total.jsonl"762OUTPUT_FILE = "dataset_shuffled.jsonl"763 764# ==========================================765# Pourquoi mélanger ?766# ==========================================767# Le mélange aléatoire (shuffling) est crucial pour l'apprentissage :768# - Évite que le modèle apprenne des patterns liés à l'ordre des données769# - Évite les biais liés à la source (Wikipédia, arXiv, PMC)770# - Permet une meilleure généralisation du modèle771 772data = []773 774# Lecture du dataset775with open(INPUT_FILE, encoding="utf8") as f:776    for line in f:777        data.append(json.loads(line))778 779print("Nombre d'exemples :", len(data))780 781# Mélange aléatoire des exemples782random.shuffle(data)783 784# Écriture du dataset mélangé785with open(OUTPUT_FILE, "w", encoding="utf8") as f:786    for example in data:787        f.write(json.dumps(example, ensure_ascii=False) + "\n")788 789print("Dataset mélangé sauvegardé dans :", OUTPUT_FILE)</code></pre>790        </div>791    </details>792    793    <!-- Script 8 : Split dataset -->794    <details class="script-details">795        <summary>796            <span class="script-icon"></span>797            <span class="script-title">split_dataset.py</span>798            <span class="script-badge">Traitement</span>799        </summary>800        <div class="script-content">801            <p><strong>Objectif :</strong> Diviser le dataset en train/dev/test</p>802            <pre><code class="language-python">import json803import random804 805# Fichier d'entrée806INPUT_FILE = "dataset_shuffled.jsonl"807 808# Fichiers de sortie809TRAIN_FILE = "train.jsonl"810DEV_FILE = "dev.jsonl"811TEST_FILE = "test.jsonl"812 813# Lecture du dataset814with open(INPUT_FILE, encoding="utf8") as f:815    data = [json.loads(line) for line in f]816 817# On mélange à nouveau pour garantir un split aléatoire818random.shuffle(data)819 820n = len(data)821print("Total d'exemples :", n)822 823# Répartition : 80% train, 10% dev, 10% test824train = data[: int(0.8 * n)]825dev = data[int(0.8 * n): int(0.9 * n)]826test = data[int(0.9 * n):]827 828# Sauvegarde des trois fichiers829for name, dataset in [830    (TRAIN_FILE, train),831    (DEV_FILE, dev),832    (TEST_FILE, test)833]:834    with open(name, "w", encoding="utf8") as f:835        for ex in dataset:836            f.write(json.dumps(ex, ensure_ascii=False) + "\n")837 838print("Train:", len(train))839print("Dev:", len(dev))840print("Test:", len(test))</code></pre>841        </div>842    </details>843    844    <!-- Script 9 : Build pairs -->845    <details class="script-details">846        <summary>847            <span class="script-icon"></span>848            <span class="script-title">build_pairs.py</span>849            <span class="script-badge">Traitement</span>850        </summary>851        <div class="script-content">852            <p><strong>Objectif :</strong> Transformer les articles en paires (prev, insert, next) pour l'entraînement</p>853            <pre><code class="language-python">import json854import argparse855 856def convert_dataset(input_file, output_file):857    """858    Transforme un dataset d'articles en paires d'insertion859    860    Pour chaque article, on génère des paires (prev, next, insert, label)861    où :862    - prev = paragraphe avant l'insertion863    - next = paragraphe après l'insertion864    - insert = paragraphe à insérer865    - label = 1 si c'est la bonne position, 0 sinon866    """867    868    # On ouvre le fichier d'entrée (dataset brut)869    # et le fichier de sortie où on va écrire les paires870    with open(input_file, encoding="utf8") as f, open(output_file, "w", encoding="utf8") as out:871        872        # On lit le fichier ligne par ligne (format JSONL)873        for line in f:874            875            # On convertit la ligne JSON en dictionnaire Python876            ex = json.loads(line)877            878            # On récupère les éléments utiles879            article = ex["article_truncated"]       # liste des paragraphes880            insert = ex["paragraph_to_insert"]      # paragraphe à insérer881            gold = ex["true_position"]              # position correcte882            883            # Si l'article est trop court, on ne peut pas créer de paires884            if len(article) < 2:885                continue886            887            # On parcourt toutes les positions possibles dans l'article888            for i in range(len(article) - 1):889                890                # On récupère le paragraphe avant et après891                prev_p = article[i]892                next_p = article[i + 1]893                894                # Label = 1 si c'est la bonne position895                # Attention : gold correspond à la position dans l'article original896                # On compare avec i+1 car les positions sont décalées897                label = 1 if i == gold - 1 else 0898                899                # On construit la paire pour le modèle900                pair = {901                    "prev": prev_p,902                    "next": next_p,903                    "insert": insert,904                    "label": label905                }906                907                # On écrit la paire dans le fichier de sortie (format JSONL)908                out.write(json.dumps(pair, ensure_ascii=False) + "\n")909 910 911if __name__ == "__main__":912    913    # On utilise argparse pour pouvoir lancer le script en ligne de commande914    parser = argparse.ArgumentParser()915    916    # Argument : chemin du fichier d'entrée917    parser.add_argument("--input")918    919    # Argument : chemin du fichier de sortie920    parser.add_argument("--output")921    922    # On récupère les arguments donnés par l'utilisateur923    args = parser.parse_args()924    925    # On lance la conversion du dataset926    convert_dataset(args.input, args.output)</code></pre>927            <p><strong>Exécution :</strong></p>928            <pre><code class="language-bash">python build_pairs.py --input train.jsonl --output train_pairs.jsonl929python build_pairs.py --input dev.jsonl --output dev_pairs.jsonl930python build_pairs.py --input test.jsonl --output test_pairs.jsonl</code></pre>931        </div>932    </details>933</div>934 935<div class="content-section">936    <h2> Rétrécissement du dataset</h2>937    <p>Face aux contraintes de mémoire GPU et pour accélérer les phases d'expérimentation, nous avons dû réduire la taille de notre dataset tout en préservant son équilibre entre les classes. Cette étape, appelée <strong>sous-échantillonnage (downsampling)</strong>, nous permet de travailler avec un jeu de données plus petit mais toujours représentatif.</p>938    939    <p>Nous avons utilisé la méthode du <strong>réservoir sampling (échantillonnage par réservoir)</strong> qui garantit un tirage aléatoire uniforme même sur des fichiers trop volumineux pour être chargés entièrement en mémoire.</p>940    941    <p>L'objectif était de créer des versions réduites de nos datasets :</p>942    <ul>943        <li><strong>Train :</strong> 20 000 exemples par classe (40 000 au total)</li>944        <li><strong>Dev (validation) :</strong> 2 000 exemples par classe (4 000 au total)</li>945        <li><strong>Test :</strong> 5 000 exemples par classe (10 000 au total)</li>946    </ul>947    948    <p>Cette réduction nous a permis de :</p>949    <ul>950        <li>Réduire drastiquement le temps d'entraînement (de plusieurs heures à 1 seule heure)</li>951        <li>Adapter la consommation mémoire aux limitations de nos GPUs </li>952    </ul>953    954    <!-- Script : Réservoir sampling -->955    <details class="script-details">956        <summary>957            <span class="script-icon"></span>958            <span class="script-title">reservoir_sampling.py</span>959            <span class="script-badge">Traitement</span>960        </summary>961        <div class="script-content">962            <p><strong>Objectif :</strong> Réduire la taille du dataset tout en préservant l'équilibre entre les classes (label 0 et label 1).</p>963            964            <pre><code class="language-python">import json965import random966from pathlib import Path967 968def reservoir_sample_by_label(input_path, output_path, target_counts, seed=42):969    """970    Échantillonne un dataset JSONL en préservant un nombre cible d'exemples par label.971    972    Args:973        input_path: chemin du fichier d'entrée974        output_path: chemin du fichier de sortie975        target_counts: dictionnaire {label: nombre_cible}976        seed: graine aléatoire pour la reproductibilité977    """978    979    # On fixe la graine aléatoire pour la reproductibilité980    random.seed(seed)981 982    # Création des réservoirs pour chaque label983    reservoirs = {label: [] for label in target_counts}984    seen_counts = {label: 0 for label in target_counts}985 986    print(f"Lecture du fichier: {input_path}")987    988    with open(input_path, "r", encoding="utf-8") as f:989        for line in f:990            obj = json.loads(line)991            label = obj["label"]992 993            # On ignore les labels non ciblés994            if label not in target_counts:995                continue996 997            seen_counts[label] += 1998            k = target_counts[label]999 1000            # Algorithme du réservoir sampling1001            if len(reservoirs[label]) < k:1002                # Réservoir pas encore plein → on ajoute directement1003                reservoirs[label].append(line)1004            else:1005                # Réservoir plein → remplacement aléatoire avec probabilité k/n1006                j = random.randint(1, seen_counts[label])1007                if j <= k:1008                    reservoirs[label][j - 1] = line1009 1010    # Vérification que tous les réservoirs sont remplis1011    for label, target in target_counts.items():1012        actual = len(reservoirs[label])1013        if actual < target:1014            print(f" Attention: label {label} n'a que {actual} exemples (objectif: {target})")1015 1016    # Mélange des exemples de tous les labels1017    all_lines = []1018    for label in target_counts:1019        all_lines.extend(reservoirs[label])1020    random.shuffle(all_lines)1021 1022    # Création du dossier de sortie si nécessaire1023    Path(output_path).parent.mkdir(parents=True, exist_ok=True)1024    1025    # Écriture du fichier de sortie1026    with open(output_path, "w", encoding="utf-8") as f:1027        for line in all_lines:1028            f.write(line)1029 1030    print(f" Dataset sauvegardé: {output_path}")1031    print("   Répartition finale :")1032    for label in target_counts:1033        print(f"     label {label}: {len(reservoirs[label])} exemples")1034 1035 1036if __name__ == "__main__":1037    # Chemins des fichiers originaux1038    TRAIN_IN = "dataset/train_pairs.jsonl"1039    DEV_IN = "dataset/dev_pairs.jsonl"1040    TEST_IN = "dataset/test_pairs.jsonl"1041 1042    # Chemins des fichiers réduits1043    TRAIN_OUT = "dataset_small/train_small_balanced.jsonl"1044    DEV_OUT = "dataset_small/dev_small_balanced.jsonl"1045    TEST_OUT = "dataset_small/test_small_balanced.jsonl"1046 1047    print("=" * 60)1048    print(" RÉTRÉCISSEMENT DES DATASETS")1049    print("=" * 60)1050    1051    # Sous-échantillonnage du train : 20k par classe1052    reservoir_sample_by_label(1053        TRAIN_IN,1054        TRAIN_OUT,1055        target_counts={0: 20000, 1: 20000},1056        seed=421057    )1058 1059    # Sous-échantillonnage du dev : 2k par classe1060    reservoir_sample_by_label(1061        DEV_IN,1062        DEV_OUT,1063        target_counts={0: 2000, 1: 2000},1064        seed=421065    )1066 1067    # Sous-échantillonnage du test : 5k par classe1068    reservoir_sample_by_label(1069        TEST_IN,1070        TEST_OUT,1071        target_counts={0: 5000, 1: 5000},1072        seed=421073    )1074    1075    print(" Tous les datasets ont été réduits avec succès!")</code></pre>1076            1077            <h3> Résultat du rétrécissement</h3>1078            1079            <table class="results-table">1080                <thead>1081                    <tr>1082                        <th>Dataset</th>1083                        <th>Label 0 (original)</th>1084                        <th>Label 1 (original)</th>1085                        <th>Label 0 (réduit)</th>1086                        <th>Label 1 (réduit)</th>1087                        <th>Total réduit</th>1088                    </tr>1089                </thead>1090                <tbody>1091                    <tr>1092                        <td><strong>Train</strong></td>1093                        <td>~5.6M</td>1094                        <td>~5.6M</td>1095                        <td>20 000</td>1096                        <td>20 000</td>1097                        <td>40 000</td>1098                    </tr>1099                    <tr>1100                        <td><strong>Dev</strong></td>1101                        <td>~700k</td>1102                        <td>~700k</td>1103                        <td>2 000</td>1104                        <td>2 000</td>1105                        <td>4 000</td>1106                    </tr>1107                    <tr>1108                        <td><strong>Test</strong></td>1109                        <td>~700k</td>1110                        <td>~700k</td>1111                        <td>5 000</td>1112                        <td>5 000</td>1113                        <td>10 000</td>1114                    </tr>1115                </tbody>1116            </table>1117            1118            <div class="note">1119                <strong> Pourquoi l'échantillonnage par réservoir ?</strong><br>1120                Cette méthode présente plusieurs avantages :<br>1121                - <strong>Économie de mémoire</strong> : on ne charge jamais tout le dataset en mémoire<br>1122                - <strong>Échantillon aléatoire uniforme</strong> : chaque ligne a la même probabilité d'être sélectionnée<br>1123                - <strong>Reproductibilité</strong> : la graine aléatoire (seed=42) garantit des résultats identiques à chaque exécution<br>1124                - <strong>Équilibre préservé</strong> : on conserve exactement le nombre d'exemples souhaité par classe1125            </div>1126        </div>1127    </details>1128</div>1129 1130<div class="content-section">1131    <h2> Scripts d'entraînement et d'évaluation</h2>1132    1133        <!-- Script 10 : XLM-RoBERTa -->1134        <details class="script-details">1135            <summary>1136                <span class="script-icon"></span>1137                <span class="script-title">XLM-RoBERTa</span>1138                <span class="script-badge">Entraînement</span>1139            </summary>1140            <div class="script-content">1141                <p><strong>Objectif :</strong> Entraîner le modèle XLM-RoBERTa (modèle multilingue performant) sur notre tâche d'insertion de paragraphe.</p>1142                <p>XLM-RoBERTa est un modèle multilingue pré-entraîné sur 100 langues, ce qui le rend particulièrement adapté pour traiter des textes en français comme en anglais. Contrairement à l'approche de ranking utilisée pour BERT Multilingual, ici nous abordons le problème comme une <strong>classification binaire classique</strong> : pour chaque position possible, le modèle doit prédire si c'est la bonne position (label 1) ou non (label 0).</p>1143                <p>Le script commence par charger les données au format JSONL, où chaque exemple contient les champs <code>prev</code>, <code>insert</code>, <code>next</code> et <code>label</code>. La fonction <code>preprocess()</code> prépare chaque exemple en concaténant les trois paragraphes sous la forme <code>insert [SEP] prev [SEP] next</code> (l'ordre est différent de l'approche ranking pour mieux correspondre à l'architecture du modèle). La tokenisation utilise <code>longest_first</code> pour gérer les séquences de longueur variable.</p>1144                <p>Le modèle XLM-RoBERTa est configuré avec <code>num_labels=2</code> pour effectuer une classification binaire. L'entraînement utilise le <code>Trainer</code> de Hugging Face, qui gère automatiquement la boucle d'entraînement, l'évaluation et la sauvegarde des checkpoints. Les métriques calculées sont l'accuracy, le F1-score, la précision et le rappel.</p>1145                <p>Les hyperparamètres sont les suivants : batch size de 16, learning rate de 2e-5, 4 epochs, warmup ratio de 0.1, et une évaluation à chaque epoch. Le modèle sauvegarde automatiquement le meilleur checkpoint en fonction de la loss de validation.</p>1146                <p>À la fin de l'entraînement, le script évalue le modèle sur les datasets de validation et de test, puis sauvegarde le modèle final dans <code>my_xlm_roberta_model</code>. Une fonction <code>predict()</code> est également fournie pour faire des prédictions sur de nouveaux exemples.</p>1147                1148                <pre><code class="language-python"># ==========================================1149# XLM-ROBERTA - ENTRAÎNEMENT CLASSIFICATION1150# ==========================================1151 1152# Imports1153import json1154import torch1155from datasets import Dataset1156from transformers import (1157    AutoTokenizer,1158    AutoModelForSequenceClassification,1159    Trainer,1160    TrainingArguments1161)1162from sklearn.metrics import accuracy_score, precision_recall_fscore_support1163 1164# ==========================================1165# CHARGEMENT DES DONNÉES1166# ==========================================1167 1168def load_jsonl(path):1169    \"\"\"Charge un fichier JSONL et retourne une liste de dictionnaires\"\"\"1170    data = []1171    with open(path, "r", encoding="utf-8") as f:1172        for line in f:1173            try:1174                data.append(json.loads(line))1175            except json.JSONDecodeError:1176                pass1177    return data1178 1179# Chargement des datasets1180train_data = load_jsonl("dataset_small/train_small_balanced.jsonl")1181dev_data   = load_jsonl("dataset_small/dev_small_balanced.jsonl")1182test_data  = load_jsonl("dataset_small/test_small_balanced.jsonl")1183 1184# Conversion en Dataset Hugging Face1185train_dataset = Dataset.from_list(train_data)1186dev_dataset   = Dataset.from_list(dev_data)1187test_dataset  = Dataset.from_list(test_data)1188 1189# ==========================================1190# TOKENIZER ET MODÈLE1191# ==========================================1192 1193model_name = "xlm-roberta-base"1194tokenizer = AutoTokenizer.from_pretrained(model_name)1195 1196# ==========================================1197# PRÉTRAITEMENT1198# ==========================================1199 1200def preprocess(example):

Showing the first 1,200 of 2145 lines. Download the file for the rest.