""" 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)