學(xué)圖像報(bào)告生成系統(tǒng):從影像到結(jié)構(gòu)化結(jié)論的完整鏈路)
簡(jiǎn)介本資源為基于Python實(shí)現(xiàn)的醫(yī)學(xué)圖像報(bào)告生成系統(tǒng)與模型面向計(jì)算機(jī)、人工智能、通信工程、自動(dòng)化、電子信息等專(zhuān)業(yè)的在校學(xué)生、教師及企業(yè)員工尤其適合作為畢業(yè)設(shè)計(jì)、課程設(shè)計(jì)、作業(yè)或項(xiàng)目初期立項(xiàng)演示的參考素材也適合具備一定基礎(chǔ)的學(xué)習(xí)者進(jìn)階研究。壓縮包共177個(gè)文件以137個(gè)json標(biāo)注數(shù)據(jù)、27個(gè)hdf5圖像特征文件、10個(gè)Python源碼腳本為主另含gitignore、md說(shuō)明與gitattributes等配置整體約609KB結(jié)構(gòu)緊湊便于快速上手。資源圍繞醫(yī)學(xué)圖像與報(bào)告生成的完整流程展開(kāi)涵蓋數(shù)據(jù)組織、模型構(gòu)建與訓(xùn)練推理等環(huán)節(jié)代碼均經(jīng)測(cè)試運(yùn)行成功答辯評(píng)審平均分達(dá)96分已有185人學(xué)習(xí)關(guān)注。讀者可據(jù)此理解醫(yī)學(xué)圖像報(bào)告生成的技術(shù)路線掌握數(shù)據(jù)處理與模型實(shí)現(xiàn)方法并在此基礎(chǔ)上修改擴(kuò)展實(shí)現(xiàn)個(gè)性化功能適合作為學(xué)習(xí)參考與項(xiàng)目原型。1. 醫(yī)學(xué)圖像報(bào)告生成系統(tǒng)從影像到結(jié)構(gòu)化結(jié)論的那條鏈路放射科醫(yī)生一天要寫(xiě)幾十份報(bào)告胸部 X 光、CT 平掃、MRI 增強(qiáng)每份都得盯著片子逐條描述病灶位置、大小、密度、邊界、伴隨征象。真正耗時(shí)的不是判斷而是把判斷轉(zhuǎn)成規(guī)范文字?;?Python 實(shí)現(xiàn)醫(yī)學(xué)圖像報(bào)告生成系統(tǒng)與模型要解決的就是這條從像素到文字的鏈路——輸入一張或一組醫(yī)學(xué)影像輸出一段符合報(bào)告書(shū)寫(xiě)習(xí)慣的描述文本。它適合兩類(lèi)人一類(lèi)是想把深度學(xué)習(xí)落到臨床輔助場(chǎng)景的算法工程師另一類(lèi)是手里有影像數(shù)據(jù)、想驗(yàn)證自動(dòng)化報(bào)告可行性的研發(fā)同學(xué)。核心難點(diǎn)不在「能不能生成一句話」而在生成的內(nèi)容是否貼合影像所見(jiàn)、術(shù)語(yǔ)是否規(guī)范、結(jié)構(gòu)是否可控。這一篇按我實(shí)際搭過(guò)的路徑把數(shù)據(jù)、模型、訓(xùn)練、推理、避坑一次講透能照著復(fù)現(xiàn)最小可用版本。2. 報(bào)告生成任務(wù)拆解為什么它不是簡(jiǎn)單的圖像描述2.1 醫(yī)學(xué)報(bào)告生成和通用 image caption 的本質(zhì)差別通用圖像描述任務(wù)里模型看到一只貓說(shuō)「一只貓坐在沙發(fā)上」就算對(duì)。醫(yī)學(xué)報(bào)告不行。同樣一句「右肺上葉見(jiàn)結(jié)節(jié)」位置、肺葉、征象三個(gè)要素錯(cuò)一個(gè)臨床意義就變了。差別體現(xiàn)在三處一是術(shù)語(yǔ)封閉報(bào)告用詞高度收斂正常/異常、部位、征象都有固定表達(dá)詞表比開(kāi)放域小得多二是結(jié)構(gòu)固定一份報(bào)告通常分「檢查技術(shù)、影像所見(jiàn)、診斷意見(jiàn)」幾段段落之間有邏輯依賴(lài)三是細(xì)粒度對(duì)齊文字里的每個(gè)發(fā)現(xiàn)都要能對(duì)應(yīng)到影像上的某個(gè)區(qū)域否則就是幻覺(jué)。這決定了模型不能只做「編碼器 解碼器」的通用套路。常見(jiàn)做法是把任務(wù)建成 image-to-text 的條件生成圖像編碼器抽視覺(jué)特征文本解碼器自回歸生成報(bào)告。但要在醫(yī)學(xué)場(chǎng)景站住得額外處理結(jié)構(gòu)約束和術(shù)語(yǔ)約束。我一般會(huì)把「影像所見(jiàn)」和「診斷意見(jiàn)」分開(kāi)建模前者偏描述、后者偏結(jié)論兩者共享視覺(jué)特征但用不同的解碼策略。2.2 數(shù)據(jù)從哪來(lái)、怎么組織成可訓(xùn)練的樣本公開(kāi)數(shù)據(jù)里IU X-Ray、MIMIC-CXR 是報(bào)告生成最常用的兩個(gè)來(lái)源前者規(guī)模小適合跑通流程后者規(guī)模大但需要申請(qǐng)權(quán)限。數(shù)據(jù)組織的最小單元是「一張影像 對(duì)應(yīng)報(bào)告文本」。報(bào)告文本要先做清洗去掉醫(yī)院抬頭、患者隱私字段、檢查編號(hào)只保留影像所見(jiàn)和診斷部分。然后做術(shù)語(yǔ)歸一化把「未見(jiàn)異?!埂肝匆?jiàn)明顯異?!埂鸽p肺紋理清晰」這類(lèi)同義表達(dá)映射到統(tǒng)一模板否則模型會(huì)在同義句上浪費(fèi)容量。樣本構(gòu)造上我習(xí)慣把一份報(bào)告拆成句子級(jí)樣本每句配整張影像。這樣能擴(kuò)大樣本量也方便后面做句子級(jí)對(duì)齊。缺點(diǎn)是丟掉了句間順序信息所以訓(xùn)練時(shí)再加一個(gè)段落位置編碼補(bǔ)回來(lái)。下面是把原始報(bào)告清洗成訓(xùn)練樣本的核心邏輯import re # 需要剔除的隱私與行政字段關(guān)鍵詞 PRIVACY_PATTERNS [ r檢查號(hào)[:]?\s*\S, r住院號(hào)[:]?\s*\S, r患者姓名[:]?\s*\S, r報(bào)告醫(yī)師[:]?\s*\S, ] # 同義表達(dá)歸一化映射按自己數(shù)據(jù)集的用詞習(xí)慣擴(kuò)充 NORMALIZE_MAP { 未見(jiàn)明顯異常: 未見(jiàn)異常, 雙肺紋理清晰: 雙肺紋理正常, 心影不大: 心影大小正常, } def clean_report(text: str) - str: for pat in PRIVACY_PATTERNS: text re.sub(pat, , text) # 合并多余空白 text re.sub(r\s, , text).strip() for src, dst in NORMALIZE_MAP.items(): text text.replace(src, dst) return text def split_sections(text: str): # 按「影像所見(jiàn)」「診斷意見(jiàn)」切段缺失時(shí)整段歸入所見(jiàn) findings, impression , if 診斷意見(jiàn) in text: parts text.split(診斷意見(jiàn)) findings parts[0].replace(影像所見(jiàn), ).strip() impression parts[1].strip() else: findings text.replace(影像所見(jiàn), ).strip() return findings, impression這段代碼做三件事去隱私、歸一化術(shù)語(yǔ)、切分報(bào)告結(jié)構(gòu)。PRIVACY_PATTERNS要按你數(shù)據(jù)集的真實(shí)字段名補(bǔ)漏一個(gè)就可能把敏感信息帶進(jìn)訓(xùn)練集。NORMALIZE_MAP是術(shù)語(yǔ)收斂的關(guān)鍵映射表越貼近你數(shù)據(jù)的實(shí)際用詞后面模型學(xué)到的表達(dá)越干凈。split_sections把報(bào)告拆成所見(jiàn)和意見(jiàn)兩段為后面雙分支解碼做準(zhǔn)備。參數(shù)上沒(méi)什么玄學(xué)重點(diǎn)是映射表要人工過(guò)一遍別指望自動(dòng)聚類(lèi)能替代領(lǐng)域判斷。2.3 視覺(jué)編碼器選型CNN 還是 ViT影像編碼器兩條路CNN backboneResNet、DenseNet和 ViT 系。CNN 在小數(shù)據(jù)集上更穩(wěn)IU X-Ray 這種幾千張的量級(jí)ResNet-50 預(yù)訓(xùn)練權(quán)重微調(diào)通常比 ViT 收斂快、過(guò)擬合輕。ViT 在數(shù)據(jù)量上萬(wàn)后開(kāi)始反超全局注意力對(duì)多發(fā)病灶的關(guān)聯(lián)建模更好。我的經(jīng)驗(yàn)是數(shù)據(jù)少于一萬(wàn)張先用 CNN 打底跑通數(shù)據(jù)夠大再換 ViT 或混合結(jié)構(gòu)。不管選哪個(gè)輸出都要轉(zhuǎn)成序列特征喂給解碼器。CNN 輸出的是(B, C, H, W)特征圖需要 flatten 成(B, H*W, C)并加位置編碼ViT 本身輸出就是序列直接接解碼器。這里有個(gè)容易翻車(chē)的點(diǎn)醫(yī)學(xué)影像分辨率高直接 resize 到 224 會(huì)丟小結(jié)節(jié)常見(jiàn)做法是保留較高分辨率如 512或做多尺度特征融合。分辨率提上去顯存吃得快batch size 要相應(yīng)降梯度累積補(bǔ)回來(lái)。3. 模型搭建編碼器、解碼器與結(jié)構(gòu)約束怎么落地3.1 編碼器-解碼器骨架的最小實(shí)現(xiàn)骨架用 PyTorch 搭編碼器拿預(yù)訓(xùn)練 ResNet解碼器用 Transformer 解碼塊。整體是標(biāo)準(zhǔn)的條件生成結(jié)構(gòu)但輸出層要接術(shù)語(yǔ)約束。先看骨架import torch import torch.nn as nn from torchvision.models import resnet50, ResNet50_Weights class ImageEncoder(nn.Module): def __init__(self, feat_dim2048, out_dim512): super().__init__() backbone resnet50(weightsResNet50_Weights.DEFAULT) # 去掉最后的分類(lèi)頭保留卷積特征 self.cnn nn.Sequential(*list(backbone.children())[:-2]) self.proj nn.Linear(feat_dim, out_dim) def forward(self, x): feat self.cnn(x) # (B, 2048, H/32, W/32) B, C, H, W feat.shape feat feat.flatten(2).transpose(1, 2) # (B, H*W, C) return self.proj(feat) # (B, H*W, out_dim) class ReportDecoder(nn.Module): def __init__(self, vocab_size, d_model512, nhead8, num_layers4): super().__init__() self.embed nn.Embedding(vocab_size, d_model) self.pos nn.Embedding(512, d_model) # 最大句長(zhǎng) 512 layer nn.TransformerDecoderLayer(d_model, nhead, batch_firstTrue) self.decoder nn.TransformerDecoder(layer, num_layers) self.fc nn.Linear(d_model, vocab_size) def forward(self, memory, tgt_ids): B, L tgt_ids.shape pos_ids torch.arange(L, devicetgt_ids.device).unsqueeze(0) x self.embed(tgt_ids) self.pos(pos_ids) # 因果掩碼防止看到未來(lái) token mask nn.Transformer.generate_square_subsequent_mask(L).to(tgt_ids.device) out self.decoder(x, memory, tgt_maskmask) return self.fc(out)ImageEncoder把 ResNet 的全局池化和全連接去掉保留空間特征再投影到解碼器維度。flatten(2).transpose(1,2)把特征圖變成序列這是 CNN 接 Transformer 的標(biāo)準(zhǔn)操作。ReportDecoder里位置編碼用可學(xué)習(xí) embedding句長(zhǎng)上限設(shè) 512醫(yī)學(xué)報(bào)告單句很少超過(guò)這個(gè)長(zhǎng)度。因果掩碼是關(guān)鍵訓(xùn)練時(shí)如果不加解碼器會(huì)偷看后面的詞loss 掉得飛快但推理時(shí)全崩這是血淚經(jīng)驗(yàn)。num_layers4是精度和顯存的折中數(shù)據(jù)量大可以加到 6。3.2 雙分支解碼所見(jiàn)和意見(jiàn)分開(kāi)生成前面把報(bào)告拆成了所見(jiàn)和意見(jiàn)模型上也對(duì)應(yīng)兩個(gè)解碼分支。共享編碼器輸出但各自有獨(dú)立的解碼器和輸出頭。這樣做的理由是所見(jiàn)偏描述性、用詞發(fā)散意見(jiàn)偏結(jié)論性、用詞收斂混在一起訓(xùn)會(huì)讓模型在兩種風(fēng)格間搖擺。實(shí)現(xiàn)上就是兩個(gè)ReportDecoder實(shí)例訓(xùn)練時(shí)分別算 loss 再加權(quán)求和。class DualBranchModel(nn.Module): def __init__(self, vocab_size, d_model512): super().__init__() self.encoder ImageEncoder(out_dimd_model) self.findings_dec ReportDecoder(vocab_size, d_model) self.impression_dec ReportDecoder(vocab_size, d_model) def forward(self, images, findings_ids, impression_ids): memory self.encoder(images) f_out self.findings_dec(memory, findings_ids) i_out self.impression_dec(memory, impression_ids) return f_out, i_out兩個(gè)分支的 loss 權(quán)重我一般設(shè)所見(jiàn) 0.6、意見(jiàn) 0.4。意見(jiàn)句短但臨床權(quán)重高權(quán)重太低模型會(huì)敷衍太高又會(huì)讓所見(jiàn)描述變粗糙。這個(gè)比例不是定死的按你數(shù)據(jù)里兩段的平均長(zhǎng)度和重要性調(diào)。如果意見(jiàn)段經(jīng)常是「未見(jiàn)異?!惯@種短句權(quán)重可以降到 0.3避免模型退化成只會(huì)輸出模板。3.3 術(shù)語(yǔ)約束與結(jié)構(gòu)約束的實(shí)現(xiàn)方式光靠數(shù)據(jù)學(xué)模型還是會(huì)冒出「左肺下葉」寫(xiě)成「左下肺葉」這類(lèi)不規(guī)范表達(dá)。約束手段有兩類(lèi)解碼時(shí)約束和訓(xùn)練時(shí)約束。解碼時(shí)用受限詞表把不在術(shù)語(yǔ)表里的 token 概率置零再采樣訓(xùn)練時(shí)加一個(gè)術(shù)語(yǔ)一致性 loss懲罰生成詞和標(biāo)準(zhǔn)術(shù)語(yǔ)的偏差。受限詞表實(shí)現(xiàn)簡(jiǎn)單、見(jiàn)效快適合先上def constrained_decode(logits, allowed_ids): # logits: (B, vocab), allowed_ids: set of token id mask torch.full_like(logits, float(-inf)) for tid in allowed_ids: mask[:, tid] 0.0 return logits maskallowed_ids是你術(shù)語(yǔ)表對(duì)應(yīng)的 token id 集合從訓(xùn)練集里統(tǒng)計(jì)高頻規(guī)范詞得到。注意別把詞表收得太死否則模型沒(méi)法生成「考慮炎性病變可能」這種帶不確定性的表達(dá)臨床上這類(lèi)措辭很重要。我的做法是分兩級(jí)核心術(shù)語(yǔ)部位、征象強(qiáng)制約束修飾詞可能、考慮、建議放開(kāi)。結(jié)構(gòu)約束靠模板前綴。生成時(shí)先喂一個(gè)「影像所見(jiàn)」的起始 token讓解碼器在固定上下文里續(xù)寫(xiě)段落邊界用特殊 token 控制。這樣輸出天然帶結(jié)構(gòu)后處理也省事。4. 訓(xùn)練與推理參數(shù)怎么設(shè)、指標(biāo)怎么看4.1 訓(xùn)練配置與關(guān)鍵超參訓(xùn)練用 teacher forcing輸入右移一位的標(biāo)簽序列算交叉熵。優(yōu)化器 AdamW學(xué)習(xí)率編碼器 1e-5、解碼器 1e-4編碼器是預(yù)訓(xùn)練權(quán)重學(xué)習(xí)率要低一個(gè)量級(jí)防止被沖垮。batch size 按顯存來(lái)512 分辨率下 8 到 16 比較現(xiàn)實(shí)不夠就用梯度累積。訓(xùn)練輪數(shù)別貪多醫(yī)學(xué)報(bào)告數(shù)據(jù)量有限10 到 20 輪就開(kāi)始過(guò)擬合早停看驗(yàn)證集 loss。import torch.optim as optim def build_optimizer(model, enc_lr1e-5, dec_lr1e-4): enc_params list(model.encoder.parameters()) dec_params list(model.findings_dec.parameters()) \ list(model.impression_dec.parameters()) return optim.AdamW([ {params: enc_params, lr: enc_lr}, {params: dec_params, lr: dec_lr}, ], weight_decay1e-4) # 訓(xùn)練循環(huán)核心片段 def train_step(model, batch, optimizer, criterion, device): images batch[image].to(device) f_ids batch[findings_ids].to(device) i_ids batch[impression_ids].to(device) f_out, i_out model(images, f_ids[:, :-1], i_ids[:, :-1]) loss_f criterion(f_out.reshape(-1, f_out.size(-1)), f_ids[:, 1:].reshape(-1)) loss_i criterion(i_out.reshape(-1, i_out.size(-1)), i_ids[:, 1:].reshape(-1)) loss 0.6 * loss_f 0.4 * loss_i optimizer.zero_grad() loss.backward() # 梯度裁剪防 RNN/Transformer 梯度爆炸 torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() return loss.item()build_optimizer給編碼器和解碼器分組學(xué)習(xí)率這是微調(diào)預(yù)訓(xùn)練模型的常規(guī)操作。訓(xùn)練循環(huán)里f_ids[:, :-1]是輸入、f_ids[:, 1:]是目標(biāo)錯(cuò)一位對(duì)齊。梯度裁剪閾值 1.0Transformer 訓(xùn)練不加這個(gè)偶爾會(huì)炸。loss 權(quán)重和前面雙分支對(duì)應(yīng)改權(quán)重就改這兩個(gè)系數(shù)。4.2 評(píng)估指標(biāo)BLEU 之外必須看的臨床指標(biāo)自動(dòng)指標(biāo)常用 BLEU-4、ROUGE-L、METEOR它們衡量 n-gram 重疊能反映文本流暢度但和臨床正確性關(guān)聯(lián)弱。一份報(bào)告 BLEU 很高可能只是套話多。所以我一定會(huì)補(bǔ)兩類(lèi)指標(biāo)一是臨床實(shí)體匹配率把報(bào)告里的部位、征象抽出來(lái)做集合比對(duì)二是錯(cuò)誤類(lèi)型統(tǒng)計(jì)人工抽 100 份看幻覺(jué)、漏報(bào)、術(shù)語(yǔ)錯(cuò)誤各占多少。指標(biāo)衡量什么參考閾值注意點(diǎn)BLEU-4n-gram 重疊0.10~0.20醫(yī)學(xué)報(bào)告普遍偏低別拿通用域標(biāo)準(zhǔn)套R(shí)OUGE-L最長(zhǎng)公共子序列0.30~0.45對(duì)長(zhǎng)句更敏感實(shí)體匹配率部位/征象命中0.70 以上需自建實(shí)體詞典幻覺(jué)率無(wú)中生有的發(fā)現(xiàn)越低越好人工抽檢自動(dòng)難測(cè)BLEU 在醫(yī)學(xué)報(bào)告上 0.15 左右就算能用了別看到 0.1 就以為模型廢了。實(shí)體匹配率才是能不能上臨床輔助的關(guān)鍵線低于 0.7 基本沒(méi)法用。4.3 推理階段beam search 與后處理推理用 beam searchbeam size 3 到 5。太大生成慢且容易出套話太小質(zhì)量不穩(wěn)。長(zhǎng)度懲罰設(shè) 1.0 到 1.2防止生成過(guò)短。生成完做后處理補(bǔ)全標(biāo)點(diǎn)、統(tǒng)一術(shù)語(yǔ)、按模板拼段落。def generate_report(model, image, tokenizer, beam4, max_len128): model.eval() memory model.encoder(image.unsqueeze(0)) # 簡(jiǎn)化版貪心實(shí)際用 beam search 庫(kù)或手寫(xiě) ids [tokenizer.bos_id] for _ in range(max_len): tgt torch.tensor([ids]) logits model.findings_dec(memory, tgt) next_id logits[0, -1].argmax().item() if next_id tokenizer.eos_id: break ids.append(next_id) return tokenizer.decode(ids[1:])這段是貪心版示意生產(chǎn)里換成 beam search。max_len按你數(shù)據(jù)里最長(zhǎng)句設(shè)128 夠覆蓋絕大多數(shù)報(bào)告句。后處理函數(shù)單獨(dú)寫(xiě)把「肺葉」類(lèi)術(shù)語(yǔ)再歸一化一遍模型偶爾還是會(huì)漏。5. 避坑與排查那些讓模型白訓(xùn)的細(xì)節(jié)5.1 現(xiàn)象loss 正常下降但生成全是重復(fù)詞原因通常是解碼器因果掩碼寫(xiě)錯(cuò)或位置編碼越界。掩碼沒(méi)加訓(xùn)練時(shí)模型偷看未來(lái) token學(xué)到的分布和推理不一致位置編碼表小于實(shí)際句長(zhǎng)超出部分索引報(bào)錯(cuò)或回繞。解決檢查generate_square_subsequent_mask是否傳入位置編碼表大小是否大于最大句長(zhǎng)。我踩過(guò)一次位置表設(shè) 256、數(shù)據(jù)里有 300 詞的報(bào)告訓(xùn)練不報(bào)錯(cuò)但生成亂套查了半天。5.2 現(xiàn)象驗(yàn)證集 BLEU 高但臨床實(shí)體匹配率低模型學(xué)會(huì)了報(bào)告的高頻套話比如「雙肺紋理清晰、心影大小正?!沟珱](méi)真正看影像。原因是數(shù)據(jù)里正常報(bào)告占比過(guò)高模型退化成輸出多數(shù)類(lèi)。解決對(duì)異常樣本過(guò)采樣或在 loss 里給異常實(shí)體加權(quán)。統(tǒng)計(jì)一下你數(shù)據(jù)里正常/異常比例超過(guò) 7:3 就得處理。5.3 現(xiàn)象顯存夠但訓(xùn)練中途 OOM多半是變長(zhǎng)序列沒(méi)做 padding 對(duì)齊或者注意力矩陣隨句長(zhǎng)平方增長(zhǎng)。解決按 batch 內(nèi)最大長(zhǎng)度動(dòng)態(tài) padding別用全局最大長(zhǎng)度句長(zhǎng)超過(guò) 512 考慮分段或換 Longformer 這類(lèi)稀疏注意力結(jié)構(gòu)。醫(yī)學(xué)報(bào)告單句一般不長(zhǎng)但所見(jiàn)段拼起來(lái)可能超切句訓(xùn)練更穩(wěn)。5.4 現(xiàn)象生成術(shù)語(yǔ)不規(guī)范同義混用訓(xùn)練數(shù)據(jù)本身術(shù)語(yǔ)就不統(tǒng)一模型只是忠實(shí)反映了噪聲。解決回到數(shù)據(jù)清洗把NORMALIZE_MAP補(bǔ)全再做一輪訓(xùn)練。別指望模型自己學(xué)會(huì)規(guī)范它只會(huì)學(xué)你喂的東西。這一步?jīng)]有捷徑映射表得人工過(guò)。5.5 現(xiàn)象推理速度慢到?jīng)]法批量跑自回歸解碼逐 token 生成長(zhǎng)報(bào)告耗時(shí)明顯。解決用 KV cache 緩存已計(jì)算的注意力鍵值避免每步重算beam search 的 beam 數(shù)別開(kāi)太大能批處理就批處理。KV cache 實(shí)現(xiàn)稍復(fù)雜但推理提速通常有兩三倍值得做。6. 進(jìn)階技巧用檢索增強(qiáng)壓住幻覺(jué)模型幻覺(jué)是報(bào)告生成最頭疼的問(wèn)題——影像上沒(méi)結(jié)節(jié)它寫(xiě)出「見(jiàn)小結(jié)節(jié)」。純生成模型靠參數(shù)記憶壓不住。我后來(lái)加了一層檢索增強(qiáng)建一個(gè)「影像特征到報(bào)告句」的向量庫(kù)生成時(shí)先檢索相似影像的歷史報(bào)告句作為額外上下文喂給解碼器讓模型在真實(shí)句子上做改寫(xiě)而不是憑空造。具體做法用編碼器輸出做影像向量存進(jìn)向量庫(kù)推理時(shí)檢索 top-k 相似樣本的報(bào)告句拼成提示前綴。這樣模型有參照幻覺(jué)率能降一截。向量庫(kù)用本地向量模型建別依賴(lài)外部服務(wù)臨床場(chǎng)景數(shù)據(jù)不能出內(nèi)網(wǎng)。import numpy as np class ReportRetriever: def __init__(self, dim512): self.vectors [] # 影像特征 self.reports [] # 對(duì)應(yīng)報(bào)告句 def add(self, feat: np.ndarray, report: str): self.vectors.append(feat / (np.linalg.norm(feat) 1e-8)) self.reports.append(report) def search(self, query: np.ndarray, topk3): q query / (np.linalg.norm(query) 1e-8) sims [float(q v) for v in self.vectors] idx np.argsort(sims)[::-1][:topk] return [self.reports[i] for i in idx]add時(shí)做 L2 歸一化檢索用余弦相似度這是向量檢索的標(biāo)準(zhǔn)做法。topk設(shè) 3 比較穩(wěn)太多會(huì)把不相關(guān)句子帶進(jìn)來(lái)干擾生成。檢索到的句子拼成前綴時(shí)加分隔符讓解碼器知道這是參考不是必須照抄。驗(yàn)證這套有沒(méi)有用別只看 BLEU重點(diǎn)看幻覺(jué)率。我一般抽 100 份生成報(bào)告人工標(biāo)「有無(wú)影像不支持的內(nèi)容」加檢索前后對(duì)比。經(jīng)驗(yàn)上幻覺(jué)率能降三到四成代價(jià)是推理多一次檢索、生成句偶爾偏保守。值不值得做看你對(duì)幻覺(jué)的容忍度——臨床輔助場(chǎng)景保守比激進(jìn)安全。這套系統(tǒng)我從數(shù)據(jù)清洗一路搭到檢索增強(qiáng)最大的教訓(xùn)是模型結(jié)構(gòu)不是瓶頸數(shù)據(jù)質(zhì)量和術(shù)語(yǔ)規(guī)范才是。花在清洗和映射表上的時(shí)間回報(bào)比調(diào)模型結(jié)構(gòu)高得多。另一個(gè)習(xí)慣是每次改完都抽 20 份生成結(jié)果肉眼過(guò)一遍自動(dòng)指標(biāo)會(huì)騙人眼睛不會(huì)。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取