Spaces:
Running
Running
File size: 6,715 Bytes
69a47f1 0eeb85b 69a47f1 0eeb85b 69a47f1 0eeb85b 443dc7d 69a47f1 443dc7d 69a47f1 443dc7d 69a47f1 443dc7d 69a47f1 443dc7d 69a47f1 443dc7d 69a47f1 443dc7d 69a47f1 443dc7d 69a47f1 443dc7d 69a47f1 0eeb85b 69a47f1 0eeb85b 69a47f1 0eeb85b 69a47f1 0eeb85b 69a47f1 0eeb85b 69a47f1 0eeb85b 69a47f1 | 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 | """
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)
|