Commit 1e677a8f authored by Delvallez Delvallez's avatar Delvallez Delvallez

conversion des perturbations et résultats d'un format au nouveau + implem...

conversion des perturbations et résultats d'un format au nouveau + implem nouveau format dans GenericAutomatizedPerturbation.py
parent 6260bd27
...@@ -98,17 +98,18 @@ query_field = "question" # default : "text" ...@@ -98,17 +98,18 @@ query_field = "question" # default : "text"
text_field = "text" # default : "text" text_field = "text" # default : "text"
is_irdata = False is_irdata = False
query_id_subset=["1"] # default : None query_id_subset=["1"] # default : None
sub_dataset_criteria = "pertinent" # default None
perturbations_path = "res/mE5-MainV2/exp6/test/perturbation_polysemie_mainV2.txt" perturbations_path = "res/mE5-MainV2/test/new_pert_test.json"
full_replace = True full_replace = True
gen_matrices = True gen_matrices = True
gen_mean = True gen_mean = False
gen_std = True gen_std = True
res_path = "res/mE5-MainV2/exp6/test/" res_path = "res/mE5-MainV2/test/"
load_values = False # default = False load_values = True # default = False
load_path = "res/mE5-MainV2/exp6/test/perts_values.json" load_path = "res/mE5-MainV2/test/perts_values.json"
save_values = True # default = False save_values = False # default = False
from mechir import Dot from mechir import Dot
...@@ -128,6 +129,7 @@ import numpy as np ...@@ -128,6 +129,7 @@ import numpy as np
from tqdm import tqdm from tqdm import tqdm
import json import json
from copy import deepcopy
#Perturbations parametrées #Perturbations parametrées
...@@ -142,17 +144,16 @@ def param_replace(doc, mot_orig="microwave", mot_rempl="toaster"): ...@@ -142,17 +144,16 @@ def param_replace(doc, mot_orig="microwave", mot_rempl="toaster"):
def param_full_replace(doc, replacmts): def param_full_replace(doc, replacmts):
res = doc res = doc
for replacemt in replacmts: for replacemt in replacmts["compo"]:
assert len(replacemt) == 3 , "Les perturbations de type full-replace doivent se composer de perturbations de type replace seulement" assert replacemt["type"] == "replace" , "Les perturbations de type full-replace doivent se composer de perturbations de type replace seulement"
res.replace(replacemt[1], replacemt[2]) res = res.replace(replacemt["from"], replacemt["to"])
return res return res
# génération des perturbations à produire à partir du document # génération des perturbations à produire à partir du document txt
def generer_transformations(fichier_regles): def generer_transformations_txt(fichier_regles):
transformations = [] transformations = []
perts_name=[]
with open(fichier_regles, "r", encoding="utf-8") as f: with open(fichier_regles, "r", encoding="utf-8") as f:
for ligne in f: for ligne in f:
...@@ -166,28 +167,27 @@ def generer_transformations(fichier_regles): ...@@ -166,28 +167,27 @@ def generer_transformations(fichier_regles):
gauche, droite = ligne.split("->", 1) gauche, droite = ligne.split("->", 1)
mot_present = gauche.strip() mot_present = gauche.strip()
mot_remplacant = droite.strip() mot_remplacant = droite.strip()
# print(f"{gauche}, {droite}") transformations.append({"type":"replace", "from":mot_present, "to":mot_remplacant, "name":ligne})
# print(f"{mot_present}, {mot_remplacant}")
transformations.append( ("replace", mot_present, mot_remplacant)
)
perts_name.append(ligne)
# Cas 2 : ajout en début (mot+) # Cas 2 : ajout en début (mot+)
elif ligne.endswith("+"): elif ligne.endswith("+"):
mot = ligne[:-1].strip() mot = ligne[:-1].strip()
transformations.append( ("prepend", mot)) transformations.append({"type":"prepend", "word":mot, "name":ligne})
perts_name.append(ligne)
# Cas 3 : ajout en fin (+mot) # Cas 3 : ajout en fin (+mot)
elif ligne.startswith("+"): elif ligne.startswith("+"):
mot = ligne[1:].strip() mot = ligne[1:].strip()
transformations.append( ("append", mot)) transformations.append({"type":"append", "word":mot, "name":ligne})
perts_name.append(ligne)
else: else:
raise ValueError(f"Règle non reconnue : {ligne}") raise ValueError(f"Règle non reconnue : {ligne}")
return transformations, perts_name return transformations
def generer_transformations_json(fichier_regles):
with open(fichier_regles, "r") as f:
raw_regles = json.load(f)
return raw_regles
# Helper function to print query/baseline document/perturbed document triplets # Helper function to print query/baseline document/perturbed document triplets
def pretty_print_triplets(batch, tokenizer, num=1): def pretty_print_triplets(batch, tokenizer, num=1):
...@@ -433,29 +433,30 @@ def plot_components_V2( ...@@ -433,29 +433,30 @@ def plot_components_V2(
plt.show() plt.show()
def load_pert_result(filepath): def load_pert_result(filepath):
# chargement du contenu
with open(filepath, 'r', encoding="utf-8") as load_file: with open(filepath, 'r', encoding="utf-8") as load_file:
perts_values = json.load(load_file) perts_values = json.load(load_file)
for pert_name in perts_values: #conversion des listes de listes en ndarray de numpy
if "patching_mean" in perts_values[pert_name] : for pert in perts_values:
perts_values[pert_name]["patching_mean"] = np.asarray(perts_values[pert_name]["patching_mean"]) if "patching_mean" in pert["results"] :
if "patching_std" in perts_values[pert_name]: pert["results"]["patching_mean"] = np.asarray(pert["results"]["patching_mean"])
perts_values[pert_name]["patching_std"] = np.asarray(perts_values[pert_name]["patching_std"]) if "patching_std" in pert["results"]:
pert["results"]["patching_std"] = np.asarray(pert["results"]["patching_std"])
return perts_values return perts_values
def save_pertubs_values(result_file, save_path): def save_pertubs_values(results_values, save_path):
save_values = {} # copie et conversion des array numpy en liste de liste
for pert_name in result_file: save_values = deepcopy(results_values)
save_values[pert_name] = {} for pert in save_values:
save_values[pert_name]["baseline_perf"] = result_file[pert_name]["baseline_perf"] if "patching_mean" in pert["results"] :
save_values[pert_name]["perturbed_perf"] = result_file[pert_name]["perturbed_perf"] pert["results"]["patching_mean"] = pert["results"]["patching_mean"].tolist()
if "patching_mean" in result_file[pert_name] : if "patching_std" in pert["results"] :
save_values[pert_name]["patching_mean"] = result_file[pert_name]["patching_mean"].tolist() pert["results"]["patching_std"] = pert["results"]["patching_std"].tolist()
if "patching_std" in result_file[pert_name]: # sauvegarde
save_values[pert_name]["patching_std"] = result_file[pert_name]["patching_std"].tolist() with open(save_path, 'w', encoding="utf-8") as file:
with open(save_path, 'w', encoding="utf-8") as result_file: json.dump(save_values, file, indent=2)
json.dump(save_values, result_file, indent=2)
def calculate_components(param_pert_dot_dataloader, gen_mean, gen_std): def calculate_components(param_pert_dot_dataloader, gen_mean, gen_std):
patching_head_outputs = [] patching_head_outputs = []
...@@ -490,30 +491,32 @@ def load_dataset(data_path, is_irdata, query_id_subset = None, query_field = "te ...@@ -490,30 +491,32 @@ def load_dataset(data_path, is_irdata, query_id_subset = None, query_field = "te
# print("Number of query,doc pairs in dataset:", len(dataset)) # print("Number of query,doc pairs in dataset:", len(dataset))
else: else:
data = pd.read_csv(data_path) data = pd.read_csv(data_path)
if sub_dataset_criteria is not None:
data = data.query(sub_dataset_criteria)
dataset = MechDataset(data, query_field=query_field, text_field=text_field) dataset = MechDataset(data, query_field=query_field, text_field=text_field)
# print("Number of query,doc pairs in dataset:", len(dataset)) # print("Number of query,doc pairs in dataset:", len(dataset))
return dataset return dataset
def one_pert_traitement( def one_pert_traitement(
pert, dot_model, pert,
dot_model,
dataset, dataset,
full_replace = False,
gen_matrices = False, gen_matrices = False,
gen_mean=False, gen_mean=False,
gen_std=False gen_std=False
): ):
if full_replace: if pert["type"] == "full_replace":
pert_fun = perturbation(lambda texte : param_full_replace(texte, pert)) pert_fun = perturbation(lambda texte : param_full_replace(texte, pert))
pert_type = "replace" pert_type = "replace"
elif pert[0]=="replace": elif pert["type"]=="replace":
pert_fun = perturbation(lambda texte : param_replace(texte, pert[1], pert[2])) pert_fun = perturbation(lambda texte : param_replace(texte, pert["from"], pert["to"]))
pert_type = pert[0] pert_type = pert["type"]
elif pert[0]=="prepend": elif pert["type"]=="prepend":
pert_fun = perturbation(lambda texte : param_prepend(texte, pert[1])) pert_fun = perturbation(lambda texte : param_prepend(texte, pert["word"]))
pert_type = pert[0] pert_type = pert["type"]
elif pert[0]=="append": elif pert["type"]=="append":
pert_fun = perturbation(lambda texte : param_append(texte, pert[1])) pert_fun = perturbation(lambda texte : param_append(texte, pert["word"]))
pert_type = pert[0] pert_type = pert["type"]
else: else:
raise RuntimeError("Unreachable") raise RuntimeError("Unreachable")
param_pert_dot_collator = DotDataCollator(dot_model.tokenizer, pert_fun, perturb_type=pert_type) param_pert_dot_collator = DotDataCollator(dot_model.tokenizer, pert_fun, perturb_type=pert_type)
...@@ -550,29 +553,29 @@ else: ...@@ -550,29 +553,29 @@ else:
# Recup dataset # Recup dataset
dataset = load_dataset(data_path, is_irdata, query_id_subset, query_field, text_field) dataset = load_dataset(data_path, is_irdata, query_id_subset, query_field, text_field)
perts, perts_name = generer_transformations(perturbations_path) match perturbations_path.split(".")[-1].strip():
perts_values = {} case "txt":
if full_replace: print("Appel à generer_transformations_txt")
perts_values["full_replace"] = one_pert_traitement( perts = generer_transformations_txt(perturbations_path)
perts, case "json":
dot_model, print("Appel à generer_transformations_json")
dataset, perts = generer_transformations_json(perturbations_path)
full_replace=True, case _:
gen_matrices= gen_matrices, raise RuntimeError("Unreachable")
gen_mean= gen_mean,
gen_std = gen_std) perts_values = []
perts_values["full_replace"]["descr"] = perts_name for pert in perts:
else: print(pert["name"], "="*50)
for i in range(len(perts)): perts_values.append({
print(perts[i], "="*50) "pert" : pert,
perts_values[perts_name[i]] = one_pert_traitement( "results" : one_pert_traitement(
perts[i], pert,
dot_model, dot_model,
dataset, dataset,
full_replace=False,
gen_matrices= gen_matrices, gen_matrices= gen_matrices,
gen_mean= gen_mean, gen_mean= gen_mean,
gen_std = gen_std) gen_std = gen_std)
})
if save_values: if save_values:
save_pertubs_values(perts_values, f"{res_path}/perts_values.json") save_pertubs_values(perts_values, f"{res_path}/perts_values.json")
...@@ -581,12 +584,12 @@ else: ...@@ -581,12 +584,12 @@ else:
# Génération des graphiques # Génération des graphiques
print("Génération des graphiques") print("Génération des graphiques")
for pert_name, pert_values in perts_values.items(): for pert_values in perts_values:
plot_scores(pert_values["baseline_perf"], pert_values["perturbed_perf"], pert_name, save_path=res_path+"PerturbationScore_"+pert_name+".png") plot_scores(pert_values["results"]["baseline_perf"], pert_values["results"]["perturbed_perf"], pert_values["pert"]["name"], save_path=res_path+"PerturbationScore_"+pert_values["pert"]["name"]+".png")
if "patching_mean" in pert_values and pert_values["patching_mean"] is not None: if "patching_mean" in pert_values["results"] and pert_values["results"]["patching_mean"] is not None:
plot_components_V2(pert_values["patching_mean"], title="Components Patching Results (mean) for "+ pert_name, save_path=res_path+"ComponentPatching-mean_"+pert_name+".png", view_style="base_adapt") plot_components_V2(pert_values["results"]["patching_mean"], title="Components Patching Results (mean) for "+ pert_values["pert"]["name"], save_path=res_path+"ComponentPatching-mean_"+pert_values["pert"]["name"]+".png", view_style="base_adapt")
plot_components_V2(pert_values["patching_mean"], title="Components Patching Results (mean) for "+ pert_name, save_path=res_path+"ComponentPatching-mean_"+pert_name+"V2.png", view_style="base_resc") plot_components_V2(pert_values["results"]["patching_mean"], title="Components Patching Results (mean) for "+ pert_values["pert"]["name"], save_path=res_path+"ComponentPatching-mean_"+pert_values["pert"]["name"]+"V2.png", view_style="base_resc")
if "patching_std" in pert_values and pert_values["patching_std"] is not None: if "patching_std" in pert_values["results"] and pert_values["results"]["patching_std"] is not None:
plot_components_V2(pert_values["patching_std"], title="Components Patching Results (std) for "+ pert_name, save_path=res_path+"ComponentPatching-std_"+pert_name+".png", view_style="std_adapt") plot_components_V2(pert_values["results"]["patching_std"], title="Components Patching Results (std) for "+ pert_values["pert"]["name"], save_path=res_path+"ComponentPatching-std_"+pert_values["pert"]["name"]+".png", view_style="std_adapt")
plot_components_V2(pert_values["patching_std"], title="Components Patching Results (std) for "+ pert_name, save_path=res_path+"ComponentPatching-std_"+pert_name+"V2.png", view_style="std_resc") plot_components_V2(pert_values["results"]["patching_std"], title="Components Patching Results (std) for "+ pert_values["pert"]["name"], save_path=res_path+"ComponentPatching-std_"+pert_values["pert"]["name"]+"V2.png", view_style="std_resc")
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Convertit un fichier texte décrivant des perturbations vers le format JSON.
Le fichier texte peut contenir :
mot1 -> mot2 : replace
mot1 -> : suppression (replace vers chaîne vide)
mot+ : prepend
+mot : append
Si le fichier ne contient que des perturbations replace, l'utilisateur
peut choisir de les interpréter comme :
- une liste de perturbations indépendantes
- une unique perturbation full_replace
"""
import argparse
import json
from pathlib import Path
# ----------------------------------------------------------------------
# Lecture du fichier texte
# ----------------------------------------------------------------------
def lire_perturbations(path):
"""Lit le fichier texte et renvoie la liste des perturbations."""
perturbations = []
with open(path, "r", encoding="utf-8") as f:
for ligne in f:
ligne = ligne.strip()
if not ligne:
continue
if "->" in ligne:
gauche, droite = ligne.split("->", 1)
perturbations.append({
"type": "replace",
"from": gauche.strip(),
"to": droite.strip(),
"name": ligne
})
elif ligne.endswith("+"):
mot = ligne[:-1].strip()
perturbations.append({
"type": "prepend",
"word": mot,
"name": ligne
})
elif ligne.startswith("+"):
mot = ligne[1:].strip()
perturbations.append({
"type": "append",
"word": mot,
"name": ligne
})
else:
raise ValueError(f"Perturbation inconnue : {ligne}")
return perturbations
# ----------------------------------------------------------------------
# Détection du cas full_replace
# ----------------------------------------------------------------------
def uniquement_replace(perturbations):
return all(p["type"] == "replace" for p in perturbations)
def demander_mode():
print()
print("Le fichier ne contient que des perturbations replace.")
print()
print("Comment souhaitez-vous l'interpréter ?")
print(" 1 - Liste de perturbations indépendantes")
print(" 2 - Une perturbation full_replace")
print()
while True:
choix = input("Votre choix (1/2) : ").strip()
if choix in ("1", "2"):
return choix
print("Veuillez répondre 1 ou 2.")
# ----------------------------------------------------------------------
# Construction du JSON
# ----------------------------------------------------------------------
def construire_json(perturbations, full_replace, descr):
if not uniquement_replace(perturbations):
return perturbations
if not full_replace:
return perturbations
return [{
"type": "full_replace",
"name": descr,
"compo": perturbations
}]
# ----------------------------------------------------------------------
# Sauvegarde
# ----------------------------------------------------------------------
def sauvegarder(obj, path):
with open(path, "w", encoding="utf-8") as f:
json.dump(
obj,
f,
ensure_ascii=False,
indent=4
)
# ----------------------------------------------------------------------
# Programme principal
# ----------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="Convertit un fichier txt de perturbations en JSON."
)
parser.add_argument(
"input",
help="Fichier texte d'entrée"
)
parser.add_argument(
"output",
nargs="?",
help="Fichier JSON de sortie (défaut : même nom)"
)
parser.add_argument(
"--full_replace",
action="store_true",
help="Traite le fichier fourni comme une perturbation full_replace. Le fichier ne doit donner que des perturbations replace."
)
parser.add_argument(
"--name",
help="Description de la perturbation full_replace"
)
args = parser.parse_args()
input_path = Path(args.input)
output_path = (
Path(args.output)
if args.output
else input_path.with_suffix(".json")
)
perturbations = lire_perturbations(input_path)
json_obj = construire_json(perturbations, args.full_replace, args.name)
sauvegarder(json_obj, output_path)
print(f"\nJSON enregistré dans : {output_path}")
if __name__ == "__main__":
main()
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Convertit un ancien fichier de résultats MechIR vers le nouveau format.
Cas gérés :
- replace
- prepend
- append
- full_replace
Dans le cas d'une perturbation full_replace, un fichier décrivant les
perturbations (nouveau format JSON) doit être fourni.
"""
import argparse
import json
from pathlib import Path
# ----------------------------------------------------------------------
# Lecture
# ----------------------------------------------------------------------
def charger_json(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
# ----------------------------------------------------------------------
# Parsing d'une perturbation simple (pas full_replace)
# ----------------------------------------------------------------------
def perturbation_depuis_nom(name):
name = name.strip()
if "->" in name:
gauche, droite = name.split("->", 1)
return {
"type": "replace",
"from": gauche.strip(),
"to": droite.strip(),
"name": name
}
if name.endswith("+"):
return {
"type": "prepend",
"word": name[:-1].strip(),
"name": name
}
if name.startswith("+"):
return {
"type": "append",
"word": name[1:].strip(),
"name": name
}
raise ValueError(f"Impossible d'interpréter : {name}")
# ----------------------------------------------------------------------
# Chargement de la description d'une perturbation full_replace
# ----------------------------------------------------------------------
def charger_full_replace(path, n_occ_full_replace):
perts = charger_json(path)
if not isinstance(perts, list):
raise ValueError("Le fichier de description de perturbation est mal formé.")
full = [p for p in perts if p["type"] == "full_replace"]
if len(full) == 0:
raise ValueError("Aucune perturbation full_replace trouvée.")
if len(full) > 1 :
if n_occ_full_replace is None:
raise ValueError(
"Plusieurs perturbations full_replace présentes. "
"Le script ne sait pas laquelle utiliser."
)
if n_occ_full_replace >= len(full):
raise ValueError("Aucune perturbation full_replace ne correspond au numéro d'occurence donné")
return full[n_occ_full_replace]
return full[0]
# ----------------------------------------------------------------------
# Conversion
# ----------------------------------------------------------------------
def convertir(ancien, full_replace=None):
nouveau = []
for nom, valeurs in ancien.items():
if nom == "full_replace":
if full_replace is None:
raise ValueError(
"Le fichier contient une perturbation full_replace.\n"
"Veuillez fournir le fichier de description correspondant."
)
perturbation = full_replace
else:
perturbation = perturbation_depuis_nom(nom)
nouveau.append({
"perturbation": perturbation,
"results": {
"baseline_perf":
valeurs.get("baseline_perf"),
"perturbed_perf":
valeurs.get("perturbed_perf"),
"patching_mean":
valeurs.get("patching_mean"),
"patching_std":
valeurs.get("patching_std")
}
})
return nouveau
# ----------------------------------------------------------------------
# Sauvegarde
# ----------------------------------------------------------------------
def sauvegarder(obj, path):
with open(path, "w", encoding="utf-8") as f:
json.dump(
obj,
f,
ensure_ascii=False,
indent=4
)
# ----------------------------------------------------------------------
# Main
# ----------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
"input",
help="Ancien fichier JSON"
)
parser.add_argument(
"-p",
"--perturbations",
help="Fichier JSON des perturbations (nécessaire pour full_replace)"
)
parser.add_argument(
"-n",
"--perturbation_number",
help="Si plusieurs perturbations full_replace sont présentes dans le fichier JSON décrivant les pertubations, numéro d'occurence de la perturbation full_replace à considérer."
)
parser.add_argument(
"-o",
"--output",
help="Fichier de sortie"
)
args = parser.parse_args()
ancien = charger_json(args.input)
full_replace = None
if "full_replace" in ancien:
if args.perturbations is None:
parser.error(
"Le fichier contient une perturbation full_replace.\n"
"Veuillez fournir --perturbations."
)
full_replace = charger_full_replace(args.perturbations,n_occ_full_replace = args.perturbation_number)
output = (
Path(args.output)
if args.output
else Path(args.input).with_name(
Path(args.input).stem + "_v2.json"
)
)
nouveau = convertir(
ancien,
full_replace=full_replace
)
sauvegarder(nouveau, output)
print(f"Fichier créé : {output}")
if __name__ == "__main__":
main()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment