validation
This commit is contained in:
parent
83b2eccd07
commit
adca297850
|
|
@ -6,26 +6,6 @@
|
||||||
{"text": "Я зустрінуся з тобою в неділю о третій.", "translation": "On se voit dimanche à trois heures."}
|
{"text": "Я зустрінуся з тобою в неділю о третій.", "translation": "On se voit dimanche à trois heures."}
|
||||||
{"text": "Я сказав собі: «Це гарна ідея».", "translation": "Je me suis dit : « C’est une bonne idée. »"}
|
{"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": "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": "Je me brosse les dents deux fois par jour."}
|
||||||
{"text": "Він ніжно поклав руку на її плече.", "translation": "Il posa la main gentiment sur son épaule."}
|
{"text": "Він ніжно поклав руку на її плече.", "translation": "Il posa la main gentiment sur son épaule."}
|
||||||
{"text": "Сьогодні жахливо холодно.", "translation": "Il fait horriblement froid aujourd'hui."}
|
{"text": "Сьогодні жахливо холодно.", "translation": "Il fait horriblement froid aujourd'hui."}
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,170 @@
|
||||||
import torch
|
import torch
|
||||||
from transformers import AutoTokenizer, AutoModelForCausalLM
|
from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
|
||||||
|
from peft import PeftModel
|
||||||
from datasets import load_dataset
|
from datasets import load_dataset
|
||||||
from nltk.translate.bleu_score import corpus_bleu
|
from nltk.translate.bleu_score import corpus_bleu
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Configuration
|
# Configuration
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
MODEL_DIR = "./qwen2.5-7b-uk-fr-lora" # dossier où tu as sauvegardé LoRA
|
BASE_MODEL = "Qwen/Qwen2.5-7B-Instruct" # base model
|
||||||
VALIDATION_FILE = "validation.jsonl" # petit subset de test (5-50 phrases)
|
LORA_DIR = "./qwen2.5-7b-uk-fr-lora" # fine-tuned LoRA
|
||||||
|
VALIDATION_FILE = "validation.jsonl" # small validation subset
|
||||||
MAX_INPUT_LENGTH = 1024
|
MAX_INPUT_LENGTH = 1024
|
||||||
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
|
|
||||||
print("=== Loading model and tokenizer ===")
|
# Liste des prompts à tester
|
||||||
tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR, trust_remote_code=True)
|
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.pad_token = tokenizer.eos_token
|
||||||
tokenizer.model_max_length = MAX_INPUT_LENGTH
|
tokenizer.model_max_length = MAX_INPUT_LENGTH
|
||||||
|
|
||||||
model = AutoModelForCausalLM.from_pretrained(
|
# ----------------------------
|
||||||
MODEL_DIR,
|
# Load base model directly on GPU
|
||||||
device_map="auto",
|
# ----------------------------
|
||||||
|
print(f"{80 * '_'}\nLoading base model on GPU...")
|
||||||
|
base_model = AutoModelForCausalLM.from_pretrained(
|
||||||
|
BASE_MODEL,
|
||||||
torch_dtype=torch.float16,
|
torch_dtype=torch.float16,
|
||||||
|
device_map={"": 0}, # all on GPU
|
||||||
trust_remote_code=True
|
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
|
# Load validation dataset
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
print("Loading validation dataset...")
|
print(f"{80 * '_'}\nLoading validation dataset...")
|
||||||
dataset = load_dataset("json", data_files=VALIDATION_FILE)
|
dataset = load_dataset("json", data_files=VALIDATION_FILE)
|
||||||
examples = dataset["train"] # petit subset
|
examples = dataset["train"]
|
||||||
print(f"{len(examples)} examples loaded for testing.")
|
print(f"{len(examples)} examples loaded for testing.")
|
||||||
|
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
# Function to generate translation
|
# Translation function
|
||||||
# ----------------------------
|
# ----------------------------
|
||||||
def translate(text):
|
@torch.inference_mode()
|
||||||
prompt = f"Translate the following Ukrainian text into French:\nUkrainian: {text}\nFrench:"
|
def translate(text, prompt_template):
|
||||||
inputs = tokenizer(prompt, return_tensors="pt", truncation=True, max_length=MAX_INPUT_LENGTH).to(DEVICE)
|
prompt = prompt_template.format(text=text)
|
||||||
with torch.no_grad():
|
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(
|
outputs = model.generate(
|
||||||
**inputs,
|
**inputs,
|
||||||
max_new_tokens=256,
|
generation_config=generation_config
|
||||||
do_sample=False, # deterministic
|
|
||||||
eos_token_id=tokenizer.eos_token_id,
|
|
||||||
pad_token_id=tokenizer.pad_token_id
|
|
||||||
)
|
)
|
||||||
|
|
||||||
result = tokenizer.decode(outputs[0], skip_special_tokens=True)
|
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...")
|
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 = []
|
references = []
|
||||||
hypotheses = []
|
hypotheses = []
|
||||||
|
|
||||||
for i, example in enumerate(examples):
|
for i, example in enumerate(examples):
|
||||||
src_text = example["text"]
|
src_text = example["text"]
|
||||||
ref_text = example["translation"]
|
ref_text = example["translation"]
|
||||||
pred_text = translate(src_text)
|
pred_text = translate(src_text, prompt_config["prompt"])
|
||||||
|
|
||||||
print(f"\n[{i+1}] Source: {src_text}")
|
print(f"\n[{i+1}] Source: {src_text}")
|
||||||
print(f" Reference: {ref_text}")
|
print(f" Reference: {ref_text}")
|
||||||
print(f" Prediction: {pred_text}")
|
print(f" Prediction: {pred_text}")
|
||||||
|
|
||||||
# Prepare for BLEU (tokenized by space)
|
|
||||||
references.append([ref_text.split()])
|
references.append([ref_text.split()])
|
||||||
hypotheses.append(pred_text.split())
|
hypotheses.append(pred_text.split())
|
||||||
|
|
||||||
# Compute corpus BLEU
|
bleu_score = corpus_bleu(references, hypotheses) * 100
|
||||||
bleu_score = corpus_bleu(references, hypotheses)
|
print(f"\n=== Corpus BLEU score for '{prompt_config['name']}': {bleu_score:.4f} ===")
|
||||||
print(f"\n=== Corpus BLEU score: {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}")
|
||||||
|
|
||||||
|
print(f"\nBEST PROMPT: {best_prompt['name']} with BLEU score: {best_bleu:.4f}")
|
||||||
|
print(f"Prompt content:\n{best_prompt['prompt']}")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue