Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 0 additions & 200 deletions policy_analysis/data/conclusions&pollitiques_synthetiques.jsonl

This file was deleted.

81 changes: 81 additions & 0 deletions policy_analysis/dspy_policies_and_taxonomy_extraction/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Extraction des policies et de la taxonomie avec DSPy

L’objectif de ce dossier est de mettre en place une pipeline complète permettant :

* le nettoyage des chunks de texte en amont
* l’entraînement de modèles d’extraction de policies et de taxonomie
* l’extraction automatique des policies et de la taxonomie à partir de documents
* la génération d’une sortie structurée au format JSON, réutilisable dans un pipeline global

L’ensemble de la pipeline repose sur DSPy, utilisé pour optimiser les prompts et entraîner des modèles robustes pour les tâches d’extraction.

# Initial Chunk Cleaning

Cette partie contient le code chargé de nettoyer les chunks de texte avant toute extraction.

Objectifs :

* supprimer le bruit inutile
* réduire la taille des chunks et le nombre de tokens
* améliorer la qualité de l’extraction des policies et de la taxonomie

Le script principal est :

* `clean_chunks.py`

Les résultats nettoyés sont sauvegardés sous forme de fichiers parquet et jsonl pour inspection et réutilisation.

Cette étape pourrait être déplacée plus en amont dans un pipeline global, mais elle est actuellement intégrée ici.

# Extraction des policies

Cette partie est dédiée à l’entraînement du modèle d’extraction de policies.

L’objectif est d’extraire les policies présentes dans les chunks de texte, en particulier dans les conclusions des papiers de recherche.

DSPy est utilisé afin d’optimiser le prompt servant à l’extraction des policies.
L’optimisation vise à maximiser la similarité entre une policy prédite et une policy de référence, en utilisant un cross-encoder comme métrique.

Il existe 32 données labellisées qui peuvent être utilisées comme dataset de validation.

Des données synthétiques ont été générées afin d’augmenter le volume de données d’entraînement et d’améliorer la robustesse du modèle.

Une fois l’optimisation terminée, le modèle DSPy généré est sauvegardé et peut être rechargé pour les phases d’inférence.


# Données d’entraînement

Le dossier `model_training_data` contient :

* des données gold pour les policies et la taxonomie,
* des données synthétiques générées pour l’entraînement des modèles.

Ces données sont utilisées à la fois pour :

* l’optimisation des prompts DSPy,
* la validation des performances,
* l’entraînement des modèles d’extraction.


# Génération de données synthétiques (taxonomie)

La génération de données synthétiques est principalement utilisée pour la taxonomie.

Le script `taxonomy_data_generator.py` permet de :

* créer des exemples structurés de taxonomie,
* améliorer la couverture des catégories,
* standardiser les formats attendus par le modèle d’extraction.

# Pipeline policy et taxonomie

Le script `pipeline_policy_and_taxonomy_extraction.py` est le point d’entrée principal.

Il permet de :

* charger les modèles DSPy de policy et de taxonomie,
* exécuter l’extraction sur l’ensemble des chunks disponibles,
* consolider les résultats.

La sortie finale est un fichier JSONL contenant l’ensemble des policies et de la taxonomie extraites.

Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import pyarrow.parquet as pq
import pyarrow as pa
import pandas as pd
import re
import json

# ============================================================
# CONFIGURATION
# ============================================================

TEST_RUN = True
INPUT_PATH = "C:/Users/calle/Downloads/chunked_results_conclusions_585k_cs1024_ov100_qw3-06B.parquet"
OUTPUT_JSON_PATH = "cleaned_results_preview.jsonl"
SAMPLE_SIZE = 100

# ============================================================
# REGEX DEFINITIONS
# ============================================================

