檢索增強生成(Retrieval-Augmented Generation, RAG)是一種先從外部知識庫找出候選證據,再交給大型語言模型(Large Language Model, LLM)整理回答的方法。前兩天,我們先比較三種候選組織方式,再建立共同的評估地圖。現在看似只差最後一步:讓三套系統各跑一次,分數最高的就是答案。
但如果系統 A 使用原始數值、平面文件與稠密檢索,系統 C 同時改用雙重查詢、階層文件、安全候選聯集、混合檢索與重排序,兩者即使相差 5 個百分點,也不能說「階層文件帶來 5 個百分點」。這個差距混合了五項改動,我們不知道哪一項帶來幫助、哪一項造成傷害,甚至不知道兩項改動是否互相抵銷。
下圖用機械模組表達同一個問題。背景三套機器一次換了許多零件,只能比較整組;前景兩套機器只有橘色模組不同,才有機會把輸出差異歸到那個模組。右側上鎖的資料盒代表兩組必須使用相同且事先固定的測試條件。

上圖用概念方式表達控制變因:共同資料與其他模組保持不變,只替換一個待比較的元件,結果差異才有清楚的解釋範圍。
讀完 Day 08 後,你應該能夠:
今天會同時談到實驗設計、檢索流程與錯誤分析,先建立一張詞彙索引;正文仍會逐一說明每個名詞的用途。
| 中文名稱 | 英文全名/縮寫 | 在本篇的用途 |
|---|---|---|
| 實驗因子 | Experimental Factor | 實驗者刻意改變、用來觀察輸出變化的條件 |
| 因子水準 | Factor Level | 同一個實驗因子可選擇的具體設定 |
| 控制條件 | Controlled Condition | 一組比較中刻意維持相同的資料、模型或參數 |
| 變因混淆 | Confounding | 多個因素一起改變,導致效果無法分開歸因 |
| 交互作用 | Interaction | 一項因素的效果會隨另一項因素的水準而改變 |
| 基準方法 | Baseline | 讓新方法有可解釋參照點的簡單或既有方法 |
| 消融研究 | Ablation Study | 從完整方法每次移除一個元件,觀察表現如何變化 |
| 前 k 筆候選 | Top-k Candidates | 檢索或排序後保留的前 k 個候選;k 是保留數量 |
| 候選覆蓋率 | Candidate Coverage | 正確級數或正確證據是否出現在前 k 筆候選中 |
| 跨側錯誤 | Straddling Error | 正確級數不在候選中,而且候選同時落在它的兩側 |
實驗因子(Experimental Factor)是實驗者刻意改變的輸入條件,例如查詢表示方式或文件結構。因子水準(Factor Level)則是這個因子的具體選項,例如文件結構可以有「平面」與「階層」兩個水準。
控制條件(Controlled Condition)是在同一組比較中刻意保持不變的部分。假設今天只想知道平面文件與階層文件的差異,測試案例、查詢文字、候選數量、檢索器、提示詞與生成模型就都應保持相同。
美國國家標準暨技術研究院(National Institute of Standards and Technology, NIST)的實驗設計術語表,把因子描述為可能影響結果、且能在實驗中改變的變數;變因混淆則表示兩個或更多效果無法分開估計。NIST:實驗設計術語表
換成白話就是:輸出變了,不代表你已經知道是哪一個改動造成的。
變因混淆(Confounding)描述的是實驗設計問題:兩個以上的因素一起改變,效果黏在一起,無法單獨解釋。
混淆矩陣(Confusion Matrix)則是 Day 07 使用的評估表格,用來統計參考級數與模型預測級數的交叉筆數。兩個中文名稱都有「混淆」,用途完全不同:前者問實驗能不能歸因,後者整理分類結果。
控制比較(Controlled Comparison)是本系列用來描述「共同條件固定,只改一項主要因素」的工程流程。它適合回答直接問題,例如:在同一個平面知識庫上,原始查詢、語意查詢與雙重查詢哪一個能找回更多正確候選?
但是,一次只改一項因素不能自動涵蓋交互作用(Interaction)。交互作用表示一項因素的效果會隨另一項設定而改變。舉例來說,語意查詢可能只在階層文件上有幫助,在平面文件上卻沒有相同效果。
因此,本篇先用控制比較建立「直接效果」的可解釋起點;若後續結果顯示效果依賴其他設定,還要另外設計組合實驗。不能把一組控制比較寫成適用所有架構的普遍因果結論。
先建立三套假想設計。這些組合只用來檢查實驗邏輯,不是已完成的系統,也沒有任何效能分數。
| 實驗因子 | 系統 A:平面基準 | 系統 B:階層版本 | 系統 C:完整方法 |
|---|---|---|---|
| 查詢表示 | 原始數值 | 語意文字 | 雙重表示 |
| 文件結構 | 平面區塊 | 父子階層 | 父子階層 |
| 門控方式 | 無門控 | 生命徵象硬門控 | 安全候選聯集 |
| 檢索方式 | 稠密檢索 | 稠密檢索 | 混合檢索 |
| 重排序 | 關閉 | 關閉 | 啟用 |
稠密檢索(Dense Retrieval)會把查詢與文件轉成向量,再用向量相似度尋找語意接近的內容。混合檢索(Hybrid Retrieval)則同時結合向量訊號與關鍵字等稀疏訊號。重排序(Reranking)不重新建立候選,而是取得初步候選後,再用另一個評分器調整先後順序。
下圖把表格中的差異用顏色標出。請從系統 A 直線看到系統 C:五列全部改變,所以 A 與 C 的分數差只能歸給「整套組合」,不能單獨歸給階層、門控或重排序。

上圖也顯示系統 B 並沒有解決全部問題。B 與 C 之間仍同時更換查詢表示、門控方式、檢索方式與重排序。就算 C 勝過 B,我們仍不知道是安全候選聯集增加了正確候選,還是混合檢索找到更多關鍵字相符的規則。
設計實驗的價值就在這裡。一般觀察只能告訴我們某個組合與某個結果一起出現;有控制的實驗則透過主動改變條件,讓歸因問題更清楚。NIST 的工程統計手冊也將這兩種資訊來源分開討論。NIST:從觀察到實驗
查詢表示(Query Representation)是送進檢索器的查詢形式。同一筆教學輸入可以有三種表示:
| 表示方式 | 示意內容 | 保留的訊息 |
|---|---|---|
| 原始數值 | 主訴為呼吸困難;呼吸速率每分鐘 28 次;血氧飽和度 91% | 原始文字、數字與單位 |
| 語意文字 | 依指定規則版本,呼吸速率落在較高區間,血氧飽和度落在較低區間 | 規則轉換後的區間語意 |
| 雙重表示 | 同時保留前兩列 | 原始值與規則語意 |
血氧飽和度(Peripheral Oxygen Saturation, SpO2)是以周邊感測方式估計血液含氧比例的生命徵象。這裡的 91% 只沿用跨篇教學案例,不足以完成檢傷判定;案例仍缺少意識、呼吸窘迫程度、病史、其他生命徵象與制度規則所需條件。
語意文字也不能由作者憑感覺改寫。它必須由固定版本的規則轉換器產生,並保留原始值、單位、命中的規則識別碼與版本。否則「語意表示變好」可能只是改寫者偷偷加入了額外判斷。
如果平面系統使用原始數值,階層系統卻使用語意文字,那麼檢索差異至少有兩種可能來源:
要測查詢表示,就固定平面知識庫、稠密檢索、無門控與無重排序,只更換原始、語意、雙重三個水準。要測文件結構,再固定已選定的查詢表示,只更換平面與階層。
這個順序讓第一步的輸出成為第二步的輸入:查詢表示先在共同環境中選定;接著文件結構比較沿用同一個查詢版本,不會把兩項改動混回去。
文件結構回答「規則如何存放」。平面知識庫把規則片段放在同一層;階層知識庫保留父層主題與子層規則的關係。
門控方式回答「哪些內容有機會進入候選」。硬門控(Hard Gate)會直接排除不符合條件的候選;軟門控(Soft Gate)只調整候選權重;安全候選聯集門控(Safety-Union Gate)則合併主訴、生命徵象與高風險保護候選,再去重與排序。
兩者解決不同問題。若比較時同時把平面文件改成階層文件,又加入生命徵象硬門控,正確規則消失可能來自父層路由錯誤,也可能來自硬門控排除。執行紀錄再完整,也只能看見候選在哪裡消失,不能從這一組分數估計兩個元件各自的效果。
合理順序是:
步驟 1 產生固定的文件結構,步驟 2 才能把差異集中在候選處理;步驟 3 則提供驗證資料,避免只看最終分數猜原因。
初步檢索器負責產生候選,重排序器負責重排既有候選。若一次從稠密檢索直接跳到「混合檢索加重排序」,即使正確證據升到第一名,也無法知道它原本就被混合檢索找回,還是原本已在候選中、只是被重排序器往前移。
因此原本的一組比較要拆成兩段:
D1 的預期輸出是兩種檢索器各自的前 k 筆候選與候選覆蓋率;D2 的輸入則是已固定的混合檢索候選。若 D2 開啟後候選集合內容也跟著改變,表示實作沒有只做重排,這組比較便不合格。
基準方法(Baseline)是新方法的參照點。基準不必是最強方法,但要能回答一個明確問題。DummyClassifier 是 scikit-learn 提供的簡單分類基準;它可以忽略輸入,按照多數類別或指定策略產生預測,用來檢查複雜方法是否只利用了類別比例。scikit-learn:DummyClassifier
本系列預計至少保留六個基準。表中的內容仍是實驗計畫,不是已完成結果。
| 代號 | 基準方法 | 它回答的問題 |
|---|---|---|
| B0 | 永遠預測多數類別 | 完全不看輸入可以得到多高的最低參照? |
| B1 | 部分規則方法;資訊不足時拒答 | 已能形式化的規則本身可以處理多少案例? |
| B2 | 結構化機器學習(Machine Learning, ML) | 不使用生成文字時,表格欄位能提供多少訊號? |
| B3 | 文字嵌入加結構化特徵 | 非生成式的文字與表格組合能做到什麼? |
| B4 | 本地 LLM,不使用 RAG | 外部規則檢索本身帶來多少差異? |
| B5 | 平面稠密 RAG | 階層、門控、混合檢索與重排序是否超過簡單 RAG? |
B4 尤其重要。若完整方法只和其他 RAG 比較,我們最多知道「哪種檢索流程較好」,無法回答外部規則檢索是否比單獨使用相同 LLM 更好。
公平的 B4 比較必須使用同一批測試案例、相同 LLM、相同輸出格式與盡可能一致的提示詞;唯一核心差異是有沒有把檢索證據送進模型。檢索增強生成的原始工作就是把生成模型與外部檢索記憶結合,因此這組對照直接檢查本專案的核心機制。Lewis 等人:Retrieval-Augmented Generation
基準分數高低都不能直接證明臨床安全。B0 可能因第三級樣本很多而取得不差的整體準確率;B1 可能在資料不足時大量拒答。這些行為仍要放回 Day 07 的各級召回、方向、距離、覆蓋與拒答評估中解讀。
消融研究(Ablation Study)通常從完整方法出發,每次移除一個元件,觀察結果如何改變。這個做法有助於檢查完整系統中的某個元件是否提供可觀察貢獻;相關方法研究也把「移除一部分再觀察表現」用來分析元件重要性。Meyes 等人:Ablation Studies in Artificial Neural Networks
本系列把實驗分成三種用途:
| 類型 | 本系列階段 | 起點 | 唯一主要變動 | 能回答的問題 |
|---|---|---|---|---|
| 控制比較 | A、B、C、D1、D2 | 一組共同簡化條件 | 更換同一因子的水準 | 哪種表示、結構、門控、檢索或重排設定較合適? |
| 移除元件的消融 | E | 完整方法 | 每次移除一個元件 | 完整組合是否依賴這個元件? |
| 生成模型敏感度 | F | 完整方法與固定提示詞 | 更換本地生成模型規模 | 結論是否只在單一生成模型上成立? |
下圖是整條實驗路線。先從 A 到 D2 建立可歸因的控制比較,再以 E 從完整方法移除單一元件,最後用 F 檢查生成模型依賴。底部深藍區塊代表開發資料與規則測試集;測試集在設定鎖定後才執行。

