Repository: https://github.com/snap-stanford/med-flamingo Stars: 451 | Forks: 39 | Language: Python | License: 未標示(repo 未附 LICENSE 檔) 論文: Med-Flamingo: A Multimodal Medical Few-shot Learner (Moor et al., 2023)


1. 專案概覽 (Project Overview)

1.1 專案背景與研究團隊

med-flamingo 是史丹佛大學 SNAP(Stanford Network Analysis Platform,社群網路分析平台)實驗室與相關合作團隊(Jure Leskovec 教授、Pranav Rajpurkar 教授等)在 2023 年發表的醫療多模態基礎模型 (medical multimodal foundation model)。專案核心貢獻者包含 Michael Moor、Qian Huang、Shirley Wu、Michihiro Yasunaga 等人,論文掛名於 arXiv:2307.15189。

這個 repo 是論文的官方程式碼釋出,但需要特別注意:README 開頭明確寫著「More updates to follow soon!」,而截至目前,repo 內容仍停留在最初釋出的最小可行示範 (minimal demo) 狀態——只有一支 demo.py 展示如何載入模型並做單次 few-shot 推論,並未包含完整訓練程式碼、評估腳本或資料前處理流程。這是理解本專案定位的第一個關鍵事實。

1.2 解決什麼問題、為什麼重要

醫療視覺問答 (Medical Visual Question Answering; Med-VQA) 是一個高難度任務:模型需要同時理解放射影像/病理影像等專業醫學影像,以及以自然語言表達的臨床問題,並產生正確的醫學回答。過去的做法多半是針對單一資料集(如 VQA-RAD、SLAKE、PathVQA)從頭訓練小模型,這類模型:

  • 需要大量標註資料,而醫療標註資料因隱私與專業門檻極為稀缺
  • 難以跨資料集/跨模態泛化(例如同時處理 X 光、MRI、病理切片)
  • 無法像通用視覺語言模型 (Vision-Language Model; VLM) 一樣做 in-context few-shot learning(少樣本上下文學習)

med-flamingo 的核心貢獻,是把 DeepMind 的 Flamingo 架構(一個支援「圖文交錯輸入 + few-shot 上下文學習」的視覺語言模型)延伸到醫療領域:先用 Flamingo 的開源重現版 OpenFlamingo 作為基底,再在醫療影像—文字配對語料(包含教科書圖說與 PMC 論文圖文)上繼續預訓練 (continued pre-training),讓模型獲得醫學領域知識,同時保留 Flamingo 原有的 few-shot in-context learning 能力。

這解決的真實問題是:如何在資料稀缺、標註昂貴的醫療場景下,用少量範例(few-shot prompt)而非大規模微調,就能讓模型對新的醫學影像問題給出合理回答。這對臨床決策輔助、醫學教育、放射科報告輔助閱讀等場景有直接價值。

1.3 在 snap-stanford 生態系中的定位

snap-stanford 組織下有大量圖神經網路 (Graph Neural Network; GNN)、知識圖譜、生醫 AI 相關專案(如 ogb、pretrain-gnns、GraphRNN 等)。med-flamingo 是其中少數聚焦於多模態醫療基礎模型的專案,可視為 SNAP 實驗室將基礎模型 (foundation model) 技術延伸進醫療影像領域的代表作之一,與同實驗室在生醫知識圖譜、藥物發現方向的其他專案共享「將前沿 AI 架構應用於生醫問題」這條主線,但技術棧完全獨立(不依賴圖神經網路,而是 Transformer-based 視覺語言模型)。

1.4 相關論文引用

README 中列出三篇奠基文獻,形成清楚的技術血緣:

  1. Med-Flamingo (Moor et al., 2023, arXiv:2307.15189) — 本 repo 對應論文
  2. OpenFlamingo (Awadalla et al., 2023) — Flamingo 的開源重現實作,med-flamingo 直接依賴此套件的 create_model_and_transforms API
  3. Flamingo (Alayrac et al., 2022) — DeepMind 原始論文,提出 gated cross-attention(門控交叉注意力)機制,是整條技術脈絡的源頭

2. 核心架構 (Core Architecture)

2.1 系統架構圖


