""" Evaluate translation results from JSONL file Compare predictions with ground truth using sacreBleu """ import json import sys import os from pathlib import Path from sacrebleu import corpus_bleu, BLEU def load_jsonl(jsonl_path): """Load predictions from JSONL file""" predictions = [] sources = [] with open(jsonl_path, 'r', encoding='utf-8') as f: for line in f: data = json.loads(line.strip()) sources.append(data['source']) predictions.append(data['translation']) return sources, predictions def load_references(ref_path): """Load reference translations from text file""" with open(ref_path, 'r', encoding='utf-8') as f: references = [line.strip() for line in f] return references def evaluate_bleu(predictions, references): """ Calculate BLEU scores using sacreBleu Args: predictions: List of predicted translations (strings) references: List of reference translations (strings) Returns: Dictionary with BLEU scores """ assert len(predictions) == len(references), \ f"Predictions ({len(predictions)}) and references ({len(references)}) must have same length" results = {} print("\nCalculating sacreBleu scores...") print(f"Total examples: {len(predictions)}") # sacreBleu expects references as list of lists (for multiple references per example) # We have one reference per example refs = [references] # Wrap in list for sacreBleu format # Calculate BLEU for different n-grams using BLEU class for n in range(1, 5): try: # Use BLEU class with max_ngram_order parameter bleu_metric = BLEU( max_ngram_order=n, smooth_method='exp', lowercase=False, tokenize='13a', effective_order=True ) bleu = bleu_metric.corpus_score(predictions, refs) results[f'bleu-{n}'] = bleu.score print(f" BLEU-{n}: {bleu.score:.2f}") except Exception as e: print(f" BLEU-{n}: Error - {e}") results[f'bleu-{n}'] = 0.0 # Show signature for reproducibility bleu_4 = corpus_bleu(predictions, refs, tokenize='13a') print(f"\nsacreBleu signature: {bleu_4.format()}") return results def show_examples(sources, predictions, references, num_examples=5): """Show translation examples""" print("\n" + "=" * 80) print("Translation Examples:") print("=" * 80) for i in range(min(num_examples, len(sources))): print(f"\nExample {i+1}:") print(f" Source: {sources[i][:100]}...") print(f" Reference: {references[i][:100]}...") print(f" Translation: {predictions[i][:100]}...") def main(): # Get project root directory PROJECT_ROOT = Path(__file__).resolve().parent # Paths JSONL_PATH = PROJECT_ROOT / "data" / "processed" / "ep13_test_predict.jsonl" REFERENCE_PATH = PROJECT_ROOT / "data" / "processed" / "test.vi" print("=" * 80) print("BLEU Score Evaluation") print("=" * 80) print(f"Predictions: {JSONL_PATH}") print(f"References: {REFERENCE_PATH}") print("=" * 80) # Check if files exist if not JSONL_PATH.exists(): print(f"\n❌ Error: Predictions file not found: {JSONL_PATH}") return if not REFERENCE_PATH.exists(): print(f"\n❌ Error: Reference file not found: {REFERENCE_PATH}") return # Load data print("\nLoading predictions...") sources, predictions = load_jsonl(JSONL_PATH) print(f" Loaded {len(predictions):,} predictions") print("\nLoading references...") references = load_references(REFERENCE_PATH) print(f" Loaded {len(references):,} references") # Check lengths match if len(predictions) != len(references): print(f"\n⚠️ Warning: Predictions ({len(predictions)}) and references ({len(references)}) have different lengths") min_len = min(len(predictions), len(references)) print(f" Using first {min_len} samples for evaluation") predictions = predictions[:min_len] references = references[:min_len] sources = sources[:min_len] # Show examples show_examples(sources, predictions, references, num_examples=5) # Evaluate print("\n" + "=" * 80) print("BLEU Score Results:") print("=" * 80) results = evaluate_bleu(predictions, references) print("\n" + "=" * 80) print("Summary:") print("=" * 80) print(f"Total samples: {len(predictions):,}") print(f"BLEU-1: {results['bleu-1']:.2f}%") print(f"BLEU-2: {results['bleu-2']:.2f}%") print(f"BLEU-3: {results['bleu-3']:.2f}%") print(f"BLEU-4: {results['bleu-4']:.2f}%") print("=" * 80) # Save results results_path = JSONL_PATH.parent / "ep13_evaluation_results.json" with open(results_path, 'w', encoding='utf-8') as f: json.dump({ 'num_samples': len(predictions), 'bleu_scores': results, 'prediction_file': str(JSONL_PATH), 'reference_file': str(REFERENCE_PATH) }, f, indent=2, ensure_ascii=False) print(f"\n✓ Results saved to: {results_path}") if __name__ == '__main__': main()