:從數(shù)據(jù)到調(diào)參的完整指南)
簡介本資源面向醫(yī)學圖像分割方向的初學者與算法實踐者提供一套基于U-Net的眼底血管二分類分割完整方案解決從數(shù)據(jù)準備到模型推理的全流程落地問題。壓縮包共216個文件以182張png切片圖像、8個py腳本、1個pth權(quán)重文件及若干txt日志與xml配置為主整體約153.92MB數(shù)據(jù)集、代碼與訓練結(jié)果一并打包。訓練腳本支持0.5至1.5倍隨機縮放的多尺度訓練utils中的compute_gray函數(shù)可將mask灰度值寫入txt并自動定義U-Net輸出通道便于擴展多分割任務學習率采用cos衰減損失與IoU曲線、各類別IoU、recall、precision及全局像素準確率均保存在run_results與訓練日志中。僅訓練10個epochs全局像素準確度已達0.95miou為0.67加大epoch性能可進一步提升。推理時只需將待測圖像放入inference目錄并運行predict腳本無需額外參數(shù)。目前已有269人學習適合快速復現(xiàn)與二次開發(fā)。1. 眼底血管分割這套 Unet 資源拿到手先別急著訓練眼底血管分割是醫(yī)學圖像分割里最經(jīng)典的入門任務之一也是很多人從分類模型轉(zhuǎn)向像素級預測的第一站。你拿到的這份資源核心是一個基于 Unet 的眼底血管二分類分割項目附帶已經(jīng)切片好的數(shù)據(jù)集、完整可運行的訓練與推理代碼以及作者跑完 10 個 epoch 后的結(jié)果文件。作者給出的指標是全局像素準確率 0.95、miou 0.67這個數(shù)字在只訓練 10 輪的前提下算是相當能打——血管本身是細長結(jié)構(gòu)前景像素占比極低miou 能到 0.67 說明模型確實學到了血管的拓撲走向而不是靠猜背景刷準確率。這份資源適合三類人剛接觸醫(yī)學圖像分割、想找一個能直接跑通全流程的練手項目的人手里有自己的眼底數(shù)據(jù)、想拿這套代碼當訓練框架改的人以及需要一份帶訓練日志和權(quán)重文件、方便做對比實驗的從業(yè)者。代碼里已經(jīng)內(nèi)置了多尺度訓練、cos 學習率衰減、按 mask 灰度值自動定義輸出通道這些工程細節(jié)不是那種只貼一個 model.py 就號稱完整的半成品。下面按「資源是什么 → 怎么跑起來 → 參數(shù)怎么調(diào) → 坑在哪」的順序拆開講。2. 拆開資源包目錄結(jié)構(gòu)、數(shù)據(jù)格式與 Unet 輸出通道的自動推導2.1 目錄里到底有什么資源包解壓后根目錄下能看到.gitignore、u-net-me.iml這類工程配置文件以及一批以數(shù)字命名的 png 圖片84.png、65.png、63.png 等。這些數(shù)字命名的圖不是隨便放的通常是數(shù)據(jù)集里的樣本切片或者訓練過程中保存的中間可視化結(jié)果。真正干活的部分在代碼目錄里按功能拆成訓練、推理、工具函數(shù)三塊。你拿到手第一件事不是運行 train而是先把目錄結(jié)構(gòu)和數(shù)據(jù)流理清楚否則后面改路徑會改到懷疑人生。常見做法是保持作者原有的目錄層級不動只替換數(shù)據(jù)。因為 train 腳本里讀數(shù)據(jù)的路徑、run_results 的寫入路徑、inference 的讀取路徑都是相對路徑寫死的你一旦挪動文件位置就得同步改三處代碼容易漏。我一般會先跑一遍tree或者find . -maxdepth 2 -type d把結(jié)構(gòu)打印出來確認數(shù)據(jù)目錄、結(jié)果目錄、權(quán)重目錄各自在哪再動手。2.2 數(shù)據(jù)格式與 mask 灰度值的約定眼底血管分割的數(shù)據(jù)對是「原圖 mask」的形式。原圖是彩色眼底照mask 是單通道灰度圖血管區(qū)域為白色灰度值 255背景為黑色0。這份資源的關鍵設計在于 utils 里的compute_gray函數(shù)它會掃描 mask 目錄下所有灰度圖把出現(xiàn)過的灰度值收集起來寫進一個 txt 文本然后根據(jù)這個灰度值列表自動決定 Unet 最后一層輸出多少個 channel。這個設計的好處是你換一個多類分割任務時不用手動去改網(wǎng)絡輸出維度。比如你以后做視盤、視杯、血管三分類mask 里有 0、1、2 三種灰度值compute_gray掃完自動把輸出通道設成 3省掉一處最容易寫錯的硬編碼。代價是每次換數(shù)據(jù)集都要重新跑一次這個函數(shù)生成 txt不能直接復用舊的。# utils 中 compute_gray 的核心邏輯示意 import os import numpy as np from PIL import Image def compute_gray(mask_dir, save_txtgray_values.txt): gray_set set() for name in os.listdir(mask_dir): if not name.lower().endswith((.png, .jpg, .bmp)): continue mask np.array(Image.open(os.path.join(mask_dir, name)).convert(L)) gray_set.update(np.unique(mask).tolist()) gray_values sorted(gray_set) with open(save_txt, w) as f: f.write(,.join(str(g) for g in gray_values)) # 輸出通道數(shù) 灰度類別數(shù)二分類時通常為 2 return len(gray_values), gray_values邏輯說明函數(shù)遍歷 mask 目錄用convert(L)強制轉(zhuǎn)單通道避免有些 png 帶 alpha 通道導致np.unique多出無意義的值。gray_set去重后排序?qū)懳募祷刂道锏念悇e數(shù)直接喂給 Unet 的輸出層。參數(shù)上mask_dir指向你的 mask 文件夾save_txt建議放在工程根目錄方便 train 腳本讀取。注意二分類任務里 mask 只有 0 和 255 兩個值但如果你用交叉熵損失輸出通道要設成 2背景 血管別設成 1否則 softmax 維度對不上。2.3 多尺度訓練是怎么實現(xiàn)的train 腳本在送數(shù)據(jù)進網(wǎng)絡前會把圖像隨機縮放為設定尺寸的 0.5 到 1.5 倍之間。這是醫(yī)學圖像分割里很實用的一招血管粗細在不同放大倍率下差異明顯模型見過多種尺度后對小血管的召回會更好。實現(xiàn)上一般是在 Dataset 的__getitem__里生成一個隨機縮放因子用cv2.resize或torch.nn.functional.interpolate對圖像和 mask 同步縮放mask 必須用最近鄰插值否則灰度值會被插值成中間值破壞類別標簽。import random import cv2 import numpy as np def random_scale(img, mask, base_size512, scale_range(0.5, 1.5)): scale random.uniform(*scale_range) new_size int(base_size * scale) img cv2.resize(img, (new_size, new_size), interpolationcv2.INTER_LINEAR) # mask 必須用最近鄰保住 0/255 的類別邊界 mask cv2.resize(mask, (new_size, new_size), interpolationcv2.INTER_NEAREST) return img, mask邏輯說明scale_range控制縮放幅度0.5 到 1.5 是作者給的默認區(qū)間你數(shù)據(jù)里血管特別細的話可以收窄到 0.8 到 1.2避免縮太小把血管縮沒了。base_size是基準尺寸縮放后再統(tǒng)一 crop 或 pad 到網(wǎng)絡輸入尺寸。這里最容易翻車的是 mask 用了雙線性插值訓練時 loss 看著在降但 miou 上不去因為標簽被污染了。3. 從零跑通訓練cos 衰減、日志解讀與結(jié)果文件3.1 訓練腳本的執(zhí)行流程train 腳本是自動化的你只要把數(shù)據(jù)按約定擺好直接運行就行。它會依次做這幾件事調(diào)用compute_gray生成灰度值 txt 并確定輸出通道、構(gòu)建 Dataset 和 DataLoader、初始化 Unet、設置 cos 學習率衰減、開始 epoch 循環(huán)、每個 epoch 結(jié)束后在測試集上算 loss 和 iou、把曲線用 matplotlib 畫出來存進 run_results、保存最優(yōu)權(quán)重和訓練日志。# 訓練啟動示意具體入口以 README 為準 python train.py \ --data_root ./data \ --epochs 10 \ --batch_size 4 \ --lr 1e-3 \ --base_size 512參數(shù)說明data_root是數(shù)據(jù)根目錄里面要有原圖和 mask 兩個子文件夾epochs作者只跑了 10你機器允許的話加到 50 到 100miou 通常還能往上走batch_size受顯存限制512 尺寸下 4 是保守值8G 顯存可以試 8lr初始學習率配合 cos 衰減1e-3 是常見起點。運行后終端會打印每個 epoch 的 lossrun_results 里會多出曲線圖和日志文件。3.2 cos 學習率衰減為什么適合這個任務學習率采用余弦退火公式是 lr 隨 epoch 按余弦曲線從初始值平滑降到接近 0。相比階梯衰減cos 衰減前期降得慢、后期降得快訓練初期能充分探索后期在小學習率下精細收斂。對血管分割這種前景稀疏的任務后期學習率太大容易把已經(jīng)學好的細血管權(quán)重沖掉cos 衰減正好壓住這個抖動。import math def cosine_lr(optimizer, base_lr, epoch, total_epochs, warmup0): if epoch warmup: lr base_lr * (epoch 1) / warmup else: progress (epoch - warmup) / max(1, total_epochs - warmup) lr 0.5 * base_lr * (1 math.cos(math.pi * progress)) for pg in optimizer.param_groups: pg[lr] lr return lr邏輯說明base_lr是初始學習率total_epochs要和訓練總輪數(shù)一致否則余弦曲線走不完。warmup是可選的預熱輪數(shù)數(shù)據(jù)量小的時候加上能防止第一個 epoch 梯度爆炸。注意這個函數(shù)每個 epoch 調(diào)用一次別放進 batch 循環(huán)里否則學習率會降得過快。3.3 訓練日志里該看哪些指標訓練日志里記錄了每個類別的 iou、recall、precision以及全局像素準確率。這里有個血淚經(jīng)驗全局像素準確率在血管分割里參考價值有限因為背景占九成以上模型全預測背景也能有 0.9 的準確率。真正要看的是血管這一類的前景 iou 和 recall。作者給的 miou 0.67 是兩類 iou 的平均如果背景 iou 接近 0.99、血管 iou 只有 0.35平均下來也能到 0.67所以你得把日志里每個類別的數(shù)字單獨拎出來看。指標含義該關注什么全局像素準確率所有像素預測正確的比例參考即可背景主導時虛高前景 iou血管區(qū)域的交并比核心指標低于 0.3 說明沒學到recall血管被召回的比例低說明漏檢多細血管斷precision預測為血管中正確的比例低說明誤檢多背景被誤判miou各類 iou 平均看趨勢別只看絕對值3.4 結(jié)果文件與最優(yōu)權(quán)重的保存run_results 目錄里會有 loss 曲線、iou 曲線、訓練日志和最優(yōu)權(quán)重。曲線圖由 matplotlib 繪制橫軸 epoch、縱軸指標方便你判斷有沒有過擬合。最優(yōu)權(quán)重一般按驗證集 miou 或 loss 來存代碼里通常是「當前指標優(yōu)于歷史最優(yōu)就覆蓋保存」。你復現(xiàn)時如果發(fā)現(xiàn)權(quán)重文件沒更新先檢查驗證集路徑是不是空的空驗證集算不出指標自然不會觸發(fā)保存。4. 推理與換自己的數(shù)據(jù)predict 腳本和「擺好數(shù)據(jù)就能訓」的邊界4.1 推理腳本怎么用推理部分設計得很省事把待推理的眼底圖放進 inference 目錄直接運行 predict 腳本不需要設任何參數(shù)。腳本會自動加載最優(yōu)權(quán)重、遍歷 inference 里的圖、輸出分割結(jié)果。這對新手很友好但也意味著權(quán)重路徑和輸入尺寸是寫死的你換權(quán)重文件或者換輸入分辨率時得去代碼里改。# 推理示意 python predict.py # 輸出默認落在 inference/result 或類似目錄邏輯說明predict 腳本內(nèi)部一般做三件事——加載權(quán)重、對每張圖做和訓練一致的預處理縮放、歸一化、前向推理后把輸出 argmax 成 mask 保存。注意預處理必須和訓練時一致訓練用了 0.5 到 1.5 隨機縮放推理時要用固定尺寸通常是 base_size不能也隨機否則同一張圖每次結(jié)果都不一樣。4.2 換成自己的數(shù)據(jù)要動哪里作者說「訓練自己數(shù)據(jù)的話直接擺好數(shù)據(jù)即可」這話對了一半。擺好數(shù)據(jù)是前提但還有幾個隱含約定要滿足原圖和 mask 文件名要能一一對應mask 必須是單通道灰度圖灰度值要能代表類別。如果你的 mask 是 RGB 三通道的彩色標注圖直接扔進去compute_gray會掃出一堆無意義的灰度值輸出通道數(shù)就錯了。# 把 RGB 標注 mask 轉(zhuǎn)成單通道類別 mask示意 import numpy as np from PIL import Image def rgb_mask_to_label(mask_path, color_map): rgb np.array(Image.open(mask_path).convert(RGB)) label np.zeros(rgb.shape[:2], dtypenp.uint8) for idx, color in enumerate(color_map): match np.all(rgb color, axis-1) label[match] idx return label邏輯說明color_map是你標注時用的顏色列表順序?qū)悇e編號。轉(zhuǎn)換后保存成 png再放進 mask 目錄。這一步不做后面訓練 loss 會異常因為網(wǎng)絡以為有幾十個類別。另外數(shù)據(jù)量小的時候建議做在線增強旋轉(zhuǎn)、翻轉(zhuǎn)、彈性形變血管分割對旋轉(zhuǎn)很敏感增強能明顯提 miou。4.3 顯存和輸入尺寸的取舍512 尺寸、batch_size 4 是作者驗證過的配置。你想提 batch_size 但顯存不夠常見做法是降到 256 尺寸再翻倍 batch但血管在 256 下會變細小血管容易丟。另一個辦法是用梯度累積攢幾個 batch 再更新一次等效大 batch 但不吃顯存。我一般會先跑 256 確認流程通再上 512 看顯存余量別一上來就拉滿導致 OOM 中斷。5. 避坑與排查訓練不收斂、miou 虛高、推理結(jié)果全黑的常見原因5.1 現(xiàn)象loss 一直不降miou 卡在 0.1 附近原因最常見的是 mask 灰度值和輸出通道對不上。比如 mask 是 0/255但代碼按 0/1 處理或者compute_gray生成的 txt 沒被 train 腳本讀到輸出通道用了默認值。另一個原因是圖像和 mask 沒對齊縮放時一個用了線性插值一個用了最近鄰標簽錯位。解決先打印compute_gray的返回值和 txt 內(nèi)容確認類別數(shù)再可視化一個 batch 的原圖和 mask 疊加看血管位置是否重合。對齊問題就統(tǒng)一 mask 用最近鄰插值。5.2 現(xiàn)象全局準確率 0.95 但血管幾乎沒分出來原因這是血管分割最典型的坑。背景像素占比極高模型退化成「全預測背景」也能拿高準確率。作者給的 0.95 準確率必須配合 miou 0.67 一起看單看準確率會被誤導。解決把評估指標換成前景 iou 和 dice或者在 loss 里加類別權(quán)重給血管類更高權(quán)重。常見做法是用 dice loss 或帶權(quán)交叉熵壓住背景的主導地位。5.3 現(xiàn)象推理結(jié)果全黑或全白原因預處理不一致。訓練時圖像做了歸一化比如除以 255 或減均值推理時忘了做輸入分布偏移網(wǎng)絡輸出全偏向一類?;蛘邫?quán)重沒加載成功用了隨機初始化的網(wǎng)絡。解決把推理的預處理函數(shù)和訓練的__getitem__對齊逐行比對。加載權(quán)重后打印幾個參數(shù)值確認不是隨機初始化。推理前先拿一張訓練集里的圖測訓練集都分不對說明權(quán)重或預處理有問題。5.4 現(xiàn)象換自己數(shù)據(jù)后報通道數(shù)不匹配原因compute_gray生成的 txt 還是舊數(shù)據(jù)集的新數(shù)據(jù)的類別數(shù)變了但 txt 沒重新生成網(wǎng)絡輸出通道和標簽對不上。解決每次換數(shù)據(jù)集都刪掉舊 txt 重新跑compute_gray別偷懶復用。這一步花不了幾秒但能省掉半小時的報錯排查。5.5 現(xiàn)象訓練到后面 miou 突然掉下去原因?qū)W習率沒降下來或者 cos 衰減的total_epochs設錯了導致后期學習率還很大把學好的權(quán)重沖亂。也可能是過擬合驗證集 miou 掉但訓練集還在漲。解決檢查 cos 衰減的total_epochs是否等于實際訓練輪數(shù)過擬合的話加數(shù)據(jù)增強、加 dropout 或早停。血管分割數(shù)據(jù)量通常不大過擬合很常見早停是最省事的后悔藥。6. 把 10 個 epoch 推到更高 miou幾個我反復驗證過的調(diào)參習慣作者只跑了 10 個 epoch 就到 miou 0.67這個起點不錯但離可用還有距離。想把指標往上推我一般按這個順序調(diào)先把 epoch 加到 50 到 100cos 衰減的total_epochs同步改這一步通常能漲 3 到 5 個點然后加數(shù)據(jù)增強旋轉(zhuǎn)、水平垂直翻轉(zhuǎn)、彈性形變?nèi)籽軐πD(zhuǎn)敏感增強收益明顯再考慮換 lossdice loss 和交叉熵按比例混合對細血管召回幫助大。驗證方法上別只看最后一個 epoch 的權(quán)重把驗證集 miou 最高的那個權(quán)重單獨存一份訓練完拿它做推理。日志里的曲線如果驗證 miou 在某個 epoch 后不再漲后面都是過擬合早停能省時間。我踩過的一個坑是只存了最后一個 epoch 的權(quán)重結(jié)果最后幾輪過擬合指標反而不如中間輪次只能重訓。調(diào)整項建議值預期收益epoch50 到 100miou 3 到 5數(shù)據(jù)增強旋轉(zhuǎn) 翻轉(zhuǎn) 彈性形變小血管召回提升lossdice 交叉熵混合前景 iou 提升輸入尺寸512 起步顯存夠上 768細血管更清晰權(quán)重保存按驗證 miou 存最優(yōu)避免過擬合權(quán)重還有一個容易忽略的點推理時的后處理。血管是連通的細長結(jié)構(gòu)模型輸出可能有小孔洞或孤立噪點用形態(tài)學閉運算填孔、去掉小于閾值的連通域miou 能再漲一點。但后處理參數(shù)別調(diào)太狠閉運算核太大會把相鄰血管粘在一起反而降指標。我一般用 3x3 核做一次閉運算連通域面積閾值設成 50 像素左右具體看你的分辨率。從那以后我每次跑分割任務都強制先可視化一個 batch 的原圖、mask 和預測疊加圖確認數(shù)據(jù)對齊、標簽正確、預處理一致再去看指標。這一步花兩分鐘能擋掉八成玄學問題。希望幫到你。本文還有配套的精品資源點擊獲取