上圖的線性順序是執行與溝通的起點,不表示 A 的最佳水準一定能和 B、C 的最佳水準完美相加。若 E 顯示元件移除效果與前面控制比較不一致,就要把交互作用列為新的待驗證問題,而不是挑一張有利的表報告。
可重現實驗需要把計畫與執行結果分開:
本篇接下來會建立 configs/experiments/day-08-controlled-comparisons.json。JavaScript 物件表示法(JavaScript Object Notation, JSON)是一種以鍵與值儲存結構化資料的純文字格式。這份檔案的 status 是 planning,to-be-locked 表示版本尚待鎖定,因此不能把它當成完成實驗的紀錄。
接下來不會要求你前往任何程式碼網站。請在自己的電腦開啟專案資料夾,依下列順序建立檔案;每個程式碼區塊都是該檔案的完整內容,不含省略號。
這份檔案不依賴前一天的程式;只要電腦能執行 Python 3,就能先做 JSON 語法檢查。
先從專案根目錄建立需要的資料夾:
mkdir -p configs/experiments
如果指令沒有印出訊息是正常的。可用 test -d 資料夾路徑 && echo "資料夾已建立" 驗證單一資料夾。接著使用你熟悉的文字編輯器新增各檔案,把對應區塊完整貼入後儲存。
configs/experiments/day-08-controlled-comparisons.json保存共同條件、A 到 F 控制比較,以及探索性跨模型家族檢查。
請在文字編輯器建立 configs/experiments/day-08-controlled-comparisons.json,貼入以下完整內容並儲存:
{
"schema_version": "0.2",
"status": "planning",
"description": "Day 08 教學用控制比較契約,不代表已完成實驗。",
"shared_conditions": {
"dataset_version": "to-be-locked",
"triage_system_version": "to-be-locked",
"reference_label_source": "to-be-locked",
"test_split_id": "locked-test-v1",
"rulebook_version": "to-be-locked",
"evaluation_protocol_version": "day-07-v1-planning",
"top_k": 5,
"prompt_version": "triage-prompt-v1-planning",
"model_lock": "configs/models/local-ollama-model-lock.json",
"generator_model": "qwen3.5:4b-mlx",
"generator_model_id": "61aa3858e9d3",
"embedding_model": "qwen3-embedding:4b",
"embedding_model_id": "df5bd2e3c74c",
"reranker_model": "online-service-to-be-selected",
"reranker_model_id": "to-be-locked",
"reranker_type": "remote_reranking_service_to_be_locked",
"random_seed": 42
},
"stages": [
{
"id": "A",
"name": "查詢表示",
"fixed": ["flat knowledge base", "dense retrieval", "no gate", "no reranker"],
"levels": ["raw", "semantic", "dual"]
},
{
"id": "B",
"name": "文件結構",
"fixed": ["dual query", "dense retrieval", "no gate", "no reranker"],
"levels": ["flat", "hierarchical"]
},
{
"id": "C",
"name": "門控方式",
"fixed": ["dual query", "hierarchical knowledge base", "dense retrieval", "no reranker"],
"levels": ["none", "vital_hard", "complaint_soft", "safety_union"]
},
{
"id": "D1",
"name": "檢索方式",
"fixed": ["dual query", "hierarchical knowledge base", "safety_union", "no reranker"],
"levels": ["dense", "hybrid"]
},
{
"id": "D2",
"name": "重排序",
"fixed": ["dual query", "hierarchical knowledge base", "safety_union", "hybrid retrieval", "same candidate set"],
"levels": ["off", "on"]
},
{
"id": "E",
"name": "移除單一元件",
"fixed": ["full proposed pipeline"],
"levels": ["full", "minus semantic", "minus hierarchy", "minus safety union", "minus sparse retrieval", "minus reranker"]
},
{
"id": "F",
"name": "生成模型敏感度",
"fixed": ["full proposed pipeline", "fixed prompt"],
"levels": ["qwen3.5:0.8b-mlx", "qwen3.5:4b-mlx"]
}
],
"exploratory_checks": [
{
"id": "G",
"name": "跨生成模型家族敏感度",
"fixed": ["full proposed pipeline", "fixed prompt", "same retrieved evidence", "not used to select the confirmatory model"],
"levels": ["qwen3.5:4b-mlx", "gemma4:e2b"]
}
],
"required_baselines": [
"majority class",
"partial rule-only",
"structured machine learning",
"embedding plus structured features",
"local language model without retrieval",
"flat dense retrieval-augmented generation"
]
}
儲存後先確認檔名與相對路徑完全一致,再繼續建立下一個檔案。
以下命令會在需要時產生套件鎖定檔,接著檢查設定格式與 Python 語法;它們不會啟動模型,也不會把病患資料送到網路:
python3 -m json.tool configs/experiments/day-08-controlled-comparisons.json
每個命令都應正常結束。若 JSON 顯示行號,先檢查貼上時是否遺漏逗號、引號或括號;若 py_compile 報錯,先依行號修正縮排或漏貼內容。靜態檢查通過後,再執行本文後面的正式步驟。
接下來用四個步驟檢查這份計畫。
configs/experiments/day-08-controlled-comparisons.json。在專案根目錄執行下列指令。它只讀取設定檔並檢查語法,不會啟動模型或讀取病患資料。
python3 -m json.tool configs/experiments/day-08-controlled-comparisons.json
json.tool 解析檔案並格式化輸出。依序檢查 shared_conditions 中的資料版本、檢傷制度版本、參考標籤來源、鎖定測試集識別碼、規則版本、評估流程版本、top_k、提示詞版本、生成模型與隨機種子。每一個階段還要確認 fixed 列出的條件沒有在不同水準中偷偷改變。
shared_conditions、各階段的 fixed 與 levels 欄位。to-be-locked 只能出現在規劃階段。正式執行前要換成可追溯的資料版本與規則版本;模型、提示詞、索引、程式版本與測試切分也要固定。實際執行後,再由程式產生 Run Manifest,不要用手動複製來假裝紀錄。
若機器學習實驗沒有保留程式、資料與執行條件,讀者就難以重建同一條比較路徑。Pineau 等人的可重現性計畫報告把「使用相同程式與可取得資料得到相近結果」視為檢查研究發現可靠性的重要步驟,也指出可重現流程有助於減少非預期錯誤。Pineau 等人:Improving Reproducibility in Machine Learning Research
開發資料用來選擇查詢表示、前 k 筆數量、文件切分、提示詞與門檻;鎖定測試集只用來做事先指定的最終評估。
如果每看一次測試結果就回頭修改 top_k、提示詞或門控門檻,再重新測到滿意為止,測試集實際上已參與開發。即使每次都沒有直接訓練模型,反覆且自適應地使用保留資料(Holdout Data)仍可能對同一批資料過度適配;相關研究專門分析了重複使用保留資料所造成的風險。Dwork 等人:Generalization in Adaptive Data Analysis and Holdout Reuse
本系列的做法是:
「鎖定」不是永遠不能改進系統,而是不能一邊偷看同一份考卷,一邊把改過的答案包裝成第一次作答。
前 k 筆候選(Top-k Candidates)是檢索或排序階段保留的前 k 個候選。k 只表示保留數量,不是樣本總數,也不是模型同時下了 k 個正式判定。本系列統一使用 Top-k,避免用 N 同時表示候選數與測試樣本數。
Top-1 是排序第一的候選,可用來分析最終方向;Top-k 則主要報告候選覆蓋率(Candidate Coverage),也就是正確級數或正確證據是否仍在候選集合中。
若要描述候選集合的錯誤方向,只分「全部偏高」與「全部偏低」仍不完整。假設參考級數 y = 3,而且正確級數不在候選中:
候選 {4, 5}:純檢傷不足候選,全部數字都比參考級數大
候選 {1, 2}:純檢傷過度候選,全部數字都比參考級數小
候選 {2, 4, 5}:跨側錯誤,候選同時落在參考級數兩側
公式中的 y 代表參考級數。因為五級數字越小越急迫,候選數字全都大於 y 表示全部偏向較不急;全都小於 y 表示全部偏向較急。第三種集合沒有單一方向,因此不能硬塞進前兩類。
跨側錯誤(Straddling Error)是本系列用於候選稽核的名稱,不是通用臨床標準。它的條件是:正確級數不在候選集合中,而且最小候選小於參考級數、最大候選大於參考級數。
下圖把三種集合放在同一條五級軸線上。請先找黑色箭頭所指的參考第 3 級,再看被外圈標記的候選落在哪一側。

