File size: 9,442 Bytes
534bcfc | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | #!/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())
|