SAP-ERP-AI-Agent / src /evaluation /_bertscore.py
daisysooyeon's picture
deploy: SAP ERP AI Agent (HF Spaces docker)
50efdc6
Raw
History Blame Contribute Delete
5.02 kB
"""
src/evaluation/_bertscore.py
BERTScore F1 ๊ณตํ†ต ํ—ฌํผ โ€” eval_worker_b / eval_e2e๊ฐ€ ๋‘˜ ๋‹ค ์‚ฌ์šฉ.
BERTScore๋Š” BERT ์ž„๋ฒ ๋”ฉ ๊ธฐ๋ฐ˜ ํ† ํฐ ๋‹จ์œ„ ์œ ์‚ฌ๋„ โ†’ precision/recall/F1 ์‚ฐ์ถœ.
n-gram(BLEU/ROUGE)๊ณผ ๋‹ฌ๋ฆฌ ํŒจ๋Ÿฌํ”„๋ ˆ์ด์ฆˆ์™€ ๋™์˜์–ด๋ฅผ ๊ฐ•ํ•˜๊ฒŒ ์žก์•„์ฃผ๋ฏ€๋กœ ์ด๋ฉ”์ผ
๋‹ต๋ณ€ยทRAG ๋‹ต๋ณ€์ฒ˜๋Ÿผ ํ‘œํ˜„์ด ๋‹ค์–‘ํ•œ ์ถœ๋ ฅ์— ์ ํ•ฉ.
์‚ฌ์šฉ:
from src.evaluation._bertscore import compute_bertscore_f1
per_case, mean = compute_bertscore_f1(
candidates=["the customer asked โ€ฆ", โ€ฆ],
references=["the customer wants โ€ฆ", โ€ฆ],
)
์„ค๊ณ„:
- ์ฒซ ํ˜ธ์ถœ์—์„œ ๋ชจ๋ธ์„ ํ•œ ๋ฒˆ๋งŒ ๋กœ๋“œ (lazy + cache).
- bert_score ๋ฏธ์„ค์น˜ / ๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ ์‹œ graceful: per_case=[None,โ€ฆ], mean=None์„ ๋ฐ˜ํ™˜ํ•˜๊ณ 
๋กœ๊ทธ๋งŒ ๋‚จ๊ธด๋‹ค. ํ˜ธ์ถœ์ž๋Š” ์ด ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋ฆฌํฌํŠธ์— None์œผ๋กœ ํ˜๋ ค๋ณด๋‚ด๋ฉด ๋œ๋‹ค.
- ๋นˆ reference/candidate ์Œ์€ ์ž๋™์œผ๋กœ None ์ฒ˜๋ฆฌ (์˜๋ฏธ ๋น„๊ต ๋ถˆ๊ฐ€).
- ๋ฉ”๋ชจ๋ฆฌ ๋ถ€๋‹ด ์ค„์ด๋ ค๊ณ  ํ•œ ๋ฒˆ์— ๋ชจ๋“  pair๋ฅผ ๋ฐฐ์น˜ ํ˜ธ์ถœ.
"""
from __future__ import annotations
import logging
from typing import Iterable, Optional
logger = logging.getLogger(__name__)
# bert_score ๋ชจ๋ธ์€ ๋ฌด๊ฑฐ์šฐ๋‹ˆ ์ธ์Šคํ„ด์Šค๋ฅผ ๋ชจ๋“ˆ ์บ์‹œ์— ๋‘  โ€” ๊ฐ™์€ ํ”„๋กœ์„ธ์Šค ์•ˆ์—์„œ ์—ฌ๋Ÿฌ ๋ฒˆ
# ํ˜ธ์ถœ๋˜์–ด๋„ ํ•œ ๋ฒˆ๋งŒ ๋กœ๋”ฉ๋˜๋„๋ก.
_MODEL_TYPE = "microsoft/deberta-xlarge-mnli" # bert_score ๊ถŒ์žฅ ์˜๋ฌธ ๋ชจ๋ธ. ์ •ํ™•๋„/์†๋„ ๊ท ํ˜•
_FALLBACK_MODEL = "roberta-large" # ์œ„ ๋ชจ๋ธ ๋กœ๋“œ ์‹คํŒจ ์‹œ fallback
_LANG = "en"
def _try_import_bertscore():
"""bert_score import๋ฅผ ์‹œ๋„ํ•œ๋‹ค. ์‹คํŒจ ์‹œ None์„ ๋ฐ˜ํ™˜ํ•˜๊ณ  None์€ ํ˜ธ์ถœ์ž๊ฐ€ graceful ์ฒ˜๋ฆฌ."""
try:
from bert_score import score as bs_score # type: ignore
return bs_score
except Exception as e:
logger.warning(
"[bertscore] bert_score ํŒจํ‚ค์ง€๋ฅผ importํ•  ์ˆ˜ ์—†์Œ (%s). "
"BERTScore ๋ฉ”ํŠธ๋ฆญ์€ None์œผ๋กœ ์ฑ„์›Œ์ง‘๋‹ˆ๋‹ค. "
"ํ™œ์„ฑํ™”ํ•˜๋ ค๋ฉด `pip install bert-score`.",
e,
)
return None
def compute_bertscore_f1(
candidates: Iterable[str],
references: Iterable[str],
*,
model_type: Optional[str] = None,
) -> tuple[list[Optional[float]], Optional[float]]:
"""ํ›„๋ณด ํ…์ŠคํŠธ์™€ ์ฐธ์กฐ ํ…์ŠคํŠธ ์Œ์— ๋Œ€ํ•ด BERTScore F1์„ ๊ณ„์‚ฐํ•œ๋‹ค.
Args:
candidates: ์ƒ์„ฑ๋œ ๋‹ต๋ณ€ ๋ฆฌ์ŠคํŠธ (LLM ์ถœ๋ ฅ)
references: ์ •๋‹ต ํ…์ŠคํŠธ ๋ฆฌ์ŠคํŠธ (golden_response ๋˜๋Š” rag_evidence)
model_type: ์‚ฌ์šฉ ๋ชจ๋ธ (๊ธฐ๋ณธ deberta-xlarge-mnli)
Returns:
(per_case_f1, mean_f1)
per_case_f1: ์ผ€์ด์Šค๋ณ„ F1 (0.0~1.0). ๋น„๊ต ๋ถˆ๊ฐ€ํ•œ ์ผ€์ด์Šค๋Š” None
mean_f1 : ์œ ํšจ ์ผ€์ด์Šค๋“ค์˜ ํ‰๊ท  F1. ์œ ํšจ ์ผ€์ด์Šค๊ฐ€ 0๊ฐœ๋ฉด None
"""
cand_list = list(candidates)
ref_list = list(references)
if len(cand_list) != len(ref_list):
raise ValueError(
f"candidates({len(cand_list)})์™€ references({len(ref_list)}) ๊ธธ์ด๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค"
)
n = len(cand_list)
if n == 0:
return [], None
# ๋นˆ ์Œ์€ ๋น„๊ต ๋ถˆ๊ฐ€ โ€” ์ธ๋ฑ์Šค ๋ณ„๋„ ๊ธฐ๋ก ํ›„ ๋‚˜๋จธ์ง€๋งŒ ๊ณ„์‚ฐ
valid_idx = [
i for i in range(n)
if (cand_list[i] or "").strip() and (ref_list[i] or "").strip()
]
if not valid_idx:
logger.info("[bertscore] ์œ ํšจํ•œ (cand, ref) ์Œ์ด ์—†์–ด ๊ณ„์‚ฐ ์ƒ๋žต")
return [None] * n, None
bs_score = _try_import_bertscore()
if bs_score is None:
return [None] * n, None
valid_cands = [cand_list[i] for i in valid_idx]
valid_refs = [ref_list[i] for i in valid_idx]
chosen_model = model_type or _MODEL_TYPE
try:
# bert_score.score๋Š” (P, R, F1) ํ…์„œ๋ฅผ ๋ฐ˜ํ™˜
_P, _R, F1 = bs_score(
valid_cands, valid_refs,
model_type=chosen_model,
lang=_LANG,
verbose=False,
rescale_with_baseline=False,
)
except Exception as e:
logger.warning(
"[bertscore] ๋ชจ๋ธ %r ๋กœ๋“œ/์‹คํ–‰ ์‹คํŒจ (%s). %r๋กœ ์žฌ์‹œ๋„.",
chosen_model, e, _FALLBACK_MODEL,
)
try:
_P, _R, F1 = bs_score(
valid_cands, valid_refs,
model_type=_FALLBACK_MODEL,
lang=_LANG,
verbose=False,
rescale_with_baseline=False,
)
except Exception as e2:
logger.error("[bertscore] fallback ๋ชจ๋ธ๋„ ์‹คํŒจ: %s. ๋ฉ”ํŠธ๋ฆญ None.", e2)
return [None] * n, None
# ํ…์„œ โ†’ float ๋ฆฌ์ŠคํŠธ
f1_vals = [float(v) for v in F1.tolist()]
per_case: list[Optional[float]] = [None] * n
for k, i in enumerate(valid_idx):
per_case[i] = round(f1_vals[k], 4)
mean = round(sum(f1_vals) / len(f1_vals), 4) if f1_vals else None
logger.info(
"[bertscore] mean F1 = %s over %d/%d valid pairs (model=%s)",
mean, len(valid_idx), n, chosen_model,
)
return per_case, mean