ProCreations's picture
download
raw
11.9 kB
import os
from FlagEmbedding import BGEM3FlagModel
import torch
import numpy as np
import random
import json
BGEM3_model = None
# 设置缓存目录
os.environ["TRANSFORMERS_CACHE"] = './model'
def load_BGEM3_model():
global BGEM3_model
if BGEM3_model is None:
BGEM3_model = BGEM3FlagModel('BAAI/bge-m3', use_fp16=True, device="cuda:0")
else:
pass
def get_embeddings(doc_list):
embeddings = BGEM3_model.encode(doc_list,
batch_size=64,
max_length=2560, # If you don't need such a long length, you can set a smaller value to speed up the encoding process.
)['dense_vecs']
return embeddings
import logging
from datetime import datetime
def print_metrics_table(title, metrics_dict, ks=(5, 10, 50), filename=''):
#要把这个也记录在log/{filename}_{timestamp}.log里面
log_dir = "log"
os.makedirs(log_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_path = os.path.join(log_dir, f"{filename}.log")
with open(log_path, "a", encoding="utf-8") as log_file:
print(f"== {title}_{timestamp} ==")
header = f"{'@K':>4} | {'P':>7} {'R':>7} {'F1':>7} {'Hit':>7} {'nDCG':>7} {'MRR':>7}"
print(header)
print("-" * len(header))
log_file.write(f"\n== {title} ==\n")
log_file.write(header + "\n")
log_file.write("-" * len(header) + "\n")
for k in ks:
m = metrics_dict[k]
print(f"{k:>4} | {m['P']:.4f} {m['R']:.4f} {m['F1']:.4f} {m['Hit']:.4f} {m['nDCG']:.4f} {m['MRR']:.4f}")
log_file.write(f"{k:>4} | {m['P']:.4f} {m['R']:.4f} {m['F1']:.4f} {m['Hit']:.4f} {m['nDCG']:.4f} {m['MRR']:.4f}\n")
from math import log2
from typing import List, Dict, Tuple, Set, Any, Iterable
# ---------- helpers ----------
def _alpha_for_gt(gt: Tuple[Set[str], Set[str]], alpha: float) -> float | None:
"""
新规则:
- M_gt 为空且 T_gt 非空 → 返回 0.0 (只用工具召回)0*1+1*T_S 假如T_S为0.6 则相似度为0.6;如果返回0.5, 则相似度为0.5*1+0.5*0.6=0.8
- T_gt 为空且 M_gt 非空 → 返回 1.0 (只用模型匹配)
- M_gt 与 T_gt 都为空 → 返回 None(调用处直接视为相似度=1)
- 二者都非空 → 返回给定 alpha
"""
M_gt, T_gt = gt
if len(M_gt) == 0 and len(T_gt) == 0:
return None
if len(M_gt) == 0 and len(T_gt) > 0:
return 0.5
if len(T_gt) == 0 and len(M_gt) > 0:
return 1.0
return alpha
def _collect_strs_raw(x: Any) -> Iterable[str]:
"""只抽取原始字符串,不做任何清洗/归一化。"""
out = []
if isinstance(x, str):
out.append(x)
elif isinstance(x, dict):
for k in ("name", "id", "tool", "model", "title"):
v = x.get(k)
if isinstance(v, str) and v:
out.append(v)
elif isinstance(x, (list, tuple, set)):
for v in x:
out.extend(_collect_strs_raw(v))
return out
def _as_agent_tuple(x: Any) -> Tuple[Set[str], Set[str]]:
"""
硬匹配:模型来自 M.name(或其他字符串字段),工具来自 T.tools(字符串列表)。
同时兼容 models/tools 顶层字段。均为原样字符串集合,不做任何规整。
"""
if isinstance(x, tuple) and len(x) == 2:
m, t = x
M = {s for s in m if isinstance(s, str)}
T = {s for s in t if isinstance(s, str)}
return M, T
if not isinstance(x, dict):
raise ValueError("Agent must be dict or (models, tools) tuple.")
models, tools = set(), set()
# 标准位:M.name
M = x.get("M") or {}
if isinstance(M, dict):
for s in _collect_strs_raw(M):
models.add(s)
# 标准位:T.tools
T = x.get("T") or {}
if isinstance(T, dict):
for s in _collect_strs_raw(T.get("tools", [])):
tools.add(s)
# 兼容旧字段
for s in _collect_strs_raw(x.get("models", [])):
models.add(s)
for s in _collect_strs_raw(x.get("tools", [])):
tools.add(s)
return models, tools
def gt_position_weights(L: int, scheme: str = "log") -> List[float]:
"""Weight earlier GT positions higher; sum to 1."""
if L == 0:
return []
if scheme == "log":
raw = [1.0 / log2(i + 2) for i in range(L)] # i:0-based -> rank i+1
elif scheme == "linear":
raw = [float(L - i) for i in range(L)]
else:
raw = [1.0] * L
s = sum(raw)
return [x / s for x in raw]
def tool_recall(T_rec: Set[str], T_gt: Set[str]) -> float:
"""
工具召回率: 用“推荐工具集”对“目标工具集”的召回率。
- 若 GT 工具集为空,返回 1.0(不惩罚)。
"""
if len(T_gt) == 0:
return 1.0
return len(T_rec & T_gt) / len(T_gt)
def model_match(M_rec: Set[str], M_gt: Set[str]) -> float:
"""
模型完全匹配:任意一个模型字符串相同则判 1,否则 0。
- 若 GT 模型集为空,返回 1.0(GT 未指定,不惩罚)。
"""
if len(M_gt) == 0:
return 1.0
return 1.0 if (M_rec & M_gt) else 0.0
def agent_similarity(A: Tuple[Set[str], Set[str]],
B: Tuple[Set[str], Set[str]],
alpha: float = 0.4) -> float:
"""
混合相似度:
sim = alpha * ModelMatch + (1 - alpha) * ToolRecall
注:ModelMatch=1 若存在任一相同模型(M_gt 为空时该函数原本返回 1,但在外层我们会用 alpha=0 绕过);
ToolRecall=|T_rec ∩ T_gt|/|T_gt|(T_gt 为空时该函数原本返回 1,但在外层我们会用 alpha=1 绕过)。
"""
M_rec, T_rec = A
M_gt, T_gt = B
# 仍沿用你原有的两个子打分
mm = model_match(M_rec, M_gt)
tr = tool_recall(T_rec, T_gt)
return alpha * mm + (1 - alpha) * tr
def soft_rel(rec_agent: Tuple[Set[str], Set[str]],
gt_agents: List[Tuple[Set[str], Set[str]]],
beta: List[float],
alpha: float) -> float:
"""
软相关度:对每个 GT 计算相似度并乘以该 GT 的位置权重 beta[i],取最大值。
采用新规则的 α 选择逻辑(见 _alpha_for_gt)。
"""
best = 0.0
for i, g in enumerate(gt_agents):
alpha_i = _alpha_for_gt(g, alpha)
if alpha_i is None:
# GT 的 M 与 T 都为空 → 直接记为满分相似度 1.0
s = 1.0
else:
s = agent_similarity(rec_agent, g, alpha=alpha_i)
val = beta[i] * s
if val > best:
best = val
return best
def config_ndcg_at_k(R, G, K, alpha, beta):
"""
R: List[agent_obj] (此次评测的候选池/推荐序列)
G: List[gt_agent_obj]
K: int
alpha: float
beta: List[float] # 由 gt_position_weights 得到
"""
if K <= 0 or not R or not G:
return 0.0
# 与 DCG 完全同构的相关度定义
rels_all = [soft_rel(r, G, beta, alpha) for r in R] # 对候选池逐个算 soft_rel
k = min(K, len(rels_all))
top_rels = rels_all[:k] # 这里假设 R 已按你的打分排序
dcg = sum(top_rels[j] / log2(j + 2) for j in range(k))
# IDCG 用同一候选池 & 同一 soft_rel,但理想排序(降序)
ideal_rels = sorted(rels_all, reverse=True)[:k]
idcg = sum(ideal_rels[j] / log2(j + 2) for j in range(k))
return (dcg / idcg) if idcg > 0 else 0.0
# ---------- configuration tailored evaluation ----------
def evaluate_agents(
gt_agents: List[Any],
rec_agents: List[Any],
ks: List[int] = [5, 10, 50],
alpha: float = 0.4,
theta: float = 0.67,
) -> Dict[int, Dict[str, float]]:
# 归一化
G = [_as_agent_tuple(x) for x in gt_agents]
R = [_as_agent_tuple(x) for x in rec_agents]
L = len(G)
beta = [1] * L # 你当前用等权
results: Dict[int, Dict[str, float]] = {}
for K in ks:
k = min(K, len(R))
if k == 0 or L == 0:
results[K] = {"Precision": 0, "Recall": 0, "F1": 0, "Config-nDCG": 0, "MRR": 0}
continue
# 先为 top-K 每个推荐,找到“最佳匹配 GT(索引)”及其 soft_rel 分数
best_scores = []
best_idx = []
for j in range(k):
r = R[j]
s_best, i_best = 0.0, None
for i, g in enumerate(G):
alpha_i = _alpha_for_gt(g, alpha)
s = 1.0 if alpha_i is None else agent_similarity(r, g, alpha=alpha_i)
s *= beta[i] # 等权就是原值
if s > s_best:
s_best, i_best = s, i
best_scores.append(s_best)
best_idx.append(i_best)
# 覆盖式计数:一个 GT 只算一次
covered = set()
unique_hits = []
for rank, (s, gi) in enumerate(zip(best_scores, best_idx), start=1):
if gi is not None and s >= theta and gi not in covered:
covered.add(gi)
unique_hits.append(1)
else:
unique_hits.append(0)
tp = len(covered) # 覆盖到的 GT 数
precision = tp / max(1, k) # 覆盖式 Precision
recall = tp / max(1, L) # 一定 ≤ 1
f1 = (2*precision*recall/(precision+recall)) if (precision+recall)>0 else 0.0
# MRR:第一次“新增覆盖”出现的排名
mrr = 0.0
seen = set()
for rank, (s, gi) in enumerate(zip(best_scores, best_idx), start=1):
if gi is not None and s >= theta and gi not in seen:
mrr = 1.0 / rank
break
# Config-nDCG@K(你的定义保留)
cndcg = config_ndcg_at_k(R, G, K, alpha, beta)
results[K] = {"Precision": precision, "Recall": recall, "F1": f1, "Config-nDCG": cndcg, "MRR": mrr}
return results
# ---------------- data I/O ----------------
def load_json(p: str):
with open(p, "r", encoding="utf-8") as f:
return json.load(f)
def collect_data(parts = ["PartI", "PartII", "PartIII"], data_root = '.'):
all_agents: Dict[str, dict] = {}
all_questions: Dict[str, dict] = {}
all_rankings: Dict[str, List[str]] = {}
for part in parts:
agents_path = os.path.join(data_root, part, "agents", "merge.json")
questions_path = os.path.join(data_root, part, "questions", "merge.json")
rankings_path = os.path.join(data_root, part, "rankings", "merge.json")
agents = load_json(agents_path)
questions = load_json(questions_path)
rankings = load_json(rankings_path)
all_agents.update(agents)
all_questions.update(questions)
all_rankings.update(rankings["rankings"])
return all_agents, all_questions, all_rankings
def ensure_cache_dir_of(data_root: str, train_filename: str) -> str:
d = os.path.join(data_root, f".cache/{train_filename}")
if not os.path.isdir(d):
raise FileNotFoundError(f"[cache] not found: {d}\n"
f"Hint: run training script first to create this namespace.")
return d
# ---------------- printer ----------------
def print_table(avg: Dict[int, Dict[str, float]], ks: List[int]):
headers = ["@K", "Precision", "Recall", "F1", "Config-nDCG", "MRR"]
print("\nEvaluation (utils.evaluate_agents) — averaged over questions")
print("".join([f"{h:>14s}" for h in headers]))
print("-" * (14 * len(headers)))
for k in ks:
m = avg[k]
print(f"{('@'+str(k)):>14s}"
f"{m['Precision']:14.4f}"
f"{m['Recall']:14.4f}"
f"{m['F1']:14.4f}"
f"{m['Config-nDCG']:14.4f}"
f"{m['MRR']:14.4f}")

Xet Storage Details

Size:
11.9 kB
·
Xet hash:
2fe902a877ef7eb654848c3af1c16f047eaf80d076bc2f595fd91c905f343ff0

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.