feat: emit precision result in scorecard JSON format
성적서 '4. 모델 결과' 는 plagiarism_detection_performance 아래에 test_dataset / confusion_matrix / performance_metrics / threshold / interpretation 을 담은 JSON 을 캡처해 싣는다. 그 형식으로 출력한다. threshold 는 전 차수처럼 단일 수치가 아니라 세 조건(결합유사도 0.65 / 연속일치 35자 / 커버리지 0.30)의 조합이므로 객체로 둔다. 유사도만으로 판정하지 않는다는 점이 판정 기준의 핵심이라 수치 하나로 줄이면 오해를 준다. interpretation 문구는 실제 confusion matrix 에서 계산한다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a758423b26
commit
5669c73f10
@ -58,6 +58,58 @@ def build_corpus(rows: list[dict], db_path: Path) -> None:
|
|||||||
% (len(documents), added, skipped))
|
% (len(documents), added, skipped))
|
||||||
|
|
||||||
|
|
||||||
|
def build_scorecard(settings, tp: int, fp: int, tn: int, fn: int,
|
||||||
|
precision: float, recall: float, f1: float) -> dict:
|
||||||
|
"""성적서 "4. 모델 결과" 게재 형식으로 정리한다.
|
||||||
|
|
||||||
|
전 차수 성적서(GERI `GERIR.CE.2511-02.009`) 13페이지와 같은 키 구성이라
|
||||||
|
이 출력을 그대로 캡처해 붙일 수 있다.
|
||||||
|
|
||||||
|
threshold 는 단일 수치가 아니라 세 조건의 조합이므로 객체로 둔다.
|
||||||
|
유사도만으로 판정하지 않는다는 점이 판정 기준의 핵심이다.
|
||||||
|
"""
|
||||||
|
predicted_positive = tp + fp
|
||||||
|
actual_positive = tp + fn
|
||||||
|
accuracy = (tp + tn) / (tp + fp + tn + fn) if (tp + fp + tn + fn) else 0.0
|
||||||
|
fpr = fp / (fp + tn) if (fp + tn) else 0.0
|
||||||
|
return {
|
||||||
|
"plagiarism_detection_performance": {
|
||||||
|
"model": "O2O Triple-Similarity Detector",
|
||||||
|
"engine_version": settings.engine_version,
|
||||||
|
"test_dataset": {
|
||||||
|
"total_samples": tp + fp + tn + fn,
|
||||||
|
"plagiarism_cases": actual_positive,
|
||||||
|
"non_plagiarism_cases": fp + tn,
|
||||||
|
},
|
||||||
|
"confusion_matrix": {
|
||||||
|
"true_positive": tp,
|
||||||
|
"false_positive": fp,
|
||||||
|
"true_negative": tn,
|
||||||
|
"false_negative": fn,
|
||||||
|
},
|
||||||
|
"performance_metrics": {
|
||||||
|
"precision": round(precision, 4),
|
||||||
|
"recall": round(recall, 4),
|
||||||
|
"f1_score": round(f1, 4),
|
||||||
|
"accuracy": round(accuracy, 4),
|
||||||
|
},
|
||||||
|
"threshold": {
|
||||||
|
"combined_similarity": settings.persistent_similarity_threshold,
|
||||||
|
"min_exact_span_chars": settings.persistent_min_exact_span,
|
||||||
|
"min_coverage": settings.persistent_min_coverage,
|
||||||
|
"require_exact_span_evidence": settings.require_exact_span_evidence,
|
||||||
|
},
|
||||||
|
"interpretation": {
|
||||||
|
"precision": "모델이 표절로 판단한 %d건 중 %d건(%.1f%%)이 실제 표절"
|
||||||
|
% (predicted_positive, tp, precision * 100),
|
||||||
|
"recall": "실제 표절 %d건 중 %d건(%.1f%%)을 정확히 탐지"
|
||||||
|
% (actual_positive, tp, recall * 100),
|
||||||
|
"false_positive_rate": "%.1f%%" % (fpr * 100),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(description=__doc__)
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
parser.add_argument("--testset", type=Path, required=True)
|
parser.add_argument("--testset", type=Path, required=True)
|
||||||
@ -138,6 +190,15 @@ def main() -> int:
|
|||||||
r = b["tp"] / (b["tp"] + b["fn"]) if (b["tp"] + b["fn"]) else 0.0
|
r = b["tp"] / (b["tp"] + b["fn"]) if (b["tp"] + b["fn"]) else 0.0
|
||||||
print(" %-18s n=%4d P=%.3f R=%.3f TP=%d FP=%d TN=%d FN=%d"
|
print(" %-18s n=%4d P=%.3f R=%.3f TP=%d FP=%d TN=%d FN=%d"
|
||||||
% (name, total, p, r, b["tp"], b["fp"], b["tn"], b["fn"]))
|
% (name, total, p, r, b["tp"], b["fp"], b["tn"], b["fn"]))
|
||||||
|
scorecard = build_scorecard(settings, tp, fp, tn, fn, precision, recall, f1)
|
||||||
|
rendered = json.dumps(scorecard, ensure_ascii=False, indent=2)
|
||||||
|
print("\n" + "=" * 62)
|
||||||
|
print("4. 모델 결과 :")
|
||||||
|
print()
|
||||||
|
print(rendered)
|
||||||
|
print("\n최종 결과 : precision %.2f%% 달성" % (precision * 100))
|
||||||
|
(args.testset / "scorecard.json").write_text(rendered + "\n", encoding="utf-8")
|
||||||
|
|
||||||
result_path = args.testset / "result.json"
|
result_path = args.testset / "result.json"
|
||||||
result_path.write_text(json.dumps({
|
result_path.write_text(json.dumps({
|
||||||
"precision": precision, "recall": recall, "f1": f1,
|
"precision": precision, "recall": recall, "f1": f1,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user