iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0

Day 6 我們優化了系統性能。但優化得好不好,怎麼衡量?「感覺變快了」不算。今天我們建設完整的評測框架:20 個標準 SRS 樣本、自動計算 F1 分數、對比不同版本性能。

今日目標

完成本篇後,你將擁有:

  1. 評測數據集 — 20+ 個標準 SRS 樣本,手工標註真實矛盾
  2. 自動評分系統 — 計算 Precision、Recall、F1 分數
  3. 性能基準 — 建立 Day 7 的基線(目標 F1 ≥ 0.75)
  4. 評測報告 — 可視化結果對比

具體目標: 實現 src/evaluator.py,能輸出「Precision: 0.85, Recall: 0.78, F1: 0.81」這樣的量化結果。

現在的問題

前六天我們造了一個完整的系統,能讀、能分、能檢測、能並行、還有快取。看起來不錯。

但你怎麼知道它是否真的在檢測衝突?

執行一次,說「檢測到 8 個衝突」。然後呢?是真的有 8 個,還是漏掉了 20 個,誤報了 5 個?無法驗證。

「這次改進了代碼,性能提升了」——提升了多少?比上次好還是更差?無法對比。

你需要客觀的指標。

量化方法

簡單的思路:

  1. 準備「標準答案」——某個規格書實際有哪些衝突(人工標註或領域專家確認)
  2. 運行檢測系統
  3. 對比結果:檢測到的衝突 vs 實際的衝突
  4. 計算指標

這就是「評測框架」。

三個關鍵指標

Precision(精確度)

簡單說:「檢測到的衝突中,有多少比例是真的?」

公式:Precision = 真檢測數 / (真檢測數 + 誤報數)

例子:檢測到 10 個衝突,其中 8 個實際存在,2 個是誤報。Precision = 8/10 = 80%。

含義:精確度高 = 誤報少。你信任系統說「有矛盾」。

Recall(召回率)

簡單說:「實際有的衝突中,檢測到了多少?」

公式:Recall = 真檢測數 / (真檢測數 + 漏報數)

例子:實際 10 個衝突,檢測到 8 個。Recall = 8/10 = 80%。

含義:召回率高 = 漏報少。系統能找全。

F1 分數

Precision 和 Recall 的調和平均。當你同時在乎誤報和漏報時用。

公式:F1 = 2 × (Precision × Recall) / (Precision + Recall)

使用場景:

  • 寧可漏報也不誤報 → 追求高 Precision(金融交易系統)
  • 寧可誤報也不漏報 → 追求高 Recall(安全審計)
  • 一般 → 追求高 F1(平衡)

實現

src/evaluation.py:

from dataclasses import dataclass
from typing import List, Set, Tuple
from enum import Enum

class ConflictType(str, Enum):
    LOGIC = "邏輯矛盾"
    SECURITY = "安全性衝突"
    PERFORMANCE = "性能衝突"

@dataclass
class GroundTruthConflict:
    """預期的衝突(標準答案)"""
    req_id_1: str
    req_id_2: str
    conflict_type: ConflictType
    description: str

@dataclass
class DetectedConflict:
    """檢測到的衝突"""
    req_id_1: str
    req_id_2: str
    conflict_type: str
    severity: str

class EvaluationMetrics:
    """評測指標計算"""
    
    def __init__(self):
        self.tp = 0  # 真正例(應檢測且檢測到)
        self.fp = 0  # 假正例(不應檢測但檢測到)
        self.fn = 0  # 假負例(應檢測但未檢測到)
    
    @property
    def precision(self) -> float:
        if self.tp + self.fp == 0:
            return 0.0
        return self.tp / (self.tp + self.fp)
    
    @property
    def recall(self) -> float:
        if self.tp + self.fn == 0:
            return 0.0
        return self.tp / (self.tp + self.fn)
    
    @property
    def f1_score(self) -> float:
        p, r = self.precision, self.recall
        if p + r == 0:
            return 0.0
        return 2 * (p * r) / (p + r)

class Evaluator:
    """評估器:比較預期與檢測結果"""
    
    def __init__(self, ground_truth: List[GroundTruthConflict]):
        # 標準化格式:(小ID, 大ID) 方便比較
        self.truth_set: Set[Tuple[str, str]] = {
            self._normalize(c.req_id_1, c.req_id_2)
            for c in ground_truth
        }
    
    @staticmethod
    def _normalize(id1: str, id2: str) -> Tuple[str, str]:
        return (id1, id2) if id1 < id2 else (id2, id1)
    
    def evaluate(self, detected: List[DetectedConflict]) -> EvaluationMetrics:
        metrics = EvaluationMetrics()
        
        detected_set = {
            self._normalize(c.req_id_1, c.req_id_2)
            for c in detected
        }
        
        # TP:檢測到且實際存在
        for pair in detected_set:
            if pair in self.truth_set:
                metrics.tp += 1
            else:
                metrics.fp += 1
        
        # FN:應該檢測但沒檢測到
        for pair in self.truth_set:
            if pair not in detected_set:
                metrics.fn += 1
        
        return metrics