REGEX_RULES = {
# --- GLOBAL RULES (Applied to whole text) ---

# 1. PUA / Garbage Symbols: Removes  etc.
"remove_pua_symbols": re.compile(r'[\uE000-\uF8FF]+'),

# 2. Trim Garbage Start: Stops at the first "dot + space + Capital"
"trim_garbage_start": re.compile(r'^(?![A-Z]).*?[.,][\s]*(?=[A-Z])', re.DOTALL),

# 3. Results/Discussion Header Cleaning
"results_discussions": re.compile(r'(?:^|(?<=[.!?]))[\s#]*(?:RESULTS|DISCUSSION)\b[:\s]*', re.IGNORECASE),

# 4. SUPER TABLE SOUP
"regex_table_soup": re.compile(
r'(?:\b(?:[A-Z0-9]{1,5}|[A-Z][a-z]{0,3}\.|omponen|Loading|value|Komponen|N/A|Ave|Max|Min|Tot)(?:\s+|$)){3,}',
re.MULTILINE
),

# 5. CITATION REMOVER (NEW)
# Handles: (Smith et al. 2019), (2015-2016), (e.g. Doe, 2020; Lee 2021)
"remove_citations": re.compile(
r'\((?:e\.g\.,?\s*)?(?:[A-Za-z\s.&-]+(?:et\s+al\.?)?,?\s*\d{4}[a-z]?(?:[-–]\d{2,4})?|(?:\d{4}[a-z]?(?:[-–]\d{2,4})?)(?:,\s*\d{4}[a-z]?(?:[-–]\d{2,4})?)*)(?:[;,]\s*(?:[A-Za-z\s.&-]+(?:et\s+al\.?)?,?\s*\d{4}[a-z]?(?:[-–]\d{2,4})?|(?:\d{4}[a-z]?(?:[-–]\d{2,4})?)(?:,\s*\d{4}[a-z]?(?:[-–]\d{2,4})?)*))*\)',
re.IGNORECASE
),

# --- LINE-BY-LINE RULES ---
"url_or_doi": re.compile(r'(?:https?://\s*\S+(?:\s+\S+)?)|(?:\bdoi:\s*10\.\S+(?:\s+\S+)?)', re.IGNORECASE),
"chapter_titles": re.compile(r'^\s*\d+(?:\.\d+)*\.?\s+.*$', re.MULTILINE),
"fig_table_ref": re.compile(r'\s*[\(\[]?(?:see\s+)?(?:Figures?|Figs?\.?|Tables?|Tabs?\.?)\s+(?:S-?)?\d+[A-Za-z]*(?:[-–,]\s*(?:S-?)?\d+[A-Za-z]*)*[\)\]]?', re.IGNORECASE),
"encoding_errors": re.compile(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\ufffd]+'),
"figure_or_table": re.compile(r'\b(?:Figure|Fig\.?|Table)\s+[A-Z]?\d+(?:-\d+)?(?:\s*\([a-z]\))?\b', re.IGNORECASE),
}

# ============================================================
# CLEANING FUNCTION
# ============================================================

def apply_regex_cleaning(text, regex_rules):
if not isinstance(text, str):
return "", 0, []

deleted_count = 0
triggered_rules = []

# --- PHASE 1: Global Rules (Before splitting) ---
global_keys = ["remove_pua_symbols", "trim_garbage_start", "results_discussions", "regex_table_soup", "remove_citations"]

for key in global_keys:
if key in regex_rules:
regex = regex_rules[key]
if regex.search(text):
deleted_count += len(regex.findall(text))
if key not in triggered_rules:
triggered_rules.append(key)
text = regex.sub(" ", text) # Replace with space

# --- PHASE 2: Line-by-Line Processing ---
lines = text.split("\n")
cleaned_lines = []

for line in lines:
line_deleted = 0

# 1. Check for full-line deletion
if regex_rules.get("figure_or_table") and regex_rules["figure_or_table"].match(line.strip()):
deleted_count += 1
if "figure_or_table" not in triggered_rules:
triggered_rules.append("figure_or_table")
continue

# 2. Apply inline regexes
for name, regex in regex_rules.items():
if name in global_keys or name == "figure_or_table":
continue

matches = regex.findall(line)
if matches:
line_deleted += len(matches)
line = regex.sub("", line)
if name not in triggered_rules:
triggered_rules.append(name)

if line.strip():
cleaned_lines.append(line.strip())

deleted_count += line_deleted

# Final cleanup
final_text = " ".join(cleaned_lines)
final_text = re.sub(r'\s+', ' ', final_text).strip()

return final_text, deleted_count, triggered_rules

# ============================================================
# MAIN EXECUTION
# ============================================================

parquet_file = pq.ParquetFile(INPUT_PATH)
target_col = "input_text" if "input_text" in parquet_file.schema.names else "text"

all_results = []

for batch in parquet_file.iter_batches(batch_size=10000):
df = batch.to_pandas()
if target_col not in df.columns:
continue

if TEST_RUN:
df = df.head(SAMPLE_SIZE)

for idx, text in enumerate(df[target_col]):
cleaned_text, deleted_count, triggered_rules = apply_regex_cleaning(text, REGEX_RULES)

result = {
"cleaned_text": cleaned_text,
"deleted_count": deleted_count,
"triggered_rules": triggered_rules
}
all_results.append(result)

if TEST_RUN:
break

with open(OUTPUT_JSON_PATH, "w", encoding="utf-8") as f:
for entry in all_results:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")

print(f"✅ Nettoyage terminé. Résultat JSONL sauvegardé dans {OUTPUT_JSON_PATH}")
Binary file not shown.
Loading
Loading