#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ LongLaMP 任务评估脚本 计算 BLEU, ROUGE, METEOR 等指标 # 指定输出文件 python evaluation/evaluate_longlamp.py \ --predictions_file /opt/tiger/PPD/data/long_output/topic_writing_zero_shot/predictions_topic_writing.json \ --task topic_writing \ --output_file /opt/tiger/PPD/data/long_output/topic_writing_zero_shot_metrics.json python evaluation/evaluate_longlamp.py \ --predictions_file /opt/tiger/PPD/data/long_output/product_review_zero_shot/predictions_product_review.json \ --task product_review \ --output_file /opt/tiger/PPD/data/long_output/product_review_zero_shot_metrics.json python evaluation/evaluate_longlamp.py \ --predictions_file /opt/tiger/PPD/data/long_output/abstract_generation_zero_shot/predictions_abstract_generation.json \ --task abstract_generation \ --output_file /opt/tiger/PPD/data/long_output/abstract_generation_zero_shot_metrics.json """ import json import argparse import logging from pathlib import Path from typing import List, Dict import evaluate # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) def postprocess_text(preds: List[str], labels: List[str]) -> tuple: """ 后处理文本:去除首尾空格 Args: preds: 预测文本列表 labels: 标签文本列表 Returns: (处理后的预测列表, 处理后的标签列表) """ preds = [pred.strip() for pred in preds] labels = [[label.strip()] for label in labels] # ROUGE 需要列表格式 return preds, labels def compute_metrics(decoded_preds: List[str], decoded_labels: List[str]) -> Dict: """ 计算评估指标:BLEU, ROUGE, METEOR Args: decoded_preds: 预测文本列表 decoded_labels: 标签文本列表 Returns: 包含所有指标的字典 """ # 后处理文本 decoded_preds, decoded_labels = postprocess_text(decoded_preds, decoded_labels) # 加载评估指标 logger.info("加载评估指标...") bleu_metric = evaluate.load("sacrebleu") rouge_metric = evaluate.load('rouge') meteor_metric = evaluate.load('meteor') # 计算 BLEU logger.info("计算 BLEU 分数...") result_bleu = bleu_metric.compute(predictions=decoded_preds, references=decoded_labels) # 计算 ROUGE logger.info("计算 ROUGE 分数...") result_rouge = rouge_metric.compute(predictions=decoded_preds, references=decoded_labels) # 计算 METEOR logger.info("计算 METEOR 分数...") result_meteor = meteor_metric.compute(predictions=decoded_preds, references=decoded_labels) # 整理结果 result = { "bleu": result_bleu["score"], "rouge-1": result_rouge["rouge1"], "rouge-2": result_rouge["rouge2"], "rouge-L": result_rouge["rougeL"], "rouge-LSum": result_rouge["rougeLsum"], "meteor": result_meteor['meteor'] } return result def load_predictions(predictions_file: str) -> List[Dict]: """ 加载预测结果文件 Args: predictions_file: 预测结果 JSON 文件路径 Returns: 预测结果列表 """ predictions_path = Path(predictions_file) if not predictions_path.exists(): raise FileNotFoundError(f"预测文件不存在: {predictions_file}") with open(predictions_path, 'r', encoding='utf-8') as f: predictions = json.load(f) if not isinstance(predictions, list): raise ValueError(f"预测文件格式错误:期望列表,得到 {type(predictions)}") logger.info(f"加载了 {len(predictions)} 个预测结果") return predictions def extract_predictions_and_labels(predictions: List[Dict]) -> tuple: """ 从预测结果中提取预测文本和标签 Args: predictions: 预测结果列表,每个元素包含 'generated' 和 'output' 字段 Returns: (preds, labels) 元组 """ preds = [] labels = [] skipped_count = 0 for idx, pred in enumerate(predictions): generated = pred.get("generated", "") output = pred.get("output", "") # 只处理有效的预测和标签 if generated and output: preds.append(generated) labels.append(output) else: skipped_count += 1 sample_id = pred.get('id', f'index_{idx}') logger.warning( f"跳过无效样本: id={sample_id}, " f"generated={'存在' if generated else '缺失'}, " f"output={'存在' if output else '缺失'}" ) if skipped_count > 0: logger.warning(f"跳过了 {skipped_count} 个无效样本") logger.info(f"提取了 {len(preds)} 个有效预测-标签对") if len(preds) == 0: raise ValueError("没有有效的预测-标签对,无法进行评估") return preds, labels def evaluate_predictions( predictions_file: str, output_file: str = None, task_name: str = None ) -> Dict: """ 评估预测结果 Args: predictions_file: 预测结果 JSON 文件路径 output_file: 输出结果文件路径(可选) task_name: 任务名称(可选,用于输出文件名) Returns: 评估结果字典 """ logger.info("=" * 50) logger.info("开始评估") logger.info("=" * 50) logger.info(f"预测文件: {predictions_file}") # 加载预测结果 predictions = load_predictions(predictions_file) # 提取预测和标签 preds, labels = extract_predictions_and_labels(predictions) # 计算指标 logger.info("计算评估指标...") metrics = compute_metrics(preds, labels) # 打印结果 logger.info("=" * 50) logger.info("评估结果") logger.info("=" * 50) for metric_name, metric_value in metrics.items(): if isinstance(metric_value, dict): # 某些指标返回字典(如 ROUGE) logger.info(f"{metric_name}:") for k, v in metric_value.items(): if isinstance(v, (int, float)): logger.info(f" {k}: {v:.4f}") else: logger.info(f" {k}: {v}") else: if isinstance(metric_value, (int, float)): logger.info(f"{metric_name}: {metric_value:.4f}") else: logger.info(f"{metric_name}: {metric_value}") # 保存结果 if output_file: output_path = Path(output_file) else: # 如果没有指定输出文件,使用默认路径 predictions_path = Path(predictions_file) if task_name: output_path = predictions_path.parent / f"metrics_{task_name}.json" else: output_path = predictions_path.parent / "metrics.json" output_path.parent.mkdir(parents=True, exist_ok=True) # 构建完整结果 result = { "task": task_name or "unknown", "num_samples": len(preds), "metrics": metrics } with open(output_path, 'w', encoding='utf-8') as f: json.dump(result, f, ensure_ascii=False, indent=2) logger.info(f"评估结果已保存到: {output_path}") return metrics def main(): parser = argparse.ArgumentParser( description="LongLaMP 任务评估脚本", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" 示例用法: # 基本用法 python evaluate_longlamp.py \\ --predictions_file ./output/predictions_topic_writing.json \\ --task topic_writing # 指定输出文件 python evaluate_longlamp.py \\ --predictions_file ./output/predictions_topic_writing.json \\ --task topic_writing \\ --output_file ./output/metrics.json """ ) parser.add_argument( "--predictions_file", type=str, required=True, help="预测结果 JSON 文件路径(推理脚本的输出文件)" ) parser.add_argument( "--output_file", type=str, default=None, help="评估结果输出文件路径(可选,默认保存在预测文件同目录)" ) parser.add_argument( "--task", type=str, default=None, choices=["abstract_generation", "product_review", "topic_writing"], help="任务名称(可选,用于输出文件名)" ) args = parser.parse_args() try: # 运行评估 metrics = evaluate_predictions( predictions_file=args.predictions_file, output_file=args.output_file, task_name=args.task ) logger.info("=" * 50) logger.info("评估完成") logger.info("=" * 50) # 返回退出码 return 0 except FileNotFoundError as e: logger.error(f"文件未找到: {e}") return 1 except ValueError as e: logger.error(f"数据格式错误: {e}") return 1 except Exception as e: logger.error(f"评估过程中出错: {e}", exc_info=True) return 1 if __name__ == "__main__": exit(main())