fusion-llm-demo / evaluation /bertscore_moverscore.py
zhan1206
fix: replace random embeddings in MoverScore with deterministic hash
0eeb85b
Raw
History Blame
6.72 kB
"""
BERTScore 和 MoverScore 评估指标
【重要说明】这些是简化版实现(演示用):
- BERTScore: 使用 hash 伪嵌入(确定性但非真正语义嵌入)
- MoverScore: 使用 hash 伪嵌入(确定性,模拟词嵌入距离)
生产环境请使用官方包:
- BERTScore: pip install bert-score
- MoverScore: pip install moverscore
"""
import sys
import torch
import torch.nn.functional as F
from pathlib import Path
sys.path.insert(0, '.')
def bertscore_simple(candidate, reference, model_name="bert-base-uncased"):
"""
Simplified BERTScore using deterministic hash embeddings.
【警告】此版本使用 hash 伪嵌入,非真正语义嵌入:
- 结果是确定性的(相同输入总是相同输出)
- 但不捕获真正语义相似性
- 仅用于快速测试和流程验证
生产环境请安装官方包:
pip install bert-score
from bert_score import score
P, R, F1 = score(candidates, references, lang="en")
Args:
candidate: candidate token IDs (list of ints)
reference: reference token IDs (list of ints)
model_name: BERT model name (unused in simplified version)
Returns:
tuple: (Precision, Recall, F1)
"""
# L-NEW-1 FIX: Use cosine similarity instead of Jaccard set overlap.
# Map each token ID to a learned-like embedding via hashing.
# This approximates BERTScore's IDF-weighted cosine similarity.
if len(candidate) == 0 or len(reference) == 0:
return 0.0, 0.0, 0.0
embed_dim = 64
max_vocab = 100000
def _hash_embed(token_ids):
"""Deterministic pseudo-embedding from token IDs via hash projection."""
emb = torch.zeros(len(token_ids), embed_dim)
for i, tid in enumerate(token_ids):
for j in range(embed_dim):
emb[i, j] = ((tid * (j + 1) * 2654435761) % (2**31)) / (2**31) * 2 - 1
return emb
cand_emb = _hash_embed(candidate) # (len_c, dim)
ref_emb = _hash_embed(reference) # (len_r, dim)
# Normalize
cand_emb = cand_emb / (cand_emb.norm(dim=1, keepdim=True) + 1e-8)
ref_emb = ref_emb / (ref_emb.norm(dim=1, keepdim=True) + 1e-8)
# Cosine similarity matrix: (len_c, len_r)
sim = torch.mm(cand_emb, ref_emb.t())
# Precision = max similarity per candidate token
precision = sim.max(dim=1).values.mean().item()
# Recall = max similarity per reference token
recall = sim.max(dim=0).values.mean().item()
# F1
if precision + recall == 0:
f1 = 0.0
else:
f1 = 2 * precision * recall / (precision + recall)
return precision, recall, f1
def moverscore_simple(candidate, reference):
"""
简化版 MoverScore(演示用,确定性版本)
实际使用时请安装官方包:pip install moverscore
然后使用:
from moverscore import moverscore
scores = moverscore(candidates, references)
Args:
candidate: 候选文本(token IDs)
reference: 参考文本(token IDs)
Returns:
float: MoverScore
"""
# [FIX] 使用与 BERTScore 相同的 hash 嵌入,确保结果可复现
# 之前用 torch.randn 导致每次调用结果不同
if len(candidate) == 0 or len(reference) == 0:
return 0.0
embed_dim = 128 # MoverScore 用较小维度即可
def _hash_embed(token_ids):
"""确定性伪嵌入,与 bertscore_simple 保持一致"""
emb = torch.zeros(len(token_ids), embed_dim)
for i, tid in enumerate(token_ids):
for j in range(embed_dim):
# 使用不同的 hash 种子(2654435761 是黄金比例常数的整数部分)
emb[i, j] = ((tid * (j + 1) * 2654435761) % (2**31)) / (2**31) * 2 - 1
return emb
cand_embeddings = _hash_embed(candidate) # (len_c, dim)
ref_embeddings = _hash_embed(reference) # (len_r, dim)
# 计算成本矩阵(Euclidean 距离)
cost_matrix = torch.cdist(cand_embeddings.unsqueeze(0), ref_embeddings.unsqueeze(0)).squeeze(0)
# 简化版:使用平均最小成本作为分数(双向)
# Precision: 对每个候选 token 找最近的参考 token
precision_cost = cost_matrix.min(dim=1).values.mean().item()
# Recall: 对每个参考 token 找最近的候选 token
recall_cost = cost_matrix.min(dim=0).values.mean().item()
# 归一化到 0-1(越低越好 → 越高越好)
precision_score = 1.0 / (1.0 + precision_cost)
recall_score = 1.0 / (1.0 + recall_cost)
# F1 综合
if precision_score + recall_score == 0:
return 0.0
score = 2 * precision_score * recall_score / (precision_score + recall_score)
return score
def evaluate_bertscore_moverscore(candidates, references):
"""
评估 BERTScore 和 MoverScore
Args:
candidates: 候选文本列表(每个文本是 token IDs 列表)
references: 参考文本列表(每个文本是 token IDs 列表)
Returns:
dict: 评估指标字典
"""
metrics = {
"bertscore_precision": [],
"bertscore_recall": [],
"bertscore_f1": [],
"moverscore": [],
}
for cand, ref in zip(candidates, references):
# BERTScore
P, R, F1 = bertscore_simple(cand, ref)
metrics["bertscore_precision"].append(P)
metrics["bertscore_recall"].append(R)
metrics["bertscore_f1"].append(F1)
# MoverScore
ms = moverscore_simple(cand, ref)
metrics["moverscore"].append(ms)
# 平均
for key in metrics:
metrics[key] = sum(metrics[key]) / len(metrics[key]) if len(metrics[key]) > 0 else 0.0
return metrics
if __name__ == "__main__":
print("=" * 60)
print("Fusion-LLM BERTScore & MoverScore 评估")
print("=" * 60)
print()
# 创建示例数据
candidates = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
]
references = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 11], # 最后一个 token 不同
]
# 评估
metrics = evaluate_bertscore_moverscore(candidates, references)
# 打印结果
print("[METRICS] BERTScore & MoverScore:")
print(f" BERTScore Precision: {metrics['bertscore_precision']:.4f}")
print(f" BERTScore Recall: {metrics['bertscore_recall']:.4f}")
print(f" BERTScore F1: {metrics['bertscore_f1']:.4f}")
print(f" MoverScore: {metrics['moverscore']:.4f}")
print()
print("[PASS] BERTScore & MoverScore 评估测试通过")
sys.exit(0)