測試用例

準備 3 個域的完整測試集:

電商平台

  • 多用戶支持 vs 單用戶設計
  • 加密支付 vs 明文顯示
  • 性能衝突(頁面加載 vs 吞吐量)
  • 預期衝突:4 個

醫療系統

  • 多醫生編輯 vs 單醫生系統
  • 加密病歷 vs 明文存儲
  • 性能差異(100 vs 1000 並行用戶)
  • 公開訪問 vs HIPAA 認證
  • 預期衝突:6 個

社交媒體

  • 帖子大小限制(100MB vs 1GB)
  • 數據保留策略(30 天 vs 永久)
  • 加密方式衝突
  • 預期衝突:6 個

評測流程

# 1. 定義標準答案
ground_truth = [
    GroundTruthConflict("REQ-2.1.1", "REQ-8.4.2", ConflictType.LOGIC, "..."),
    # ... 更多
]

# 2. 運行檢測器
detected = your_detector.detect_conflicts(srs_content)

# 3. 評測
evaluator = Evaluator(ground_truth)
metrics = evaluator.evaluate(detected)

# 4. 查看結果
print(f"Precision: {metrics.precision:.3f}")
print(f"Recall: {metrics.recall:.3f}")
print(f"F1: {metrics.f1_score:.3f}")

實際例子

假設測試電商平台,預期 4 個衝突:

情況 1:完美檢測

  • 檢測到:4 個,都正確
  • TP=4, FP=0, FN=0
  • Precision=1.0, Recall=1.0, F1=1.0 ✅

情況 2:漏報

  • 檢測到:3 個都正確,實際有 4 個
  • TP=3, FP=0, FN=1
  • Precision=1.0, Recall=0.75, F1=0.857
  • 問題:漏掉了一個

情況 3:誤報

  • 檢測到:4 個,其中 3 個正確,1 個誤報
  • TP=3, FP=1, FN=1
  • Precision=0.75, Recall=0.75, F1=0.75
  • 問題:既誤報也漏報

情況 4:過檢測

  • 檢測到:6 個,其中 4 個正確,2 個誤報
  • TP=4, FP=2, FN=0
  • Precision=0.667, Recall=1.0, F1=0.8
  • 問題:誤報多,但全部漏報為 0

哪個最好?看場景。如果你不能容忍漏報(安全審計),情況 4 是好的(Recall 完美)。如果你不能容忍誤報(成本高),情況 2 是好的(Precision 完美)。

驗證結果

執行驗證命令

在 /Users/imac-4096/Desktop/srs-review-agent 目錄中執行:

# 執行評測
python3 -m pytest tests/test_day7_evaluation.py -v

# 或運行評測指令碼
python3 tests/test_day7_evaluation.py

預期輸出

🧪 Day 7 評測系統單元測試
======================================================================

測試 1:Precision 計算
✅ 正確:TP=8, FP=2, Precision=0.8

測試 2:Recall 計算
✅ 正確:TP=8, FN=2, Recall=0.8

測試 3:F1 分數計算
✅ 正確:Precision=0.8, Recall=0.8, F1=0.8

======================================================================
評測結果摘要
======================================================================

20 個測試用例評測:
• 平均 Precision:0.78
• 平均 Recall:0.81
• 平均 F1 分數:0.789

基線性能:✅ F1 = 0.789
======================================================================

驗證清單

✅ 評測框架正常初始化
✅ Precision/Recall/F1 計算正確
✅ 20 個測試用例通過
✅ 基線 F1 分數 ≥ 0.75
✅ 評測報告可輸出

執行評測

python3 tests/test_day7_evaluation.py

會輸出每個測試用例的 Precision、Recall、F1,然後是平均分。

用這個作為基準線。每次改進代碼後重新評測,看指標有沒有提升。

為什麼這很重要

可量化:不再「感覺變好了」,而是「F1 從 0.75 升到 0.82」。

可對比:不同版本之間有客觀的分數。

可追蹤:知道系統在哪裡失敗。Recall 低?漏報多,需要添加新檢測規則。Precision 低?誤報多,需要精化規則。

可指導改進:分析失敗案例,了解系統缺陷,有針對性地改進。

明天

Day 8 我們會根據評測結果,分析「為什麼漏報了這些」「為什麼誤報了那些」,然後優化檢測規則。

現在你有了衡量標尺。接下來就是打磨系統讓指標一步步提升。


進度

第 1 週完成 ✅

  • Day 1-6:構建系統
  • Day 7:建立評測框架 ← 今天

第 2 週:優化與驗證

  • Day 8-10:根據評測結果優化
  • Day 11-14:性能調優和最終測試

🎉 現在我們可以用數據說話了


上一篇
Day 6:系統撞牆了,然後我們修了它
下一篇
Day 8:我想改進它,結果搞砸了
系列文
解決需求規格書矛盾:用 Claude Code × MCP 實作自律型文檔審查 Agent 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言