RadFact 完整教學

Repository: https://github.com/microsoft/RadFact Stars: 98 | Fork: 12 | License: MIT Tags: radiology, evaluation, LLM, Microsoft 語言: Python | 最後更新: 2026-06-02

2. 核心架構

2.1 整體流程架構


flowchart TB
    subgraph INPUT["輸入層"]
        NAR["敘述性報告
(Narrative Text)"] GND["定位報告
(Grounded Phrases + Boxes)"] end subgraph PREPROCESS["前處理層"] R2P["Report-to-Phrases
報告拆句
(LLM: GPT-4)"] NEG["Negative Filtering
負面發現過濾
(僅 CT)"] end subgraph CORE["核心評估層"] direction TB NLI_FWD["正向 NLI 驗證
Prediction → Ground Truth
(計算 Precision)"] NLI_BWD["反向 NLI 驗證
Ground Truth → Prediction
(計算 Recall)"] BOX["Bounding Box
空間蘊含計算
(Pixel Precision ≥ 0.5)"] end subgraph ENGINE["平行處理引擎"] LLM_ENG["LLMEngine
多端點平行處理"] CACHE["中間結果快取
(Batch Outputs)"] SHARD["資料分片
(Speed Factor)"] end subgraph OUTPUT["輸出層"] METRICS["RadFactScore
6 項指標"] BOOT["Bootstrap CI
信賴區間"] JSON["outputs.json
完整結果"] end NAR --> R2P R2P --> NEG NEG --> NLI_FWD GND --> NLI_FWD NLI_FWD --> BOX NLI_BWD --> BOX R2P --> NLI_BWD GND --> NLI_BWD NLI_FWD -.->|"透過"| LLM_ENG NLI_BWD -.->|"透過"| LLM_ENG LLM_ENG --> CACHE LLM_ENG --> SHARD BOX --> METRICS METRICS --> BOOT BOOT --> JSON

2.2 NLI 蘊含驗證流程

RadFact 的核心是雙向 NLI (Bidirectional NLI)。對每一對 (prediction, ground_truth),執行兩次蘊含驗證:

  1. 正向 (Forward):以 ground truth 為前提 (Premise),逐句判斷 prediction 中的每句是否被蘊含 → 得到 Precision 指標
  2. 反向 (Backward):以 prediction 為前提,逐句判斷 ground truth 中的每句是否被蘊含 → 得到 Recall 指標

每次蘊含判斷的輸出包含:

  • entailment status:是否蘊含 (entailed / not entailed)
  • evidence:來自前提的支持證據句

2.3 模組結構

 1src/radfact/
 2├── cli/                           # 命令列介面
 3│   ├── run_radfact.py             # 主要執行入口
 4│   ├── run_radfact_test_examples.py  # 測試範例驗證
 5│   └── run_report_to_phrases.py   # 報告拆句工具
 6├── data_utils/
 7│   └── grounded_phrase_list.py    # 定位短語資料結構
 8├── llm_utils/
 9│   ├── endpoint.py                # LLM 端點管理
10│   ├── text_utils.py              # 文字處理工具
11│   ├── prompt_tasks.py            # Prompt 任務定義
12│   ├── engine/                    # 平行處理引擎
13│   │   ├── engine.py              # LLMEngine 核心
14│   │   ├── arguments.py           # 引擎參數
15│   │   ├── data_subset.py         # 資料分片
16│   │   ├── endpoint_utils.py      # 端點工具
17│   │   └── redis_cache.py         # Redis 快取
18│   ├── nli/                       # NLI 蘊含驗證
19│   │   ├── processor.py           # NLI 處理器
20│   │   ├── schema.py              # NLI 資料結構
21│   │   └── prompts/               # Few-shot 範例
22│   │       ├── cxr/               # 胸部 X 光
23│   │       └── ct/                # 電腦斷層
24│   ├── report_to_phrases/         # 報告拆句
25│   │   ├── processor.py           # 拆句處理器
26│   │   ├── schema.py              # 拆句資料結構
27│   │   └── prompts/               # 拆句 Few-shot
28│   │       ├── cxr/
29│   │       └── ct/
30│   ├── negative_filtering/        # 負面發現過濾
31│   │   ├── processor.py
32│   │   └── prompts/ct/
33│   └── processor/                 # 處理器抽象層
34│       ├── base_processor.py
35│       └── structured_processor.py
36├── metric/                        # 指標計算
37│   ├── radfact.py                 # RadFactMetric 主類別
38│   ├── schema.py                  # RadFactScore 資料結構
39│   ├── box_metrics.py             # 框體指標計算
40│   ├── bootstrapping.py           # Bootstrap 信賴區間
41│   └── print_utils.py             # 輸出格式化
42├── azure_utils/                   # Azure 認證工具
43│   ├── auth.py
44│   └── bearer_token_provider.py
45└── paths.py                       # 路徑常數

2.4 設定管理

RadFact 使用 Hydra 進行設定管理,設定檔位於 configs/ 目錄:

設定檔用途MAIRA-2 使用的 LLM
radfact.yamlNLI 蘊含驗證端點Llama-3-70B-Instruct
report_to_phrases.yaml報告拆句端點GPT-4
negative_filtering.yaml負面發現過濾端點(自訂)
endpoints/*.yaml端點類型模板AzureChatOpenAI / ChatOpenAI

3. 安裝與設定

3.1 基本安裝

 1# 方法一:pip 直接安裝
 2git clone https://github.com/microsoft/RadFact.git
 3cd RadFact
 4pip install .
 5
 6# 方法二:使用 Makefile + conda(推薦用於開發)
 7make miniconda
 8make mamba
 9make env
10conda activate radfact

建議:若使用 uv 管理 Python 環境:

1git clone https://github.com/microsoft/RadFact.git
2cd RadFact
3uv venv --python 3.10
4source .venv/bin/activate
5uv pip install .

3.2 LLM 端點設定

RadFact 需要存取 LLM API。支援兩種端點類型:

方式 A:ChatOpenAI(適用 Llama-3 等自建模型)

建立 configs/endpoints/my_endpoint.yaml

1MY_LLAMA3_ENDPOINT:
2  type: "CHAT_OPENAI"
3  url: "https://your-model-endpoint.example.com/v1"
4  deployment_name: "llama3-70b"
5  api_key_env_var_name: "RADFACT_API_KEY"
6  speed_factor: 1.0
7  num_parallel_processes: 10

方式 B:AzureChatOpenAI(適用 Azure 上的 GPT 模型)

1MY_GPT4_ENDPOINT:
2  type: "AZURE_CHAT_OPENAI"
3  url: "https://your-azure-endpoint.openai.azure.com/"
4  deployment_name: "gpt-4"
5  api_key_env_var_name: "AZURE_OPENAI_KEY"
6  speed_factor: 1.0
7  num_parallel_processes: 5

3.3 認證方式

RadFact 支援三種認證方式(優先順序由高到低):

  1. 環境變數 API Keyexport RADFACT_API_KEY="your-key-here"
  2. Azure Key Vault:透過 config.json(含 subscription_id / resource_group / workspace_name)+ 端點設定中的 keyvault_secret_name
  3. Azure Token Provider:自動產生 Azure AD Token(僅限 AzureChatOpenAI 類型)

3.4 驗證安裝

1# 設定 API Key
2export API_KEY="your-api-key"
3
4# 執行測試範例,確認 NLI 蘊含驗證正常
5python src/radfact/cli/run_radfact_test_examples.py

此測試使用預設的 few-shot 範例驗證蘊含判斷行為。預期結果基於 Llama-3-70B-Instruct;如果使用不同的 LLM,結果可能有差異。


4. 使用方式與程式碼範例

4.1 範例一:評估非定位敘述報告 (Narrative Text)

這是最基礎的使用情境——比較 AI 生成的放射報告與標準報告,不涉及空間定位。

準備輸入 CSV(格式與 examples/findings_generation_examples.csv 相同):

1example_id,prediction,target
20,"The lungs are well expanded. Large nodular density in the right midlung field measuring 3.5 x 3.5 cm.","Ovoid density superimposed lung fields in the mid and lower lung fields measuring half to 3 cm."
31,"Patient is status post median sternotomy and CABG. Heart size and pulmonary vasculature are normal.","Patient is status post median sternotomy and CABG. The lungs are well aerated without focal consolidation."

執行評估

 1# 基本執行(含 Bootstrap 信賴區間,預設 500 次取樣)
 2run_radfact \
 3  --input_path examples/findings_generation_examples.csv \
 4  --is_narrative_text \
 5  --report_type cxr
 6
 7# 快速測試(關閉 Bootstrap)
 8run_radfact \
 9  --input_path examples/findings_generation_examples.csv \
10  --is_narrative_text \
11  --bootstrap_samples 0
12
13# CT 報告 + 過濾負面發現
14run_radfact \
15  --input_path my_ct_reports.csv \
16  --is_narrative_text \
17  --report_type ct \
18  --filter_negatives

輸出結構

1outputs/radfact/run_20240814_075225/
2├── batch_outputs/          # 每批次中間結果
3│   ├── outputs_0_100.json
4│   └── outputs_100_200.json
5├── progress/               # 各端點處理進度
6├── skipped/                # 失敗跳過的樣本
7├── outputs.json            # 最終完整結果
8├── progress.csv
9└── skipped.csv

4.2 範例二:評估定位報告 (Grounded Report)

若 AI 模型的輸出不僅包含文字描述,還帶有 Bounding Box 空間定位(如 MAIRA-2),可以使用定位模式進行更全面的評估。

準備輸入 JSON(格式與 examples/grounded_reporting_examples.json 相同):

 1[
 2  {
 3    "prediction": [
 4      {
 5        "text": "Cardiac silhouette remains normal in size.",
 6        "boxes": null
 7      },
 8      {
 9        "text": "Moderate-sized area of airspace opacity in the left base has improved.",
10        "boxes": [
11          {"x_min": 0.535, "y_min": 0.355, "x_max": 0.865, "y_max": 0.725}
12        ]
13      }
14    ],
15    "target": [
16      {
17        "text": "Cardiac silhouette is normal in size.",
18        "boxes": null
19      },
20      {
21        "text": "Left basilar airspace opacity has improved since prior exam.",
22        "boxes": [
23          {"x_min": 0.520, "y_min": 0.340, "x_max": 0.880, "y_max": 0.740}
24        ]
25      }
26    ]
27  }
28]

執行評估

1# 定位報告不需要 --is_narrative_text 旗標
2run_radfact \
3  --input_path examples/grounded_reporting_examples.json \
4  --report_type cxr \
5  --bootstrap_samples 500 \
6  --output_dir my_results/

此模式會計算完整的六項指標,包含 Grounding Precision/Recall 和 Spatial Precision/Recall。空間蘊含的判定標準為:候選框的像素精確度 (Pixel Precision) 相對於證據框聯集需 ≥ 0.5

4.3 範例三:Python API 程式化使用

若要將 RadFact 整合到自己的評估管線 (Evaluation Pipeline) 中,可直接使用 Python API:

 1from radfact.metric.radfact import RadFactMetric
 2
 3# 初始化 RadFactMetric
 4metric = RadFactMetric(
 5    is_narrative_text=True,       # 輸入為敘述文字
 6    report_type="cxr",            # 報告類型: cxr 或 ct
 7    filter_negatives=False,       # 是否過濾負面發現
 8)
 9
10# 準備資料:字典格式 {study_id: report_text}
11generations = {
12    "study_001": "The lungs are clear. No pleural effusion. Heart size is normal.",
13    "study_002": "Right lower lobe consolidation. Small right pleural effusion.",
14}
15
16ground_truths = {
17    "study_001": "Clear lungs bilaterally. Normal cardiac silhouette. No effusion.",
18    "study_002": "Right basilar consolidation with associated pleural effusion.",
19}
20
21# 計算 RadFact 指標
22results = metric.compute(
23    generations=generations,
24    ground_truths=ground_truths,
25)
26
27# 取得彙總分數
28aggregate_score = results["aggregate_scores"]
29print(f"Logical Precision: {aggregate_score.logical_precision:.3f}")
30print(f"Logical Recall:    {aggregate_score.logical_recall:.3f}")
31print(f"Logical F1:        {aggregate_score.logical_f1:.3f}")
32
33# 取得每個樣本的詳細結果
34for sample_result in results["per_sample_results"]:
35    print(f"\nStudy: {sample_result.study_id}")
36    if sample_result.scores:
37        print(f"  Precision: {sample_result.scores.logical_precision:.3f}")
38        print(f"  Recall:    {sample_result.scores.logical_recall:.3f}")
39    # 檢視候選句的蘊含細節
40    for phrase in sample_result.candidate_phrases:
41        print(f"  [{phrase.status}] {phrase.text}")
42        for ev in phrase.evidence:
43            print(f"    evidence: {ev.text}")

RadFactScore 資料結構 提供的屬性:

 1from radfact.metric.schema import RadFactScore
 2
 3# RadFactScore 是 frozen dataclass
 4score: RadFactScore
 5score.logical_precision   # float: 邏輯精確度
 6score.logical_recall      # float: 邏輯召回率
 7score.logical_f1          # property: 邏輯 F1
 8score.spatial_precision   # float: 空間精確度
 9score.spatial_recall      # float: 空間召回率
10score.spatial_f1          # property: 空間 F1
11score.grounding_precision # float: 定位精確度
12score.grounding_recall    # float: 定位召回率
13score.grounding_f1        # property: 定位 F1
14score.num_candidate_phrases       # int: 候選句數量
15score.num_reference_phrases       # int: 參考句數量
16score.num_candidate_phrases_with_boxes  # int: 帶框候選句數量
17score.num_reference_phrases_with_boxes  # int: 帶框參考句數量

5. 在醫療 LLM / 文本 SDG 生態系中的定位

5.1 Domain 6 生態系定位

在 Bio-SDG Domain 6「醫療 LLM 與文本 SDG」的生態系中,RadFact 佔據 評估層 (Evaluation Layer) 的關鍵位置:

子領域角色代表工具
A. 醫療 LLM 訓練管線訓練模型MedicalGPT, BioGPT, BioMedLM
B. LLM 驅動的 SDG 框架產生合成資料distilabel, sdg_hub
C. 放射報告生成生成 + 評估R2GenGPT (生成), RadFact (評估), LLM-RG4, FORTE
D. 醫療對話生成產生對話資料GEML-MDG
E. 資源與教學參考工具NeMo DataDesigner

RadFact 在子領域 C 中扮演的是 品質守門員 (Quality Gatekeeper) 角色。當 R2GenGPT、LLM-RG4、FORTE 等工具產生合成放射報告時,RadFact 提供了超越傳統 NLP 指標的臨床語義評估能力。

5.2 為什麼需要 RadFact

傳統指標的侷限性:

指標問題
BLEU只看 n-gram 重疊,同義不同詞即低分
ROUGE同上,且對長報告偏頗
BERTScore捕捉部分語義,但無法處理放射學特有的邏輯關係
CheXbert僅限 14 個預定義標籤,無法評估自由文本細節

RadFact 的優勢:

  • 臨床語義等價判斷:「心臟大小正常」與「心臟輪廓未增大」被正確視為蘊含
  • 雙向評估:同時捕捉「說錯了」(precision) 與「漏掉了」(recall)
  • 定位評估:不只評文字,還能評估空間標註的正確性
  • 可解釋性:每句都有蘊含狀態與證據鏈,便於錯誤分析

5.3 合成資料品質評估的藍海

RadFact 目前聚焦於 CXR 與 CT 報告,但其核心方法——LLM-based 雙向蘊含驗證——可以延伸到更廣泛的醫療文本 SDG 品質評估:

  • 合成臨床筆記 (Synthetic Clinical Notes):判斷合成病歷與真實範本的臨床資訊一致性
  • 合成 EHR 敘述 (Synthetic EHR Narratives):評估合成電子病歷的完整性與正確性
  • 法規文件 (Regulatory Documents):在 pre-IND 流程中,評估 AI 輔助生成的 CMC (Chemistry, Manufacturing, and Controls) 或非臨床摘要文件的事實一致性
  • 病理報告 (Pathology Reports):將 few-shot 範例替換為病理學領域的蘊含範例

6. 與其他工具的整合

6.1 與放射報告生成工具的整合

R2GenGPT + RadFact 評估迴圈

 1# 1. 用 R2GenGPT 生成報告
 2python generate_reports.py \
 3  --model r2gengpt \
 4  --images test_images/ \
 5  --output generated_reports.csv
 6
 7# 2. 用 RadFact 評估生成品質
 8run_radfact \
 9  --input_path generated_reports.csv \
10  --is_narrative_text \
11  --report_type cxr \
12  --bootstrap_samples 500

LLM-RG4 / FORTE + RadFact:相同模式——將生成工具的輸出轉換為 RadFact 的 CSV 或 JSON 輸入格式即可。

6.2 與 AIKT Pipeline 的整合

在 AI-Knowledge Template 管線中,RadFact 可以作為品質檢查節點

1paper-search → 收集放射報告生成論文
23paper-qa-lite → 萃取方法論
45RadFact → 評估複製實驗的報告品質
67quarkdown → 產出品質報告 HTML

6.3 與 distilabel / SDG 框架的整合

當使用 distilabel 等 SDG 框架產生合成放射報告時,RadFact 可以作為過濾器 (Filter)品質指標 (Quality Signal)

 1# 概念範例:將 RadFact 整合到 SDG 品質過濾
 2from radfact.metric.radfact import RadFactMetric
 3
 4metric = RadFactMetric(is_narrative_text=True, report_type="cxr")
 5
 6def filter_high_quality_synthetic(
 7    synthetic_reports: dict,
 8    reference_reports: dict,
 9    threshold: float = 0.7
10) -> dict:
11    """只保留 RadFact 邏輯精確度超過門檻的合成報告"""
12    results = metric.compute(
13        generations=synthetic_reports,
14        ground_truths=reference_reports,
15    )
16    filtered = {}
17    for sample in results["per_sample_results"]:
18        if sample.scores and sample.scores.logical_precision >= threshold:
19            filtered[sample.study_id] = synthetic_reports[sample.study_id]
20    return filtered

6.4 前置處理:獨立使用報告拆句工具

RadFact 的報告拆句功能可以獨立使用,適合將長篇敘述報告拆解為結構化句子:

1# 將 CXR 報告拆成個別句子
2run_report_to_phrases dataset.csv_path=my_reports.csv

這在訓練資料前處理、句子級標註、或 NER (Named Entity Recognition; 命名實體辨識) 管線中都很有用。


7. 優缺點分析

7.1 優點

面向優點
臨床語義理解透過 LLM NLI 捕捉臨床等價表述,遠超文字匹配指標
可解釋性每句都有蘊含狀態 + 證據句,便於錯誤分析與模型除錯
Grounding 支援業界少見同時評估文字與空間定位的放射報告指標
工程品質Microsoft 標準:Hydra 設定管理、平行處理、中間快取、Bootstrap CI
開放授權MIT License,可自由商用、修改、整合
多報告類型支援 CXR 與 CT,且架構允許擴展到其他影像模態
容錯機制LLM 呼叫失敗時預設標記為 not-entailed,並記錄到 skipped 目錄

7.2 缺點與限制

面向限制
LLM 依賴每次評估都需呼叫 LLM API,有成本且不可完全離線運行
一致性問題不同 LLM 後端會產生不同結果,cross-study 比較需統一模型
報告類型有限目前僅支援 CXR 與 CT,MRI / PET / Ultrasound 等需自行擴展 few-shot
負面過濾限 CTNegative Filtering 功能目前僅支援 CT 報告
Few-shot 品質瓶頸NLI few-shot 範例來自私有資料集 (USMix),無法檢視或客製化原始標註邏輯
延遲與成本大量樣本評估需要長時間 LLM API 呼叫,適合離線批次評估而非即時評分
無中文支援目前 prompts 與 few-shot 均為英文,直接用於中文放射報告需自行改造

7.3 適用場景建議

場景建議
放射報告生成模型開發強烈推薦作為主要評估指標
合成放射報告品質過濾推薦作為品質守門員
快速迭代的模型訓練考慮搭配 CheXbert 做粗篩,RadFact 做精評
即時線上評估不建議(API 延遲與成本)
非放射領域報告需自行替換 few-shot 範例與 system prompt
pre-IND 法規文件品質概念可行但需大量客製化

7.4 與同類工具比較

指標工具是否需 LLM語義理解Grounding可解釋性成本
RadFact支援
CheXbert不支援
BERTScore不支援
BLEU/ROUGE不支援
GREEN (Liu et al.)不支援

一句話總結:RadFact 是目前最全面的 LLM-based 放射報告評估框架,透過雙向 NLI 蘊含驗證提供六項涵蓋邏輯 / 空間 / 定位的精確度與召回率指標,適合作為放射報告生成與合成資料品質控制的標準評估工具。