From adca297850dc0bac1452aaa55a6bf3bd121b52de Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 14 Jan 2026 17:59:12 +0100 Subject: [PATCH] validation --- Finetunning/validation.jsonl | 20 ---- Finetunning/validation.py | 181 ++++++++++++++++++++++++++--------- 2 files changed, 137 insertions(+), 64 deletions(-) diff --git a/Finetunning/validation.jsonl b/Finetunning/validation.jsonl index c1c1bc9..2775fe9 100644 --- a/Finetunning/validation.jsonl +++ b/Finetunning/validation.jsonl @@ -6,26 +6,6 @@ {"text": "Я зустрінуся з тобою в неділю о третій.", "translation": "On se voit dimanche à trois heures."} {"text": "Я сказав собі: «Це гарна ідея».", "translation": "Je me suis dit : « C’est une bonne idée. »"} {"text": "Ми збиралися пробути там біля двох тижнів.", "translation": "Nous avions l’intention de rester là près de deux semaines."} -{"text": "Як на мене, то наразі помовчу.", "translation": "En ce qui me concerne, je n’ai pour le moment rien à dire."} -{"text": "Мій дядько вчора помер від раку шлунку.", "translation": "Mon oncle est mort hier d’un cancer à l’estomac."} -{"text": "Я не знаю, що ще можна зробити.", "translation": "Je ne sais plus quoi faire."} -{"text": "Я навчився жити без неї.", "translation": "J’ai appris à vivre sans elle."} -{"text": "Мені завжди більше подобалися загадкові персонажі.", "translation": "J’ai toujours préféré les personnages mystérieux."} -{"text": "Тобі краще поспати.", "translation": "Tu ferais mieux de dormir."} -{"text": "Обдумай це.", "translation": "Penses-y."} -{"text": "Наприклад, тобі подобається англійська?", "translation": "Par exemple, est-ce que tu aimes l’anglais ?"} -{"text": "Коли Вам буде зручно?", "translation": "Quand est-ce que ça vous arrangera ?"} -{"text": "Том вільно розмовляє японською.", "translation": "Tom parle couramment japonais."} -{"text": "Ти не знаєш, де мій годинник?", "translation": "Tu ne sais pas où est ma montre ?"} -{"text": "Є ще одне питання, яке нам так само треба обміркувати.", "translation": "Il reste encore une question que nous devons discuter."} -{"text": "Я біжу якомога швидше, щоб наздогнати його.", "translation": "Je cours le plus vite possible pour le rattraper."} -{"text": "Не всі, хто тут живе, багаті.", "translation": "Tous ceux qui habitent ici ne sont pas forcément riches."} -{"text": "Ліки прискорили процес росту.", "translation": "Les médicaments accéléraient la croissance."} -{"text": "Я бачив, як він біг.", "translation": "Je l'ai vu courir."} -{"text": "Як ти ставишся до того, щоб погуляти в парку?", "translation": "Que penserais-tu d'une balade au parc ?"} -{"text": "Наближається Різдво.", "translation": "Noël approche."} -{"text": "Я передзвоню тобі пізніше.", "translation": "Je te rappelle plus tard."} -{"text": "Людина — це єдина тварина, яка може сміятися.", "translation": "L’Homme est le seul animal qui peut rigoler."} {"text": "Я чищу зуби двічі на день.", "translation": "Je me brosse les dents deux fois par jour."} {"text": "Він ніжно поклав руку на її плече.", "translation": "Il posa la main gentiment sur son épaule."} {"text": "Сьогодні жахливо холодно.", "translation": "Il fait horriblement froid aujourd'hui."} diff --git a/Finetunning/validation.py b/Finetunning/validation.py index 98f1b46..030c66b 100644 --- a/Finetunning/validation.py +++ b/Finetunning/validation.py @@ -1,77 +1,170 @@ import torch -from transformers import AutoTokenizer, AutoModelForCausalLM +from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig +from peft import PeftModel from datasets import load_dataset from nltk.translate.bleu_score import corpus_bleu # ---------------------------- # Configuration # ---------------------------- -MODEL_DIR = "./qwen2.5-7b-uk-fr-lora" # dossier où tu as sauvegardé LoRA -VALIDATION_FILE = "validation.jsonl" # petit subset de test (5-50 phrases) +BASE_MODEL = "Qwen/Qwen2.5-7B-Instruct" # base model +LORA_DIR = "./qwen2.5-7b-uk-fr-lora" # fine-tuned LoRA +VALIDATION_FILE = "validation.jsonl" # small validation subset MAX_INPUT_LENGTH = 1024 DEVICE = "cuda" if torch.cuda.is_available() else "cpu" -print("=== Loading model and tokenizer ===") -tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR, trust_remote_code=True) +# Liste des prompts à tester +PROMPTS_TO_TEST = [ + { + "name": "Prompt de base", + "prompt": "Traduis la phrase ukrainienne suivante en français: {text}" + }, + { + "name": "Prompt spécialisé mémoires", + "prompt": ( + "Tu es un traducteur spécialisé dans les mémoires ukrainiennes des années 1910.\n" + "- Garde le style narratif et les tournures orales de l'auteur.\n" + "- Respecte les règles de traduction suivantes :\n\n" + "Règles strictes :\n" + "1. **Conserve tous les noms de lieux** dans leur forme originale (ex. : Львів → Lviv, mais ajoute une note si nécessaire entre [ ]).\n" + "2. **Respecte le style narratif** : garde les tournures orales et les expressions propres à l'auteur.\n\n" + "Voici la phrase à traduire :\nUkrainien : {text}\nFrançais :" + ) + }, + { + "name": "Prompt détaillé", + "prompt": ( + "Tu es un expert en traduction littéraire spécialisé dans les textes historiques ukrainiens.\n" + "Règles à suivre absolument :\n" + "1. Conserve tous les noms propres et toponymes dans leur forme originale\n" + "2. Préserve le style et le registre de l'auteur original\n" + "3. Ajoute des notes entre crochets pour expliquer les références culturelles si nécessaire\n" + "4. Traduis de manière naturelle en français tout en restant fidèle au texte source\n\n" + "Texte à traduire :\nUkrainien : {text}\nTraduction française :" + ) + }, + { + "name": "Prompt minimaliste", + "prompt": "Traduction fidèle de l'ukrainien vers le français : {text}" + } +] + +print("=== Loading tokenizer and model ===") + +# ---------------------------- +# Load tokenizer +# ---------------------------- +tokenizer = AutoTokenizer.from_pretrained( + BASE_MODEL, + trust_remote_code=True +) tokenizer.pad_token = tokenizer.eos_token tokenizer.model_max_length = MAX_INPUT_LENGTH -model = AutoModelForCausalLM.from_pretrained( - MODEL_DIR, - device_map="auto", +# ---------------------------- +# Load base model directly on GPU +# ---------------------------- +print(f"{80 * '_'}\nLoading base model on GPU...") +base_model = AutoModelForCausalLM.from_pretrained( + BASE_MODEL, torch_dtype=torch.float16, + device_map={"": 0}, # all on GPU trust_remote_code=True ) -model.eval() -print("Model loaded.") +# ---------------------------- +# Apply LoRA adapter +# ---------------------------- +print(f"{80 * '_'}\nApplying LoRA adapter...") +model = PeftModel.from_pretrained(base_model, LORA_DIR) +model.eval() +model.to(DEVICE) # ensure everything on GPU +print("Model ready for validation.") # ---------------------------- # Load validation dataset # ---------------------------- -print("Loading validation dataset...") +print(f"{80 * '_'}\nLoading validation dataset...") dataset = load_dataset("json", data_files=VALIDATION_FILE) -examples = dataset["train"] # petit subset +examples = dataset["train"] print(f"{len(examples)} examples loaded for testing.") # ---------------------------- -# Function to generate translation +# Translation function # ---------------------------- -def translate(text): - prompt = f"Translate the following Ukrainian text into French:\nUkrainian: {text}\nFrench:" - inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=MAX_INPUT_LENGTH).to(DEVICE) - with torch.no_grad(): - outputs = model.generate( - **inputs, - max_new_tokens=256, - do_sample=False, # deterministic - eos_token_id=tokenizer.eos_token_id, - pad_token_id=tokenizer.pad_token_id - ) +@torch.inference_mode() +def translate(text, prompt_template): + prompt = prompt_template.format(text=text) + inputs = tokenizer( + prompt, + return_tensors="pt", + truncation=True, + max_length=MAX_INPUT_LENGTH + ).to(DEVICE) + + # Utilisation de GenerationConfig pour éviter les avertissements + generation_config = GenerationConfig.from_model_config(model.config) + generation_config.max_new_tokens = 256 + generation_config.do_sample = False + + outputs = model.generate( + **inputs, + generation_config=generation_config + ) + result = tokenizer.decode(outputs[0], skip_special_tokens=True) - # Remove prompt from result - return result.replace(prompt, "").strip() + + # Extraction de la partie traduction + if "Français :" in result: + translation_part = result.split("Français :")[-1].strip() + elif "Traduction française :" in result: + translation_part = result.split("Traduction française :")[-1].strip() + else: + translation_part = result.split(text)[-1].strip() + + return translation_part # ---------------------------- -# Test all examples and compute BLEU +# Evaluate all prompts and select best BLEU # ---------------------------- -print("Generating translations...") -references = [] -hypotheses = [] +best_bleu = 0 +best_prompt = None +all_results = {} + +print(f"{80 * '_'}\nTesting all prompts and computing BLEU scores...") + +for prompt_config in PROMPTS_TO_TEST: + print(f"\n{80 * '='}\nTesting prompt: {prompt_config['name']}\n{80 * '='}") + references = [] + hypotheses = [] -for i, example in enumerate(examples): - src_text = example["text"] - ref_text = example["translation"] - pred_text = translate(src_text) - - print(f"\n[{i+1}] Source: {src_text}") - print(f" Reference: {ref_text}") - print(f" Prediction: {pred_text}") + for i, example in enumerate(examples): + src_text = example["text"] + ref_text = example["translation"] + pred_text = translate(src_text, prompt_config["prompt"]) - # Prepare for BLEU (tokenized by space) - references.append([ref_text.split()]) - hypotheses.append(pred_text.split()) + print(f"\n[{i+1}] Source: {src_text}") + print(f" Reference: {ref_text}") + print(f" Prediction: {pred_text}") + + references.append([ref_text.split()]) + hypotheses.append(pred_text.split()) + + bleu_score = corpus_bleu(references, hypotheses) * 100 + print(f"\n=== Corpus BLEU score for '{prompt_config['name']}': {bleu_score:.4f} ===") + + all_results[prompt_config["name"]] = bleu_score + + if bleu_score > best_bleu: + best_bleu = bleu_score + best_prompt = prompt_config + +# ---------------------------- +# Display results +# ---------------------------- +print(f"\n{80 * '='}\nFINAL RESULTS\n{80 * '='}") +for prompt_name, score in all_results.items(): + print(f"{prompt_name}: {score:.4f}") -# Compute corpus BLEU -bleu_score = corpus_bleu(references, hypotheses) -print(f"\n=== Corpus BLEU score: {bleu_score:.4f} ===") +print(f"\nBEST PROMPT: {best_prompt['name']} with BLEU score: {best_bleu:.4f}") +print(f"Prompt content:\n{best_prompt['prompt']}")