flowchart TB
    subgraph input["輸入層"]
        img["醫學影像
(X光/MRI/病理切片)"] txt["文字 Prompt
(few-shot 範例 + 問題)"] end subgraph vision["視覺編碼分支"] clip["CLIP ViT-L/14
(openai 預訓練權重)"] vproc["FlamingoProcessor
.preprocess_images()"] end subgraph text["文字編碼分支"] tok["LlamaTokenizer
(Llama-7B v1 詞表)"] tproc["FlamingoProcessor
.encode_text()"] end subgraph core["OpenFlamingo 核心"] llama["Llama-7B (v1)
凍結/部分凍結語言模型骨幹"] xattn["Gated Cross-Attention
每 4 層插入一次
(cross_attn_every_n_layers=4)"] end subgraph ckpt["Med-Flamingo 權重"] hfhub["hf_hub_download
med-flamingo/med-flamingo
model.pt"] end subgraph output["輸出"] gen["model.generate()
自迴歸生成"] clean["clean_generation()
移除 <unk> 等雜訊 token"] ans["最終醫學問答回覆"] end img --> vproc --> clip txt --> tproc --> tok clip --> xattn tok --> llama xattn --> llama hfhub -.載入權重覆蓋.-> llama hfhub -.載入權重覆蓋.-> xattn llama --> gen --> clean --> ans style core fill:#2b2b3d,stroke:#8888ff,color:#fff style ckpt fill:#3d2b2b,stroke:#ff8888,color:#fff

2.2 關鍵模組、類別與資料流說明

整個 repo 的程式碼量非常精簡(僅 4 支 Python 檔案),但每個模組職責清楚:

檔案角色說明
src/utils.py資料前處理抽象層定義 AbstractProcessor 抽象類別 (abstract class) 與其實作 FlamingoProcessor
scripts/demo.py主流程載入模型、下載權重、組 prompt、跑推論的完整範例
scripts/demo_utils.py輔助函式範例影像路徑列表、生成結果清理函式 clean_generation
install.sh / requirements.txt環境設定固定版本的相依套件安裝腳本

資料流(data flow)demo.py 的執行順序:

  1. 模型建構:呼叫 open_flamingo.create_model_and_transforms(),傳入 CLIP 視覺編碼器規格與本地 Llama-7B 路徑,產生 (model, image_processor, tokenizer) 三元組——這一步建的是通用 OpenFlamingo 骨架,權重仍是初始化/CLIP 預訓練狀態。
  2. 權重覆蓋:透過 huggingface_hub.hf_hub_downloadmed-flamingo/med-flamingo repo 下載 model.pt,再用 model.load_state_dict(..., strict=False) 蓋上去。strict=False 很關鍵:因為 med-flamingo 只微調了部分參數(cross-attention 層與少量投影層),其餘 Llama 骨幹權重不在 checkpoint 內,必須容許 key 不完全匹配。
  3. Processor 封裝FlamingoProcessor(tokenizer, image_processor) 把兩種模態的前處理封裝成統一介面。
  4. Accelerate 部署:用 Hugging Face Accelerateaccelerator.prepare(model) 處理裝置搬移(單卡/多卡/CPU 皆可透過同一份程式碼路徑執行)。
  5. 推論:影像先 preprocess_images → 用 einops.repeat 擴增維度成 (batch, num_images, num_frames, C, H, W) 這個 Flamingo 特有的 5 維張量格式 → 文字 prompt 用 <image><|endofchunk|> 兩個特殊 token 交錯範例 → 送進 model.generate()

2.3 設計哲學與技術選擇分析

med-flamingo 的設計哲學可以歸納為「最大化重用、最小化改動」:

  • 不重新設計架構:完全借用 OpenFlamingo 的 create_model_and_transforms 作為模型工廠函式,本專案沒有自己定義任何神經網路層。這是務實的工程選擇——架構創新留給 Flamingo/OpenFlamingo 團隊,med-flamingo 專注在「領域資料 + 繼續預訓練」這個增量貢獻上。
  • strict=False 的權重載入策略:這反映了 continued pre-training(延續預訓練)常見模式——只更新部分參數(通常是新增的 cross-attention gate 參數),其餘沿用基礎模型權重,藉此降低微調成本並保留原模型能力(避免災難性遺忘)。
  • Processor 用抽象類別而非 duck typingAbstractProcessor(ABC) 明確定義 encode_text / preprocess_images 兩個必須實作的方法,這在只有一個具體實作 FlamingoProcessor 的情況下略顯「過度設計」,但預留了未來替換視覺編碼器(例如換成醫療專用的 CLIP 變體)的擴充點。
  • <image> / <|endofchunk|> 特殊 token 的 prompt 設計:這不是本專案發明的,而是繼承自 Flamingo 論文的 in-context 交錯格式,讓語言模型能學會「圖片後面接一組問答」的模式,這正是 few-shot in-context learning 之所以能運作的關页設計。

2.4 模型規模與參數量拆解

理解 med-flamingo 的推論成本,需要先拆解它由哪些子模組組成、各佔多少參數量。以下數字依 OpenFlamingo 官方文件與 Flamingo 論文公開資訊整理,供資源規劃參考(實際數字以模型 config 為準):

子模組概略參數量是否被 med-flamingo 繼續預訓練更新
CLIP ViT-L/14 視覺編碼器約 3 億否,凍結沿用 openai 預訓練權重
Llama-7B 語言模型骨幹約 70 億否,凍結沿用原始 Llama 權重
Gated Cross-Attention 層(每 4 層插入)約數千萬至一億(依插入層數而定),這是 med-flamingo 訓練更新的主要部分
Perceiver Resampler(將視覺 token 壓縮為固定長度)約數千萬是,隨 cross-attention 一併訓練

這個拆解回答了一個常見疑問:「7B 的模型檔案下載下來明明很大,為什麼 model.pt 只需要載入部分權重(strict=False)?」——因為 med-flamingo 釋出的 checkpoint 只包含新增/更新的那一小部分參數(cross-attention 與 resampler),凍結的 CLIP 與 Llama 骨幹權重是在 create_model_and_transforms() 這一步就從各自的官方來源載入的,两者互補才組成完整可推論的模型。這也是為什麼 llama_path 一定要指向正確的 Llama-7B 權重——少了它,load_state_dict(strict=False) 只會補上 cross-attention 部分,語言模型骨幹仍是空的。

2.5 與純文字 LLM 應用的架構差異

如果你熟悉一般純文字大型語言模型 (Large Language Model; LLM) 的應用開發(例如接 OpenAI API 做 RAG),med-flamingo 的架構有幾個本質差異需要留意:

  1. 輸入不是單純字串:純文字 LLM 的 API 呼叫通常是 prompt: str -> response: str;med-flamingo 需要額外傳入 vision_x(一個 5 維張量)與精確對齊的 <image> token 位置,圖片數量必須與 prompt 中 <image> token 出現次數一致,否則會出現張量維度不匹配的錯誤。
  2. 沒有 Chat/Instruct 對話格式:med-flamingo 使用的是 Flamingo 原始的「純接龍」(completion-style) 生成,而非像 ChatGPT 系列那樣的多輪對話 (multi-turn) 格式,也沒有 system/user/assistant 角色標記,prompt 設計完全依賴 few-shot 範例的排列方式。
  3. 無伺服器化 API:純文字 LLM 常見的做法是包一層 REST API(如 vLLM、TGI)常駐服務;med-flamingo 官方 demo 是「每次執行都重新載入 7B 模型」的腳本模式,若要服務化需自行改寫(詳見 6.3 節建議)。

3. 安裝與設定 (Installation & Setup)

3.1 環境需求

  • 作業系統:Linux(install.sh 假設 CUDA 可用;README 標註「we assume GPU device / cuda available」)
  • GPU:建議至少一張具備 ≥24GB VRAM 的 NVIDIA GPU(Llama-7B fp32/fp16 推論的實務門檻;repo 本身未寫死顯存需求,但 7B 參數語言模型 + 視覺編碼器的組合,consumer GPU 如 3090/4090 24GB 大致可跑推論)
  • Python:requirements.txt 鎖定的套件版本對應約 Python 3.9–3.10(backports.zoneinfo 這類套件僅在 Python < 3.9 環境需要,暗示鎖定版本較舊)
  • CUDA 工具鏈torch==2.0.0 搭配 cu117(CUDA 11.7)
  • git-lfs:下載 Llama-7B 權重需要

3.2 完整安裝步驟

 1# 1. 建立虛擬環境
 2virtualenv flam_env
 3source flam_env/bin/activate
 4
 5# 2. Clone 專案
 6git clone https://github.com/snap-stanford/med-flamingo.git
 7cd med-flamingo
 8
 9# 3. 執行官方安裝腳本
10source install.sh
11# install.sh 內部會依序執行:
12#   pip install torch==2.0.0 torchvision==0.15.1 -f https://download.pytorch.org/whl/cu117
13#   pip install git+https://github.com/usuyama/open_clip.git@01a53cc...   # 客製化 open_clip fork
14#   pip install datasets==2.9.0 wandb==0.13.10 einops==0.6.0 einops_exts==0.0.4
15#   pip install h5py ipykernel
16#   pip install -r requirements.txt   # 含 open-flamingo==0.0.2、transformers 特定 commit 等

3.3 資料集下載與準備(Llama-7B v1 權重)

這是整個安裝流程中最容易卡關的一步。原因是 med-flamingo 綁定 Llama-7B v1(Meta 最早釋出、非官方轉存的版本),而近期 transformers 函式庫的 tokenizer 類別命名已改版,直接用 Hugging Face Space 上的版本可能會遇到 tokenizer 類別對不上的問題。README 給出的解法:

 1# 1. 安裝 git-lfs
 2git lfs install
 3
 4# 2. 手動下載 Llama-7B (v1) 到本地
 5mkdir -p models
 6cd models
 7git clone https://huggingface.co/decapoda-research/llama-7b-hf
 8cd ..
 9
10# 3. 修正 tokenizer 設定檔
11# 編輯 models/llama-7b-hf/tokenizer_config.json,將:
12#   "tokenizer_class": "LLaMATokenizer"
13# 改成:
14#   "tokenizer_class": "LlamaTokenizer"

接著 med-flamingo 的模型權重會在執行 demo.py 時自動下載(不需手動準備資料集):

1checkpoint_path = hf_hub_download("med-flamingo/med-flamingo", "model.pt")

這行程式碼會從 Hugging Face Hub 上的 med-flamingo/med-flamingo repo 拉取繼續預訓練後的權重檔 model.pt,並快取在本機 ~/.cache/huggingface/hub。範例影像(7 張醫學影像,如 synpic50962.jpg 等)則已隨 repo 一起釋出在 img/ 目錄下,取自 SynPic 醫學影像案例庫的公開範例。

3.4 執行 Demo

1cd scripts/
2# 編輯 demo.py,將 llama_path 改成你的本地 Llama-7B 路徑:
3#   llama_path = '../models/llama-7b-hf'
4python demo.py

3.5 常見問題排解

問題原因解法
ValueError: Llama model not yet set upllama_path 路徑不存在依 3.3 節手動下載並確認路徑
Tokenizer 類別載入失敗transformers 版本與 tokenizer_class 欄位不匹配修改 tokenizer_config.jsonLlamaTokenizer(注意大小寫)
CUDA OOM (顯存不足)7B 語言模型 + 視覺編碼器全載入改用 Accelerator(cpu=True)(極慢)、或用 bitsandbytes 做 8-bit 量化載入(repo 已在 requirements 內含 bitsandbytes==0.39.0,但 demo.py 未預設啟用,需自行修改載入邏輯)
open_clip 安裝失敗官方 open_clip 版本與 OpenFlamingo 要求不符務必用 README 指定的 fork:usuyama/open_clip 特定 commit,不要用 PyPI 版本
套件版本衝突(transformers/accelerate 用 git commit 鎖版)requirements.txt 鎖 2023 年中的特定 commit hash用獨立虛擬環境隔離,不要裝進既有專案環境
hf_hub_download 下載緩慢或失敗Hugging Face Hub 網路連線問題,或該 repo 需要接受使用條款確認已用 huggingface-cli login 登入且已在網頁上接受 med-flamingo/med-flamingo 的使用條款;必要時設定 HF_HUB_ENABLE_HF_TRANSFER=1 加速
RuntimeError: size mismatchload_state_dictcross_attn_every_n_layers 與釋出 checkpoint 訓練時的設定不一致保持 demo.py 預設值 4,不要自行更改此參數去配合其他 Llama 變體
生成結果全是重複或無意義字元few-shot prompt 中 <image> 數量與實際傳入圖片數量不一致確認 demo_images 長度與 prompt 中 <image> token 出現次數完全相同
git clone Llama 權重時卡在極慢下載decapoda-research/llama-7b-hf 檔案體積約 13GB,且該 repo 為非官方鏡像,下載速度不穩定改用機構內部鏡像、或考慮改用其他已驗證與本專案相容的 Llama v1 權重來源,並自行驗證檔案完整性(checksum)

3.6 驗證安裝是否成功

在真正跑 demo.py 之前,建議先用以下最小檢查腳本快速驗證環境(可獨立跑,不需完整跑完 7B 模型推論):

 1"""quick_check.py —— 驗證關鍵套件與路徑是否就緒"""
 2import importlib
 3import os
 4
 5REQUIRED_PACKAGES = ["torch", "open_flamingo", "accelerate", "einops", "huggingface_hub", "PIL"]
 6
 7for pkg in REQUIRED_PACKAGES:
 8    try:
 9        importlib.import_module(pkg)
10        print(f"[OK] {pkg} 已安裝")
11    except ImportError as e:
12        print(f"[FAIL] {pkg} 未安裝: {e}")
13
14llama_path = "models/llama-7b-hf"
15if os.path.exists(os.path.join(llama_path, "tokenizer_config.json")):
16    print(f"[OK] 找到 Llama tokenizer 設定於 {llama_path}")
17else:
18    print(f"[FAIL] 尚未在 {llama_path} 找到 Llama-7B 權重,請依 3.3 節下載")
19
20import torch
21print(f"[INFO] CUDA 可用: {torch.cuda.is_available()}")
22if torch.cuda.is_available():
23    print(f"[INFO] GPU: {torch.cuda.get_device_name(0)}, "
24          f"顯存: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")

執行 python quick_check.py,若全部顯示 [OK] 且顯存 ≥ 24GB,即可放心跑完整 demo。


4. 核心概念詳解 (Key Concepts)

4.1 Few-shot In-Context Learning(少樣本上下文學習)是什麼

想像你要教一個從沒看過胸腔 X 光片的實習醫生怎麼判讀。你不會給他一本厚重的教科書重新訓練他的大腦(那是 fine-tuning/微調),而是直接給他看 5-6 個「範例影像 + 問題 + 正確答案」的案例,讓他當場「照樣造句」去回答新的一張片子。這就是 few-shot in-context learning:不更新模型任何參數,純粹靠 prompt 裡的範例讓模型現學現用

demo.py 中,這個「教學範例」被寫成一長串字串:

1<image>Question: What is/are the structure near/in the middle of the brain? Answer: pons.<|endofchunk|>
2<image>Question: Is there evidence of a right apical pneumothorax on this chest x-ray? Answer: yes.<|endofchunk|>
3...(共 6 組範例)...
4<image>Question: Where is the largest mass located in the cerebellum? Answer:

模型看到 6 組「圖 + 問 + 答」的模式後,遇到第 7 張圖只給問題不給答案,就會依循同樣的模式「接龍」生成答案。這正是 Flamingo 架構最大的賣點——不用重新訓練就能適應新任務

4.2 Gated Cross-Attention(門控交叉注意力):視覺如何「插入」語言模型

Flamingo 架構最關鍵的設計,是它並非把圖片特徵和文字 token 簡單串接(concat)在一起餵給 Transformer,而是保留 Llama 語言模型原本的每一層,並在其中每隔幾層(本專案設定 cross_attn_every_n_layers=4,也就是每 4 層插入一次)新增一個 cross-attention 子層,讓語言模型在生成每個文字 token 時,都能「回頭看」前面出現過的影像特徵。

用類比來說:這就像你在寫病歷報告時,桌上攤著病人的 X 光片——你不需要把片子「背」進大腦裡(不像純文字模型只能吃已經被轉譯成文字的描述),而是隨時抬頭看一眼片子再繼續寫字。「gated」(門控)則是指這個看片子的動作有一個可學習的開關參數,訓練初期這個開關接近 0(幾乎不看),確保不會一開始就破壞原本語言模型的能力,隨訓練逐漸放開。


flowchart LR
    subgraph llama_stack["Llama-7B 堆疊(部分層示意)"]
        direction TB
        l1["Layer 1
(純文字 self-attention)"] l2["Layer 2
(純文字 self-attention)"] l3["Layer 3
(純文字 self-attention)"] l4["Layer 4
(純文字 self-attention)"] xa1["Gated Cross-Attn
(插入點, 每4層一次)"] l5["Layer 5...8"] end vfeat["視覺特徵
(CLIP 編碼輸出)"] -.門控加權.-> xa1 l1 --> l2 --> l3 --> l4 --> xa1 --> l5 style xa1 fill:#3d2b2b,stroke:#ff8888,color:#fff

4.3 為何要「繼續預訓練 (Continued Pre-training)」而非直接 Fine-tune

一般人可能會問:既然有標註好的醫療 VQA 資料集,為何不直接拿去 fine-tune?med-flamingo 的做法是先在大量弱監督的醫學圖文配對資料(教科書圖說、PMC 論文中的圖片與其 caption)上做繼續預訓練,讓模型先學會「醫學影像長什麼樣、對應的醫學術語怎麼講」,之後在推論時才用少量 VQA 範例做 few-shot in-context 適配,完全跳過在 VQA 資料集上做梯度更新的步驟

這解決了一個現實問題:醫療 VQA 標註資料稀少且品質參差(常源自小型單一機構的資料集),若直接在上面 fine-tune 容易過擬合(overfitting)到特定資料集的措辭習慣,泛化能力差。用「先做領域繼續預訓練 + 後做 few-shot 推論」的兩階段策略,可以更好地利用大量非結構化醫學圖文資料,同時保留模型的通用推理彈性。

4.4 Processor 抽象化的意義

AbstractProcessor 把「文字要怎麼編碼」和「圖片要怎麼前處理」定義成介面契約:


classDiagram
    class AbstractProcessor {
        <>
        +encode_text(prompt) *
        +preprocess_images(images: list) *
    }
    class FlamingoProcessor {
        -tokenizer
        -vision_processor
        +encode_text(prompt)
        +preprocess_images(images: list)
    }
    AbstractProcessor <|-- FlamingoProcessor

這個設計的價值在於:未來若要換用不同的視覺編碼器(例如換成醫療影像專用的 BiomedCLIP)或不同語言模型骨幹,只需要新增一個 XxxProcessor 實作類別,demo.py 主流程完全不用改動——這是典型的「依賴介面而非依賴實作」原則的體現。

4.5 論文的評估方法論:為何醫療 VQA 不能只看「答對率」

med-flamingo 論文的一個重要方法論貢獻,是指出傳統醫療 VQA 評測(單純比對答案字串是否完全一致的 accuracy 指標)不足以反映模型的真實臨床可用性——因為醫學問題常常有多種同樣正確但用詞不同的回答方式(例如「肺炎」與「肺部感染」在特定語境下可能都算對)。論文因此引入臨床醫師人工評分 (clinician evaluation),讓真正的醫師對模型生成的回答進行相關性與正確性評分,而非單純的字串比對。

這對想要在自己資料上重現或延伸 med-flamingo 評測結果的使用者是重要提醒:若只用簡單的 exact-match 或 BLEU/ROUGE 分數評估,很可能低估模型的實際表現,也可能誤導後續的模型選型決策。若要做嚴謹的評估,建議:

  1. 準備結構化評分表單(正確性、相關性、有害性/幻覺風險三個維度)
  2. 邀請領域專家(非工程師本人)盲評,避免評分者知道答案來源產生偏誤
  3. 針對「模型拒答」與「模型幻覺出錯誤但看似合理的答案」分開統計,因為兩者的臨床風險完全不同——後者風險遠高於前者

4.6 名詞對照表

English縮寫中文翻譯本文中的意義
Vision-Language ModelVLM視覺語言模型能同時處理影像與文字的模型類別總稱
Visual Question AnsweringVQA視覺問答「看圖回答問題」的任務類型
Few-shot In-Context LearningICL少樣本上下文學習靠 prompt 內範例現學現用,不更新參數
Gated Cross-Attention門控交叉注意力Flamingo 讓語言模型「看」影像特徵的機制
Continued Pre-training繼續預訓練在既有預訓練模型基礎上,用新領域資料繼續訓練
Perceiver Resampler感知重取樣器把可變數量的影像特徵壓縮成固定長度 token 序列的模組
Foundation Model基礎模型大規模預訓練、可遷移至多任務的通用模型
Retrieval-Augmented GenerationRAG檢索增強生成生成前先檢索外部知識輔助的架構模式

5. 使用方式與程式碼範例 (Usage & Code Examples)

5.1 基礎用法:完整可執行的推論範例

以下是根據 repo 原始 demo.py 整理、可直接執行的完整範例(需先完成第 3 節安裝與 Llama-7B 設定):

 1"""
 2med-flamingo 基礎推論範例
 3執行方式:cd scripts/ && python demo.py
 4"""
 5from huggingface_hub import hf_hub_download
 6import torch
 7import os
 8from open_flamingo import create_model_and_transforms
 9from accelerate import Accelerator
10from einops import repeat
11from PIL import Image
12import sys
13sys.path.append('..')
14from src.utils import FlamingoProcessor
15from demo_utils import image_paths, clean_generation
16
17
18def main():
19    # 步驟 0:裝置管理(CPU/單GPU/多GPU 由 Accelerate 統一處理)
20    accelerator = Accelerator()  # 純 CPU 推論可改成 Accelerator(cpu=True)
21    device = accelerator.device
22
23    print('Loading model..')
24    llama_path = '../models/llama-7b-hf'
25    if not os.path.exists(llama_path):
26        raise ValueError('Llama model not yet set up, please check README for instructions!')
27
28    # 步驟 1:建立 OpenFlamingo 骨架(CLIP 視覺編碼器 + Llama 語言模型 + cross-attn)
29    model, image_processor, tokenizer = create_model_and_transforms(
30        clip_vision_encoder_path="ViT-L-14",
31        clip_vision_encoder_pretrained="openai",
32        lang_encoder_path=llama_path,
33        tokenizer_path=llama_path,
34        cross_attn_every_n_layers=4
35    )
36
37    # 步驟 2:下載並載入 med-flamingo 繼續預訓練權重
38    checkpoint_path = hf_hub_download("med-flamingo/med-flamingo", "model.pt")
39    print(f'Downloaded Med-Flamingo checkpoint to {checkpoint_path}')
40    model.load_state_dict(torch.load(checkpoint_path, map_location=device), strict=False)
41
42    processor = FlamingoProcessor(tokenizer, image_processor)
43
44    model = accelerator.prepare(model)
45    model.eval()
46
47    # 步驟 3:載入範例影像(7 張醫學影像:MRI/X光/病理切片混合)
48    demo_images = [Image.open(path) for path in image_paths]
49
50    # 步驟 4:組裝 few-shot in-context prompt
51    #   前 6 組是「範例」(image + question + answer),最後一組只給 question 要模型接生答案
52    prompt = (
53        "You are a helpful medical assistant. You are being provided with images, "
54        "a question about the image and an answer. Follow the examples and answer "
55        "the last question. "
56        "<image>Question: What is/are the structure near/in the middle of the brain? Answer: pons.<|endofchunk|>"
57        "<image>Question: Is there evidence of a right apical pneumothorax on this chest x-ray? Answer: yes.<|endofchunk|>"
58        "<image>Question: Is/Are there air in the patient's peritoneal cavity? Answer: no.<|endofchunk|>"
59        "<image>Question: Does the heart appear enlarged? Answer: yes.<|endofchunk|>"
60        "<image>Question: What side are the infarcts located? Answer: bilateral.<|endofchunk|>"
61        "<image>Question: Which image modality is this? Answer: mr flair.<|endofchunk|>"
62        "<image>Question: Where is the largest mass located in the cerebellum? Answer:"
63    )
64
65    # 步驟 5:前處理 —— 影像轉張量並擴增為 Flamingo 要求的 5 維格式
66    pixels = processor.preprocess_images(demo_images)
67    pixels = repeat(pixels, 'N c h w -> b N T c h w', b=1, T=1)
68    tokenized_data = processor.encode_text(prompt)
69
70    # 步驟 6:生成
71    generated_text = model.generate(
72        vision_x=pixels.to(device),
73        lang_x=tokenized_data["input_ids"].to(device),
74        attention_mask=tokenized_data["attention_mask"].to(device),
75        max_new_tokens=10,
76    )
77    response = processor.tokenizer.decode(generated_text[0])
78    response = clean_generation(response)
79    print(f'{response=}')
80
81
82if __name__ == "__main__":
83    main()

關鍵維度說明pixelspreprocess_images 出來的形狀是 (N, C, H, W)(N 張圖),經 einops.repeat 擴增成 (b, N, T, C, H, W),其中 b 是 batch size、T 是每張圖的「畫格數」(Flamingo 原始設計支援影片輸入,T 對應影格數,靜態圖片固定 T=1)。這個 5 維張量格式是 OpenFlamingo API 的硬性要求,也是最容易在自行改寫程式碼時出錯的地方。

5.2 進階用法:替換為自己的醫學影像與問題

 1# 假設你想用自己的一組範例(少量 few-shot exemplars)搭配新問題做推論
 2from PIL import Image
 3
 4# 準備你自己的範例(建議 4-8 組,過多會超出 context 長度且效果未必更好)
 5my_images = [
 6    Image.open("my_data/chest_xray_01.jpg"),
 7    Image.open("my_data/chest_xray_02.jpg"),
 8    Image.open("my_data/chest_xray_query.jpg"),  # 最後一張是要問的目標影像
 9]
10
11my_prompt = (
12    "You are a helpful medical assistant. "
13    "<image>Question: Is there cardiomegaly on this chest x-ray? Answer: no.<|endofchunk|>"
14    "<image>Question: Is there cardiomegaly on this chest x-ray? Answer: yes.<|endofchunk|>"
15    "<image>Question: Is there cardiomegaly on this chest x-ray? Answer:"
16)
17
18pixels = processor.preprocess_images(my_images)
19pixels = repeat(pixels, 'N c h w -> b N T c h w', b=1, T=1)
20tokenized_data = processor.encode_text(my_prompt)
21
22generated_text = model.generate(
23    vision_x=pixels.to(device),
24    lang_x=tokenized_data["input_ids"].to(device),
25    attention_mask=tokenized_data["attention_mask"].to(device),
26    max_new_tokens=15,       # 若預期答案較長(如描述性回答),可調高
27    num_beams=3,             # 可加入 beam search 提升生成品質(demo.py 原版未使用,屬進階調整)
28)
29response = clean_generation(processor.tokenizer.decode(generated_text[0]))
30print(response)

參數調整建議

  • max_new_tokens:預設 10,適合「yes/no」或單詞答案;若要模型生成完整句子描述(如「請描述這張 X 光片的異常」),建議調高到 50-100
  • num_beams:demo.py 未設定(預設貪婪解碼 greedy decoding),若要更穩定的輸出可加 beam search,但推論時間會等比增加
  • cross_attn_every_n_layers:這是建模型時的參數,必須與下載的 checkpoint 訓練時一致(med-flamingo 釋出的權重固定用 4),不可任意更改

5.3 實際應用場景展示:批次處理多張影像

 1"""
 2批次推論範例:對一批醫學影像分別做同一種問題的 VQA
 3(demo.py 原版僅支援單筆推論,此範例展示如何包裝成批次迴圈)
 4"""
 5import glob
 6
 7def batch_medical_vqa(model, processor, device, few_shot_prefix, image_dir, question_template):
 8    results = {}
 9    for img_path in glob.glob(f"{image_dir}/*.jpg"):
10        image = Image.open(img_path)
11        prompt = few_shot_prefix + f"<image>Question: {question_template} Answer:"
12
13        pixels = processor.preprocess_images([image])
14        pixels = repeat(pixels, 'N c h w -> b N T c h w', b=1, T=1)
15        tokenized_data = processor.encode_text(prompt)
16
17        generated_text = model.generate(
18            vision_x=pixels.to(device),
19            lang_x=tokenized_data["input_ids"].to(device),
20            attention_mask=tokenized_data["attention_mask"].to(device),
21            max_new_tokens=10,
22        )
23        response = clean_generation(processor.tokenizer.decode(generated_text[0]))
24        results[img_path] = response
25        print(f"{img_path}: {response}")
26
27    return results
28
29# 使用範例
30few_shot_prefix = (
31    "You are a helpful medical assistant. "
32    "<image>Question: Which image modality is this? Answer: chest x-ray.<|endofchunk|>"
33)
34results = batch_medical_vqa(
35    model, processor, device,
36    few_shot_prefix=few_shot_prefix,
37    image_dir="../img",
38    question_template="Which image modality is this?"
39)

注意:這是基於官方 API 合理推導的擴充範例,非 repo 內建功能——demo.py 本身只示範單筆推論,批次化需自行實作(並注意顯存管理,逐張處理比一次塞進大 batch 更穩妥)。

5.4 包裝成簡易本地 API(服務化雛形)

官方 demo 每次執行都要重新載入 7B 模型與 checkpoint,耗時甚長。若要在本地做多次互動測試,建議把模型載入與推論邏輯分離,模型只載一次、常駐記憶體:

 1"""
 2simple_server.py —— 用 FastAPI 包裝 med-flamingo 為本地 REST 服務(雛形)
 3需額外安裝:pip install fastapi uvicorn
 4"""
 5from fastapi import FastAPI, UploadFile, Form
 6from PIL import Image
 7import torch, io
 8from huggingface_hub import hf_hub_download
 9from open_flamingo import create_model_and_transforms
10from accelerate import Accelerator
11from einops import repeat
12import sys
13sys.path.append('scripts')
14from src.utils import FlamingoProcessor
15from demo_utils import clean_generation
16
17app = FastAPI()
18
19# --- 模型只在服務啟動時載入一次 ---
20accelerator = Accelerator()
21device = accelerator.device
22llama_path = "models/llama-7b-hf"
23
24model, image_processor, tokenizer = create_model_and_transforms(
25    clip_vision_encoder_path="ViT-L-14",
26    clip_vision_encoder_pretrained="openai",
27    lang_encoder_path=llama_path,
28    tokenizer_path=llama_path,
29    cross_attn_every_n_layers=4,
30)
31checkpoint_path = hf_hub_download("med-flamingo/med-flamingo", "model.pt")
32model.load_state_dict(torch.load(checkpoint_path, map_location=device), strict=False)
33processor = FlamingoProcessor(tokenizer, image_processor)
34model = accelerator.prepare(model)
35model.eval()
36
37
38@app.post("/vqa")
39async def vqa(image: UploadFile, few_shot_prefix: str = Form(...), question: str = Form(...)):
40    img = Image.open(io.BytesIO(await image.read()))
41    prompt = few_shot_prefix + f"<image>Question: {question} Answer:"
42
43    pixels = processor.preprocess_images([img])
44    pixels = repeat(pixels, 'N c h w -> b N T c h w', b=1, T=1)
45    tokenized_data = processor.encode_text(prompt)
46
47    generated_text = model.generate(
48        vision_x=pixels.to(device),
49        lang_x=tokenized_data["input_ids"].to(device),
50        attention_mask=tokenized_data["attention_mask"].to(device),
51        max_new_tokens=15,
52    )
53    response = clean_generation(processor.tokenizer.decode(generated_text[0]))
54    return {"answer": response}
55
56# 啟動:uvicorn simple_server:app --host 0.0.0.0 --port 8000

這個雛形展示了「模型載入」與「請求處理」分離的基本模式——這是任何要把研究型 demo 腳本轉為可重複呼叫服務時的第一步,也是本教學認為對使用者最實用的擴充方向之一(repo 官方並未提供這類服務化程式碼)。


6. 進階應用場景 (Advanced Use Cases)

6.1 真實世界應用案例

  • 放射科報告輔助初篩:用少量院內既有報告作為 few-shot 範例,讓模型對新影像生成初步印象描述,供放射科醫師參考校對(不可作為最終診斷依據,屬輔助工具定位)。
  • 醫學教育互動問答:教科書圖片配上臨床問答,訓練醫學生辨識影像特徵,med-flamingo 可作為互動式練習題生成/評分的後端。
  • 多模態病歷摘要:結合病歷文字與影像,用 in-context learning 方式生成結構化摘要,避免針對每個科別另外訓練專用模型。

6.2 與其他 snap-stanford 工具的整合

med-flamingo 本身不含知識圖譜或圖神經網路元件,但可以與 SNAP 實驗室的其他生醫工具組成管線:例如先用生醫知識圖譜工具抽取影像對應的疾病本體 (ontology) 資訊,作為 few-shot prompt 的補充背景知識,再交給 med-flamingo 做最終問答生成——這種「知識圖譜提供結構化背景 + 多模態模型做生成」的組合,是近期 RAG (Retrieval-Augmented Generation; 檢索增強生成) 架構的常見模式。

6.3 效能調校與最佳實踐

  • 量化推論:requirements.txt 已包含 bitsandbytes,可透過 load_in_8bit=True 這類參數(需自行修改 create_model_and_transforms 呼叫或載入後處理)大幅降低顯存需求,代價是推論速度與精度略有下降。
  • Prompt 長度管理:few-shot 範例數量與 Llama-7B 的 context window 相關,範例過多會拉長 prompt token 數,需注意不超過模型上限(Llama v1 原生 context 為 2048 tokens)。
  • 批次化與快取:官方 demo 未做 KV cache 復用優化;若要服務化部署,建議包裝成常駐 process,避免每次請求都重新載入 7B 模型(載入本身就要數十秒到數分鐘)。
  • 環境隔離:由於 requirements.txt 鎖定了 2023 年中的特定 git commit(transformersaccelerate 均指向特定 commit hash 而非 release 版本),務必使用獨立虛擬環境,避免與其他專案的套件版本衝突。

6.4 部署架構考量

若要把 med-flamingo 從「研究 demo」升級為「可被多人使用的內部工具」,以下是需要額外補齊的工程面向:


flowchart TB
    subgraph client["請求端"]
        u1["醫學研究人員"]
        u2["內部工具/儀表板"]
    end

    subgraph gateway["服務閘道層(需自行搭建)"]
        auth["身分驗證/授權"]
        queue["請求佇列
(避免多請求同時佔用單張GPU)"] rate["流量限制"] end subgraph inference["推論服務(本教學 5.4 節雛形延伸)"] model_mem["常駐記憶體的模型
(避免每次重新載入)"] batch_infer["動態批次化
(可選,提升吞吐量)"] end subgraph monitor["監控與稽核(醫療場景必要)"] log["請求/回應日誌
(含影像雜湊避免直存病人影像)"] audit["人工覆核佇列
(高風險回應標記)"] end u1 --> auth u2 --> auth auth --> queue --> rate --> model_mem model_mem --> batch_infer model_mem --> log --> audit style monitor fill:#3d2b2b,stroke:#ff8888,color:#fff

特別提醒:若部署場景涉及真實病患影像資料,務必額外考量法規遵循(如 HIPAA、當地個資法)、影像去識別化 (de-identification)、以及模型輸出的免責聲明(med-flamingo 目前僅為研究原型,未經臨床驗證,不應直接用於臨床決策)。這些都超出 repo 本身範疇,需使用單位自行建置。


7. 與 AIKT 的關聯分析 (AIKT Integration Analysis)

7.1 此專案可作為 AIKT 的資料來源或分析工具嗎?

可以,但角色是「被納管的知識素材」而非「編排引擎」。med-flamingo 是一個垂直領域推論模型,本身不提供 CLI 介面、不產出結構化文字報告,因此它不適合直接作為 AIKT 某個 Layer 的執行後端;但它作為研究素材,非常適合被 L2(ai-gh-save)收錄、被 L12(gh-tutorial-qd)製作教學(也就是本文件正在做的事),以及被 L18/L19(research-pipeline-v2 / tu-plan-generator)當作生醫 AI 技術地景 (technology landscape) 調研的一筆資料點。

7.2 AIKT 哪些 Layer 可以包裝或編排此專案

Layer可行整合方式
L2 ai-gh-save已完成:本教學即由 gh-tutorial-qd 對 gh-save 收錄的 repo metadata 進行深度分析產出
L9 paper-searchmed-flamingo 論文 (arXiv:2307.15189) 可被收錄進論文搜尋索引,與 Flamingo/OpenFlamingo 原始論文建立引用關係圖
L18 research-pipeline-v2若研究主題涉及「醫療多模態基礎模型現況調查」,可將 med-flamingo 列為技術路線之一,與其他 Med-VQA 方案(如 LLaVA-Med)比較分析
L19 tu-plan-generator若藥物開發計畫涉及影像生物標記 (imaging biomarker) 分析,可評估 med-flamingo 類技術是否適用於臨床試驗影像資料的初步標註輔助(需極謹慎評估法規與驗證要求)

7.3 潛在整合場景與價值

最實際的整合場景是:AIKT 作為「知識管理與研究自動化中樞」,負責發現、摘要、比較像 med-flamingo 這樣的技術方案,而不是執行它。也就是說,若使用者要做「醫療影像 AI 技術盡調 (due diligence)」,可以透過 L22(company-intel,雖名為公司盡調但方法論可延伸至技術方案盡調)或 L18 研究管線,將 med-flamingo 的架構特性、資源需求、授權風險(無明確 LICENSE)系統性地整理進技術評估報告,供後續決策參考。


flowchart LR
    subgraph aikt["AIKT 系統"]
        l2["L2 ai-gh-save
收錄 repo metadata"] l9["L9 paper-search
收錄 arXiv:2307.15189"] l12["L12 gh-tutorial-qd
本教學文件"] l18["L18 research-pipeline-v2
Med-VQA 技術地景調研"] graph["L4 graphify
知識圖譜索引"] end mf["med-flamingo
(snap-stanford)"] of["OpenFlamingo
(依賴套件)"] fl["Flamingo 論文
(DeepMind)"] mf --> l2 --> graph mf --> l9 --> graph mf --> l12 of -.引用關係.-> l9 fl -.引用關係.-> l9 l9 --> l18 style aikt fill:#1e2a1e,stroke:#88cc88,color:#fff

8. 優缺點與生態系定位 (Strengths, Limitations & Ecosystem Position)

8.1 核心優勢與創新點

  • 技術路線清楚且可驗證:把成熟的 Flamingo/OpenFlamingo 架構套用到醫療領域,而非重新發明架構,降低了複現風險,論文與 checkpoint 均公開釋出(透過 Hugging Face Hub)。
  • Few-shot 範式減少對大規模標註資料的依賴:對醫療這種標註資料稀缺的領域尤其實用。
  • 模組化的 Processor 設計:雖然目前只有一個實作,但介面設計為未來替換視覺/語言骨幹留了餘地。
  • 釋出即可跑的最小範例:不像許多論文 repo 只放模型架構程式碼卻不給可執行 demo,med-flamingo 提供了完整、可直接執行的端到端範例(含真實醫學影像)。

8.2 目前限制與改進空間

  • 開發停滯:README 寫著「More updates to follow soon」,但截至最後一次更新(2026-07-09),repo 內容仍是最初的最小 demo,沒有訓練程式碼、評估腳本、資料前處理管線,也沒有完整的 benchmark 結果重現流程。對想深入研究或二次開發的使用者而言,這是最大的落差。
  • 綁定過時的 Llama-7B v1:截至本教學撰寫時,業界已有更新的 Llama 2/3 系列,Llama-7B v1 且需要 decapoda-research 這個非官方轉存版本,安裝過程繁瑣且需手動修正 tokenizer 設定,對新手不友善。
  • 授權不明確:repo 未附 LICENSE 檔案(licenseInfo 為空),這對想要商業化應用或衍生開發的使用者是重大風險,任何使用前應先確認實際授權條款(通常需查閱論文或聯繫作者)。
  • 缺乏完整基準測試腳本:README 沒有提供在標準 Med-VQA benchmark(VQA-RAD、PathVQA 等)上重現論文數字的腳本,使用者無法自行驗證論文報告的效能。
  • 依賴套件版本鎖定過死transformersaccelerate 鎖定特定 2023 年 commit hash,隨著時間推移,這些 commit 可能因上游倉庫變動而難以復現安裝。

8.3 與同領域工具的比較

專案架構基礎開放程度特點
med-flamingoOpenFlamingo/Flamingo僅釋出 demo + checkpoint,無訓練程式碼Few-shot in-context learning,領域繼續預訓練
LLaVA-MedLLaVA (CLIP + Vicuna)相對完整(含訓練腳本)走 instruction-tuning 路線,而非 in-context few-shot
BiomedCLIPCLIP 架構開放模型權重專注影像-文字對比學習,非生成式問答
通用 GPT-4V 類商用模型專有架構封閉 API零樣本能力強,但無法針對機構私有資料做輕量客製

med-flamingo 在這個光譜中的定位是「開放、輕量、但生態不完整」的學術原型 (research prototype):適合作為研究起點或教學範例,但要用於實際生產環境,需要使用者自行補齊訓練管線、評估工具與授權釐清等工作。

8.4 適用場景建議

  • 適合:學術研究複現、醫療 AI 技術教學展示、少樣本學習方法論探索、作為更大型系統中的一個實驗性推論模組
  • 不適合:直接用於臨床決策系統、需要嚴謹授權合規的商業產品、要求高可靠性 SLA 的生產服務(缺乏維護與測試覆蓋)
  • 建議搭配:若要落地應用,建議搭配更新的語言模型骨幹重新訓練(而非沿用 Llama-7B v1)、補上完整評估管線,並優先釐清商業使用的授權邊界

附錄:常見問題 FAQ

Q1:可以不用 GPU,純 CPU 跑這個模型嗎? 技術上可以(Accelerator(cpu=True)),但 7B 參數語言模型在 CPU 上做自迴歸生成非常緩慢(單次推論可能需要數分鐘到數十分鐘),僅適合功能驗證,不適合互動式使用。

Q2:demo.py 裡的 <|endofchunk|> 是什麼?可以自己改嗎? 這是 Flamingo/OpenFlamingo 訓練時定義的特殊分隔 token,用來標記一組「範例」的結束。這個 token 必須是模型 tokenizer 詞表中原本就存在、且在訓練 checkpoint 時就有意義的 token,不建議自行更改成其他字串,否則模型無法正確辨識範例邊界。

Q3:可以用 med-flamingo 處理病理切片全片影像 (Whole Slide Image; WSI) 嗎? 不建議直接使用。WSI 通常解析度極高(可達數萬 x 數萬像素),CLIP 視覺編碼器的輸入解析度遠低於此(通常是 224x224 或 336x336),直接餵入會嚴重損失病理細節。若要處理 WSI,需要先透過切片 (patch) 策略切成小圖,再考慮如何整合多個 patch 的資訊,這已超出本 repo 的示範範疇。

Q4:為什麼我跑出來的答案跟論文報告的效果差很多? 可能原因包括:(1) few-shot 範例的選擇與排列順序會顯著影響生成品質,論文可能使用了經過篩選/檢索的範例組合,而非固定的 7 組範例;(2) max_new_tokens、beam search 等生成參數設定不同;(3) 本 repo 釋出的僅是最小 demo,並非論文所有實驗設定的完整重現。若需要嚴謹重現論文數字,需要向作者確認完整實驗設定或等待後續程式碼釋出。

Q5:這個模型支援中文醫學問答嗎? Llama-7B v1 的預訓練語料以英文為主,med-flamingo 的繼續預訓練語料(教科書圖說、PMC 論文)同樣以英文醫學文獻為主,因此對中文輸入的理解與生成能力預期遠弱於英文,不建議直接用於中文醫療場景,除非額外針對中文醫學語料做繼續預訓練。

Q6:模型會不會「幻覺」出不存在的醫學診斷? 會。這是所有生成式醫療 AI 共通的風險,few-shot in-context learning 並不能消除幻覺問題,只是提供了範例引導。任何輸出都應視為「草稿」而非「結論」,必須有具備專業資格的人員複核,這也是 8.4 節強調不適合直接用於臨床決策的原因。

Q7:可以把 Llama-7B 換成更新的模型(如 Llama 2/3)嗎? 理論上 OpenFlamingo 架構本身支援替換語言模型骨幹(只要改 lang_encoder_path),但 med-flamingo 釋出的 model.pt針對 Llama-7B v1 的詞表與隱藏層維度訓練的 cross-attention 權重,直接換成 Llama 2/3(詞表大小、隱藏維度均不同)會導致權重形狀不匹配,無法直接載入。若要用更新的語言模型,必須重新做繼續預訓練,而非單純替換路徑參數。


本教學文件由 AIKT (AI Knowledge Template) 系統的 gh-tutorial-qd Layer 自動生成,資料來源為 GitHub API 直接查詢 snap-stanford/med-flamingo repo 之 README、目錄結構與原始碼檔案內容(2026-07-13 查詢版本)。