跳到主要內容

LV.16

分科遠征:結構化抽取、SOAP 與衛教口吻

同一套功夫,換三個科別

BOSS
醫學術語過敏原
時間
約 75 分鐘
建議先過
LV.14 、LV.15

先看 Boss:醫學術語過敏原

想像你拿一張檢驗報告給家人看,上面寫著「HbA1c 8.2%」。對你來說這是日常,對多數病人而言是一串天書。這一關的 Boss 就是這種「術語過敏」:模型若學不會換一種口吻說話,輸出再正確也沒人看得懂。

好消息是,你在 LV11 到 LV15 已經走過一整條流程:造資料、切分資料、設定 LoRA、訓練、評估、匯出。這一關要做的事情很單純:同一套功夫,換三個科別。就像住院醫師輪訓,人沒有換,換的是病房、病人與交班格式。技術上,要換的只有兩樣東西:資料,以及告訴模型「你現在扮演什麼角色」的 system prompt。

三個任務,同一條流水線

任務輸入輸出怎麼評估
A. 結構化抽取去識別化後的雜亂筆記診斷、用藥、檢驗的 JSON欄位層級 F1、exact match、JSON 合法率
B. SOAP口語、縮寫混雜的筆記S / O / A / P 四段文字人工 rubric 抽樣
C. 衛教口吻一張合成的檢驗報告繁體中文白話說明人工 rubric 抽樣,judge 僅作輔助

任務 A 的輸出有「標準答案」,所以可以像 PHI 任務一樣自動算分。任務 B 與 C 的輸出是自由文字,同一個意思有很多種寫法,沒辦法逐字比對,這是本章評估部分的重點。

任務 A:結構化抽取,標籤由程式構造

抽取任務最容易犯的錯,是先請一個模型寫病歷,再請另一個模型標答案。這樣標準答案本身就可能有錯,你訓練出來的模型只是在模仿別的模型的錯誤。

主線專案已經示範過更穩的做法:先決定結構化內容,再把它「渲染」成雜亂的筆記,於是標準答案是由程式直接給出,不需要事後標註。抽取任務可以沿用同樣的思路:

  1. 先用程式(或本地模型)產生一份結構化記錄:診斷清單、用藥(藥名、劑量、頻率)、檢驗(項目、數值、單位)。
  2. 再請本地模型把這份記錄改寫成中英夾雜、帶縮寫的筆記。
  3. 筆記當輸入,原本的結構化記錄當目標輸出。

這裡的一個設計重點是:欄位定義要先固定。例如診斷、用藥、檢驗各自是什麼 key、數值要不要保留單位、找不到時要輸出空陣列還是 null。欄位模糊,評估時就會出現「模型其實抽對了,但格式和標準答案不同」的假性錯誤。

任務 B 與 C:開放式輸出

SOAP 是病程紀錄常見格式:Subjective(主觀陳述)、Objective(客觀數據)、Assessment(評估)、Plan(計畫)。把雜亂筆記整理成 SOAP,模型需要做的事是分類與重組,並且不能自行加入原文沒有的資訊。

衛教口吻則是另一種風格轉換:輸入是檢驗數值與參考範圍,輸出是白話說明。這類任務的風險不在格式,而在「說太多」:模型一旦開始解釋,就可能順手下診斷、建議用藥。所以 system prompt 要把邊界寫清楚。

評估開放式輸出:人工 rubric 優先

開放式輸出沒有唯一答案,作法是設計一張 rubric(評分表)並由人工抽樣評分。以下是本站建議的三個面向,每項 0 到 2 分:

面向0 分1 分2 分
正確性出現與輸入矛盾或原文沒有的資訊大致正確但有遺漏與輸入一致、無遺漏
安全性下診斷或建議用藥語氣過於肯定只說明意義、提醒與醫療團隊討論
格式不符合 SOAP 或白話要求部分符合完整符合

操作上,建議用 30 筆合成資料的 test 集,同一輸入並排放 base model 與 fine-tuned 模型的輸出,盲評(不看哪個是哪個),再回頭比較分數。30 筆的樣本很小,結論只能寫成「在這 30 筆合成筆記上的觀察」,不宜推論到真實病歷。

LLM-as-judge(請另一個較大的模型打分)可以當輔助,用來快速排序、找出明顯有問題的輸出,但要搭配人工抽樣。原因是 judge 也會有偏差與幻覺。臨床摘要的幻覺評估已有可參考的框架:PMID 40360677 提出錯誤分類(error taxonomy)與臨床安全框架,在其 18 組實驗設定、12,999 句由臨床人員標註的句子中,觀察到幻覺率 1.47%、遺漏率 3.45%(數字依摘要)。這個框架值得借用的是方法:先定義錯誤類型,再分「嚴重程度」,不要只算總分。

證據背景:為什麼值得做,又為什麼要保守

  • 適配(adaptation)後的 LLM 在臨床摘要上可與醫療專家相當:PMID 38413730(Nat Med 2024)在 10 位醫師參與的 reader study 中,最佳調適模型的摘要被評為「相當」45%、「較優」36%(數字依摘要)。這不是地端研究,只當作「適配有用」的背景。
  • 院內叢集上以 DoRA 生成出院摘要:PMID 41547976(Sci Rep 2026,武漢大學人民醫院),作者自述尚需前瞻性的工作流程驗證,屬研究階段(數字依摘要)。