上圖使用合成集合示範候選落點,不是實驗結果。若候選是 {2, 3, 5},雖然同時出現在參考級數兩側,但第 3 級已被找回,所以這是候選覆蓋成功,不屬於跨側錯誤。最終系統是否判成第 3 級,還要看重排序與生成階段,不能由候選集合直接推定。
因此報告要分成三層:
這三層輸出不能互相代替。候選覆蓋成功只證明正確資訊沒有在檢索階段消失,不代表模型一定採用它;最終答案正確也不代表檢索可靠,因為模型可能忽略錯誤證據後碰巧答對。
系統數量不等於因子數量。每套系統可能同時改變查詢、文件、門控、檢索與重排序;必須先畫因子矩陣才能知道改了幾項。
控制比較只能支持指定共同條件下的效果。元件之間可能存在交互作用,換到其他文件結構、資料集或生成模型後仍需驗證。
A 到 D2 是在共同條件下更換因子水準;E 才是從完整方法移除單一元件;F 是模型敏感度檢查。名稱分清楚,讀者才知道比較的起點與問題。
最簡單基準只能說明完整方法超過最低參照。沒有結構化 ML、不使用 RAG 的 LLM 與平面 RAG 等中間基準,就無法知道提升來自生成、檢索、結構還是額外複雜度。
候選可能同時落在參考級數兩側,也可能已包含正確級數。候選層要先報覆蓋,再將未覆蓋集合拆成純單側與跨側錯誤;最終方向仍由 Top-1 或最終輸出判定。
今天沒有訓練模型,也沒有產生效能排行榜。我們完成的是一份能讓後續結果被解釋的實驗骨架:
這些設計提升的是結果的可解釋性與可重現性,不是臨床部署資格。系統仍定位為臨床決策支援候選,不能取代醫師、護理師與正式檢傷流程。
Day 08 已經把模糊的「三套架構誰最好」拆成可以控制的實驗因子。Day 09 會把每一項比較再寫成明確的研究問題、預先假設、主要指標與可被資料否定的判準。
換句話說,今天決定「一次只動哪一顆旋鈕」;下一篇要決定「動完之後,看到什麼才算支持或不支持原本的想法」。