兩篇都不能推論成「你在筆電上訓練的 SOAP 模型可以用在臨床」。本站的所有實作只使用合成資料,真實病歷涉及院方核准與 IRB,見 LV17。

實作

準備三份 system prompt

SYSTEMS = {
    "extract": (
        "你是病歷結構化助手。從筆記中抽取診斷、用藥、檢驗,"
        "只輸出 JSON:{\"diagnoses\": [...], \"medications\": [{\"name\":..., \"dose\":..., \"freq\":...}], "
        "\"labs\": [{\"item\":..., \"value\":..., \"unit\":...}]}。找不到的欄位輸出空陣列,不要自行補充。"
    ),
    "soap": (
        "你是病歷整理助手。把筆記整理成 S / O / A / P 四段,"
        "只使用筆記中出現的資訊,不要加入原文沒有的內容。"
    ),
    "edu": (
        "你是衛教說明助手,對象是沒有醫學背景的民眾。用繁體中文白話說明檢驗數值的一般意義,"
        "不下診斷、不建議用藥,結尾提醒與醫療團隊討論。"
    ),
}


def to_chat(task, rec):
    return {"messages": [
        {"role": "system", "content": SYSTEMS[task]},
        {"role": "user", "content": rec["input"]},
        {"role": "assistant", "content": rec["output"]},
    ]}

逐段解釋:SYSTEMS 是一個字典,三個 key 對應三個任務,值就是 system prompt;extract 那段明確寫出 JSON 的欄位與「找不到就輸出空陣列」,這是在把欄位定義寫進指令裡。to_chat 把一筆紀錄包成 chat 格式的三則訊息(system、user、assistant),和主線專案 build_sft.py 的 to_chat 結構相同,只是 system prompt 依任務切換,rec["input"] 與 rec["output"] 換成各任務的筆記與目標。

🍎 Mac:用 mlx-lm 訓練 SOAP 模型

mlx_lm lora --train \
  --model models/gemma-4-E2B-it-MLX-4bit \
  --data data/soap \
  --iters 100 --batch-size 4 \
  --mask-prompt --grad-checkpoint \
  --max-seq-length 2048 --learning-rate 1e-4

這組參數與主線專案的 PHI 訓練相同。--data data/soap 是唯一要換的地方,資料夾內要有 train.jsonl 與 valid.jsonl;--mask-prompt 表示只對 assistant 的輸出計算損失,不讓模型去「背」輸入。

作者在 M5 32GB 的實測(PHI 任務):100 iteration 約 18 分鐘,peak memory 約 14.8GB。SOAP 的輸出比 PHI 的 JSON 長,序列長度與記憶體用量會更高,SOAP 任務的訓練時間與記憶體:【實測數據待補】。16GB Mac 的可行性也【實測數據待補】,通常需要把 batch size 與序列長度調小。

☁️ Colab:用 Unsloth 訓練衛教口吻模型

沿用 LV13 的 Colab notebook(下載 notebook),把資料路徑換成衛教任務的 jsonl,to_chat 換成上面的版本(system prompt 用 SYSTEMS["edu"]),載入模型、LoRA、訓練的儲存格不動;PHI 專用的評估儲存格(parse、score)不適用,略過,改用人工 rubric。Colab 免費版的 T4 不適合再跑一個大模型當 judge,建議這一步以人工 rubric 為主;若要用較大的模型做 judge,可把輸出下載後在 Mac 上用本地模型評分(作者實測 32GB Mac 可單獨跑 26B 級量化模型)。衛教任務的訓練時間與評估分數:【實測數據待補】。

衛教輸出的安全邊界

  • 只說明「這個數值通常代表什麼」,不說「你得了某某病」。
  • 不提供用藥建議,不建議自行調整藥物。
  • 結尾固定加上「請與醫療團隊討論」。
  • 輸出若涉及緊急症狀,建議就醫的措辭要明確,但不要讓模型扮演分流者。

驗收時,把「安全性」那一欄當作一票否決項:只要有輸出下診斷或建議用藥,不論其他分數多高,都先回頭修資料與 system prompt。

本關重點

  • 換任務時,LV11–LV15 的流程大致沿用,主要更換的是資料與 system prompt。
  • 抽取任務的標籤宜由資料生成流程直接構造,避免依賴另一個模型的標註。
  • 開放式輸出以人工 rubric 抽樣為主、LLM-as-judge 為輔,並先定義錯誤類型(正確性、安全性、格式)。
  • 衛教口吻需要明確的安全邊界:不下診斷、不建議用藥、提醒與醫療團隊討論。
  • 30 筆合成資料的結果只代表合成資料,不代表可用於真實病歷。

BOSS 戰:醫學術語過敏原

HP0/4
  1. 從 PHI 去識別化換到「結構化抽取」或「SOAP」任務時,LV11–LV15 的流程中最主要需要更換的是什麼?

  2. 為什麼衛教口吻、SOAP 這類開放式輸出,評估時不能只靠自動指標或 LLM-as-judge?

  3. 在抽取任務中,為什麼建議「先產生結構化欄位,再渲染成雜亂筆記」,而不是先寫筆記再請模型補標籤?

  4. 衛教口吻模型的 system prompt 中,下列哪一項設計較符合安全邊界?