據(jù)集全流程指南:從數(shù)據(jù)組織到推理調(diào)優(yōu))
簡介一套面向深度學習道路提取任務的nnU-Net格式訓練與測試數(shù)據(jù)集源自馬薩諸塞道路遙感數(shù)據(jù)集Massachusetts Roads Dataset適合使用nnU-Net框架進行遙感圖像分割的研究者與工程師。數(shù)據(jù)包共59個文件含58張PNG圖像和1份dataset.json配置壓縮后約142.02MB目錄嚴格按nnU-Net規(guī)范組織imagesTr/imagesTs存放訓練與測試影像labelsTr/labelsTs存放道路標注掩膜訓練與測試集劃分清晰默認命名符合nnU-Net預處理要求可直接被讀取使用。影像覆蓋城市、郊區(qū)和農(nóng)村等多種地形道路標簽由OpenStreetMap道路中心線柵格化生成適合檢驗模型在樹木遮擋、陰影等復雜場景下的提取能力。目前已有1772人瀏覽學習拿到后無需格式轉(zhuǎn)換即可用于道路分割實驗也可作為遙感語義分割基準數(shù)據(jù)集對比不同算法。1. nnunet訓練測試數(shù)據(jù)集為什么這個框架能“開箱即用”“nnunet訓練測試數(shù)據(jù)集”這幾個詞放在一起容易讓人誤以為這是某個現(xiàn)成項目的操作手冊。實際經(jīng)歷過的人都知道nnU-Net最反直覺的地方在于它不靠發(fā)明新網(wǎng)絡結(jié)構(gòu)而是靠一套自動配置流程把預處理、訓練策略和推理參數(shù)按數(shù)據(jù)集特征給你定好。你只需要按它的規(guī)矩把數(shù)據(jù)集擺放整齊、寫好dataset.json剩下的plan、train、predict三步命令就能跑起來。對做醫(yī)學影像分割、又不想在參數(shù)調(diào)優(yōu)上耗太多時間的團隊來說這套流程省掉的不是幾分鐘是幾周。我按自己慣用的落地路徑來寫數(shù)據(jù)集怎么組織、預處理怎么跑、訓練怎么訓、測試推理怎么出結(jié)果以及最容易返工的那幾個坑。2. 把數(shù)據(jù)集整理成nnU-Net要的樣子任務目錄與JSON配置2.1 從原始標注到nnU-Net的數(shù)據(jù)組織labelsTr、imagesTr與dataset.jsonnnU-Net v2的數(shù)據(jù)組織是按“任務Task”管理的。每個任務是一個獨立的文件夾放在nnUNet_raw_data目錄底下命名規(guī)則是Task序號_任務名稱比如Task001_liver或Task002_brain。序號就是后面plan和train命令里用的任務編號名稱是給你自己看的兩個部分用下劃線連接。文件夾內(nèi)部固定是imagesTr、labelsTr、imagesTs三個目錄分別放訓練圖像、訓練標簽和測試圖像命名中的Tr是training的縮寫Ts是testing的縮寫。nnU-Net只吃NIfTI格式.nii.gz其他格式需要先轉(zhuǎn)這個沒得商量。文件名要按“病例名_模態(tài)標識符_病例名”來組織。比如一個病例叫case_001的CT圖像在imagesTr里就叫case_001_0000.nii.gz其中_0000代表模態(tài)序號0。標簽文件在labelsTr里就叫case_001.nii.gz。這里最容易翻車的是圖像文件和標簽文件名必須完全對應但標簽不能帶模態(tài)后綴。你有一張case_001_0000.nii.gz就必須有一張case_001.nii.gz在labelsTr里否則訓練時nnU-Net報“找不到配對”的錯誤。另一個容易忽略的規(guī)則是case_001這個名稱本身不能再包含下劃線因為nnU-Net解析文件名時靠下劃線切分字段case名字里再帶下劃線會讓解析邏輯直接混亂。下面的小腳本可以快速把散落的圖像和標簽整理成這個結(jié)構(gòu)import shutil from pathlib import Path raw_root Path(raw_data) # 原始圖像目錄 label_root Path(raw_label) # 原始標簽目錄 target Path(nnUNet_raw/nnUNet_raw_data/Task001_demo) (target / imagesTr).mkdir(parentsTrue, exist_okTrue) (target / labelsTr).mkdir(parentsTrue, exist_okTrue) (target / imagesTs).mkdir(parentsTrue, exist_okTrue) for i, img_path in enumerate(sorted(raw_root.glob(*.nii.gz))): case_id img_path.stem.split(.)[0] # 取文件名當作病例名 tgt_img target / fimagesTr/{case_id}_0000.nii.gz shutil.copy(img_path, tgt_img) # 找到同名標簽 lab_path label_root / f{case_id}.nii.gz if lab_path.exists(): shutil.copy(lab_path, target / flabelsTr/{case_id}.nii.gz) else: print(f缺少標簽: {case_id})這段腳本做的事情遍歷原始圖像目錄把每個.nii.gz文件復制到imagesTr并自動加上_0000后綴同時檢查同名標簽是否存在存在的復制到labelsTr不存在的單獨打印出來提醒你補數(shù)據(jù)。我用它處理過一次幾百例的肝臟分割數(shù)據(jù)唯一要注意的是case_id不能包含下劃線以外的特殊字符否則訓練階段解析文件名時會異常。2.2 寫dataset.json的字段channel_names、labels與file_ending每個Task目錄下都要有一個dataset.jsonnnU-Net從plan階段開始就讀它。最核心的三個字段是channel_names、labels和file_ending。channel_names描述每個模態(tài)通道是什么單模態(tài)CT就是[CT]多模態(tài)比如MRI的T1和T2就寫[T1,T2]順序要和文件名里的_0000、_0001一一對應。labels是字典從0開始0必須是background后面的1、2就是你要分割的解剖結(jié)構(gòu)。file_ending固定寫.nii.gz。{ channel_names: { 0: CT }, labels: { 0: background, 1: liver, 2: tumor }, numTraining: 120, file_ending: .nii.gz }這里的numTraining要和imagesTr里實際圖像數(shù)量一致不一致時plan階段會警告。labels的鍵必須是字符串值是類別名建議用英文方便在日志里識別。多模態(tài)場景常見做法是把MRI的多個序列按固定順序排列比如T1加權(quán)、T2加權(quán)、FLAIR分別作為_0000、_0001、_0002同一病例的所有模態(tài)都必須存在缺失一個模態(tài)時nnU-Net不會替你補全訓練會以報錯告終。如果你處理的是帶子區(qū)域的分割任務比如肝臟和肝臟腫瘤分別標注還可以在dataset.json里額外寫regions字段來描述子區(qū)域歸屬但這個字段是可選的新手階段先不用管。2.3 檢查圖像與標簽形狀跑plan之前先做自檢數(shù)據(jù)放好、json寫完之后我不會立刻跑plan_and_preprocess而是先做一個自檢把每個病例的圖像和標簽都load進來比對shape和spacing。因為nnU-Net在訓練階段會自動把圖像裁剪到非零區(qū)域但如果你圖像的尺寸和標簽尺寸不一致等到預處理重采樣之后才發(fā)現(xiàn)再回去改數(shù)據(jù)就晚了。下面這個腳本遍歷一遍import nibabel as nib from pathlib import Path img_dir Path(nnUNet_raw/nnUNet_raw_data/Task001_demo/imagesTr) lab_dir Path(nnUNet_raw/nnUNet_raw_data/Task001_demo/labelsTr) for img_path in sorted(img_dir.glob(*.nii.gz)): case_id img_path.name.replace(_0000.nii.gz, ) lab_path lab_dir / f{case_id}.nii.gz if not lab_path.exists(): print(f標簽缺失: {case_id}) continue img nib.load(img_path) lab nib.load(lab_path) if img.shape ! lab.shape: print(fshape不一致: {case_id} img{img.shape} lab{lab.shape}) else: print(fOK: {case_id} {img.shape})這一步花五分鐘能避免后面plan階段報一堆形狀不匹配的錯。shape不一致通常是因為原始標注軟件把標簽裁剪過或重采樣過常見做法是把標簽重新resample到圖像的空間建議用SimpleITK的Resample插值方式選最近鄰避免引入新的標簽值。多模態(tài)數(shù)據(jù)還要把每個模態(tài)的shape也互相核對一遍我遇到過T1和T2序列采集范圍不同導致shape不一致的情況這種病例要么裁到共同區(qū)域要么干脆剔除。3. 跑通預處理與訓練從nnUNet_plan_and_preprocess到nnUNet_train3.1 安裝與環(huán)境變量nnUNet_raw、nnUNet_preprocessed、nnUNet_results三件套nnU-Net v2用pip安裝命令是pip install nnunetv2它的命令行工具前綴是nnUNet_。要跑起來必須先設置三個環(huán)境變量。nnUNet_raw對應上面放原始數(shù)據(jù)的目錄nnUNet_preprocessed是預處理輸出目錄plan階段生成的plans.json和預處理后的數(shù)組都落在那里nnUNet_results是訓練結(jié)果目錄權(quán)重和日志都在里面。這三個目錄建議都放到同一塊剩余空間足夠的盤上因為預處理后的數(shù)據(jù)往往比原始數(shù)據(jù)大好幾倍。export nnUNet_raw/data/nnUNet/nnUNet_raw export nnUNet_preprocessed/data/nnUNet/nnUNet_preprocessed export nnUNet_results/data/nnUNet/nnUNet_results這三個環(huán)境變量在每個終端都要export一遍如果忘記設置命令會直接提示找不到路徑。我一般把它們寫進~/.bashrc省得每次重開終端都要敲一遍。另外要注意nnunetv1和v2的命令不兼容網(wǎng)上一搜一大把舊教程里nnUNet_plan_and_preprocess的用法是v1風格v2里已經(jīng)統(tǒng)一成nnUNet_plan_and_preprocess -t 任務ID這種寫法不需要再手動指定數(shù)據(jù)集名稱。3.2 nnUNet_plan_and_preprocess數(shù)據(jù)指紋與自動配置預處理階段做的事情是掃描所有數(shù)據(jù)的統(tǒng)計信息形狀、spacing、強度分布、前景占比然后在3d_fullres、3d_lowres、2d這些配置里選出適合當前數(shù)據(jù)的方案同時完成重采樣、裁剪、z-score歸一化。這一步的輸出是預處理后的數(shù)據(jù)、plans.json和dataset.json副本。命令很簡單nnUNet_plan_and_preprocess -t 1 --verify_dataset_integrity-t 1對應Task001那個序號--verify_dataset_integrity是額外做一次數(shù)據(jù)完整性校驗會檢查每個病例的圖像和標簽是否一一對應、尺寸是否一致比2.3節(jié)那個自檢腳本更嚴格。跑完之后看nnUNet_preprocessed/Task001_demo目錄里面應該出現(xiàn)一個plans.json和一堆預處理后的.npz、.npy文件。預處理時間取決于數(shù)據(jù)量和分辨率幾十例CT大概十幾分鐘幾百例高分辨率MRI可能要跑幾個小時這個速度是正常的別看到進度條不動就去CtrlC。plan階段不是所有配置都會跑。如果數(shù)據(jù)量和前景區(qū)域都不滿足3d_lowres的條件nnU-Net會自動跳過低分辨率配置只跑2d和3d_fullres。這一步看起來像個黑匣子但它其實把傳統(tǒng)調(diào)參過程里“圖像應該重采樣成什么大小”“l(fā)oss怎么配”“要不要用級聯(lián)”這些問題都自動回答了。對新手來說不需要手動干預對想干預的熟手plans.json里的preprocessor_name、resampling_scheme這些字段都可以改但改完要能說得清為什么否則不如不改。3.3 nnUNet_train開始訓練配置、Trainer與fold參數(shù)預處理完成后就能訓練了。nnUNet_train命令格式是nnUNet_train 3d_fullres nnUNetTrainerV2 1 0第一個參數(shù)是配置名3d_fullres是完整分辨率的三維模型數(shù)據(jù)少或顯存小時用2d第二個參數(shù)是Trainer類一般就寫nnUNetTrainerV2想換loss或改學習率就繼承這個類寫自己的Trainer第三個參數(shù)是任務ID第四個是fold。fold從0到4代表5折交叉驗證的哪一折如果只想快速驗證流程通不通可以用0這一個fold訓完后續(xù)要出穩(wěn)定結(jié)果再訓全5個fold。模型權(quán)重默認寫在nnUNet_results/Task001_demo/nnUNetTrainerV2__3d_fullres/fold_0下面文件名是model_best和model_final_checkpoint。訓練日志在同一目錄下的training_log_*.txt里每個epoch會打印loss、Dice等指標。顯存不夠時可以在命令里加--disable_deep_supervision減少顯存占用但會犧牲一點精度也可以手動把batch size調(diào)小訓練腳本默認會按數(shù)據(jù)大小自動選batch size手動改的話一般從2開始逐步往下試同時觀察GPU利用率別掉到30%以下。3.4 用--val跑驗證集先拿到訓練階段的客觀指標訓練完fold_0之后我想先看一眼這個fold在驗證集上的表現(xiàn)再決定要不要把5個fold都訓練完。做法是重新執(zhí)行一次nnUNet_train但加上--val參數(shù)nnUNet_train 3d_fullres nnUNetTrainerV2 1 0 --val這個命令不會重新訓練而是用剛才訓練好的權(quán)重在fold_0的驗證集就是訓練時留出來的那20%上做推理然后輸出一個validation文件夾里面是預測結(jié)果同時打印每個類別的Dice系數(shù)。這個驗證指標的意義在于它是nnU-Net自己按數(shù)據(jù)劃分算出來的和后面nnUNet_predict階段你自己拿測試集去推的結(jié)果在數(shù)據(jù)分布上是一致的可以用來判斷訓練是否收斂、需不需要加大epochs或者換Trainer。如果你的數(shù)據(jù)類別極度不均衡這里還要關(guān)注一下每個類別的Dice而不是只看平均Dice某個類別Dice特別低通常是該類別樣本太少需要回去檢查標注質(zhì)量或考慮用更大的學習率。4. 用nnUNet_predict做測試推理輸入輸出與后處理4.1 nnUNet_predict的標準命令與關(guān)鍵參數(shù)測試階段的核心命令是nnUNet_predict。它讀取一個輸入文件夾里所有圖像輸出對應的分割結(jié)果。一個完整的調(diào)用是nnUNet_predict -i /data/test_images -o /data/test_pred \ -t 1 -m 3d_fullres -f 0 \ --save_npz參數(shù)含義-i是測試圖像目錄里面放和訓練集同名的.nii.gz文件模態(tài)后綴_0000也是必需的-o是輸出目錄-t是任務ID-m是配置名-f是指定用哪幾個fold的模型多個fold時寫-f 0 1 2 3 4nnU-Net會把多個fold的結(jié)果取平均再argmax這是它比單模型更穩(wěn)的重要原因5折ensemble的預測結(jié)果通常比單折高1到2個點Dice--save_npz會把softmax概率保存下來占空間但后續(xù)做集成或分析方便。如果訓練時用的自定義Trainer還要加-tr nnUNetTrainerV2指定如果plan階段生成了多個配置要選對你想用的那個。這些參數(shù)不一致時最常見的報錯是“checkpoint not found”——因為你指定的fold沒有訓練過或者Trainer名對不上。排查時先看nnUNet_results/Task001_demo目錄下有哪些配置和fold再回頭對照命令里的參數(shù)。4.2 單張與批量推理輸入目錄結(jié)構(gòu)決定成敗nnUNet_predict按目錄批量處理輸入目錄里的每個文件都會被當成一個病例。所以如果你只測一張圖也得放在一個目錄里不能直接給文件路徑。輸出目錄不需要提前創(chuàng)建程序會自動建。推理完成之后輸出目錄里是case名加.nii.gz后綴的分割結(jié)果標簽值對應dataset.json里定義的1、2等類別。結(jié)果讀進Python里很簡單import nibabel as nib import numpy as np pred nib.load(/data/test_pred/case_001.nii.gz) data pred.get_fdata().astype(np.int16) print(np.unique(data)) # 應該出現(xiàn)0、1、(2)這些標簽這里一個容易踩的坑是輸出的是label還是probabilities。默認輸出的是標簽圖只有加了--save_npz才會額外生成npz概率文件。如果你拿到結(jié)果發(fā)現(xiàn)值全是0和1而訓練時有兩個目標類別多半是label id寫錯或者模型本身沒訓好。批量推理時建議分批拷貝輸入文件比如一次放50例跑完一批再放下批這樣萬一某個文件有問題不會整個任務失敗重來。4.3 TTA開不開、step_size怎么設nnU-Net推理時默認會啟用test time augmentationTTA即對輸入做幾種空間變換后分別預測再取平均這個機制常被稱為測試時訓練。TTA通常能漲0.5到1個點Dice代價是推理時間翻倍。顯存夠、時間不急時我不關(guān)但如果是做線上服務或處理幾百例的大批量數(shù)據(jù)加--disable_tta能省一半時間。這里我一般先開著TTA跑一遍測試集把結(jié)果存好再關(guān)掉TTA跑一遍對比指標差異如果差異不到0.3個點后續(xù)就關(guān)掉TTA換速度。step_size是滑窗推理的步長比例默認0.5表示窗口每次移動一半。對特別大的圖像step_size調(diào)到0.8能加快推理但邊界區(qū)域的分割會略差調(diào)小到0.3會變慢但更穩(wěn)。這個參數(shù)是推理階段少數(shù)值得手動調(diào)的因為它直接控制滑窗重疊程度對分割穩(wěn)定性影響明顯。調(diào)的時候觀察一下輸出mask的連通性如果目標區(qū)域出現(xiàn)很多細碎的碎片多半是step_size太大導致窗口間預測不一致。4.4 模型checkpoint的“后悔藥”中斷續(xù)訓與權(quán)重選擇訓練中途斷了不用從頭跑。nnUNet_train會記錄斷點重新執(zhí)行同樣的訓練命令會從最近的checkpoint繼續(xù)。這里要分清兩個文件model_best是驗證集上表現(xiàn)最好的權(quán)重model_final_checkpoint是訓練結(jié)束時的權(quán)重兩個文件在同名目錄下。如果訓練到一半被kill掉只有model_final_checkpoint存在可以用它繼續(xù)如果要推理優(yōu)先用model_best還是model_final_checkpoint可以看驗證指標常見做法是哪個在驗證集上高用哪個。我在實際項目中習慣每個fold訓完后把model_best單獨復制一份到另一個目錄避免后續(xù)誤操作把結(jié)果覆蓋。權(quán)重文件是幾百MB級別不占空間但這個習慣救過我兩次一次是重跑訓練時把fold_0的結(jié)果覆蓋了另一次是清理目錄時誤刪了訓練輸出。nnU-Net的自動化程度再高也架不住手動操作失誤備份永遠是成本最低的后悔藥。5. nnunet訓練測試數(shù)據(jù)集的5個常見問題現(xiàn)象、原因與解決5.1 標簽是RGB索引而不是單通道標簽loss怎么都不降現(xiàn)象訓練幾十個epochloss在0.6左右不動Dice一直是0或極低。原因很多人從標注工具比如Labelme或某些Web標注平臺導出的標簽是RGB彩色圖比如背景是(0,0,0)目標是(255,0,0)直接存成PNG轉(zhuǎn)成NIfTI后每個像素是一個三維向量不是單一類別ID。nnU-Net讀進去會把每個向量當成多通道標簽來處理模型無法學習這種離散映射loss自然降不下去。解決把RGB標簽映射成索引標簽。常見做法是按顏色構(gòu)建一個查找表把每個像素的RGB向量映射成整數(shù)類別import numpy as np from PIL import Image # 顏色到類別ID的映射按你的實際標注顏色填寫 color_map { (0, 0, 0): 0, # background (255, 0, 0): 1, # liver (0, 0, 255): 2, # tumor } label_img np.array(Image.open(label.png)) # 形狀 (H, W, 3) out np.zeros((label_img.shape[0], label_img.shape[1]), dtypenp.int16) for rgb, cls_id in color_map.items(): mask (label_img[..., 0] rgb[0]) (label_img[..., 1] rgb[1]) (label_img[..., 2] rgb[2]) out[mask] cls_id nib.save(nib.Nifti1Image(out, affine), label_mapped.nii.gz)關(guān)鍵是映射后要檢查np.unique(out)只能出現(xiàn)0、1、2這些連續(xù)整數(shù)中間不能有缺口否則模型訓練時類別數(shù)量和dataset.json定義對不上又產(chǎn)生新的報錯。我遇到過標注工具把兩種顏色混在一起的情況比如兩個類別都含有(255,0,0)分量這時候要回到標注軟件里重新修正靠腳本補不干凈。5.2 plan_and_preprocess在計算數(shù)據(jù)指紋時中斷現(xiàn)象nnUNet_plan_and_preprocess跑到某個病例時直接報錯或Killed日志里沒有明確報錯信息。原因最常見的是圖像非零區(qū)域dense比如整幅圖都沒有全零切片導致裁剪時申請的內(nèi)存過大或者圖像spacing異常某個維度spacing為0導致重采樣計算溢出再就是內(nèi)存不夠被系統(tǒng)OOM kill。解決先看plans.json是否已經(jīng)生成如果生成了一部分說明是在統(tǒng)計階段之后就掛的。然后單跑一遍數(shù)據(jù)完整性校驗nnUNet_plan_and_preprocess -t 1 --verify_dataset_integrity如果這步能過再把內(nèi)存上限放開或者分批預處理。nnU-Net支持--mode參數(shù)把plan和preprocess拆開執(zhí)行先plan再preprocess這樣能定位到底是哪一步掛的。數(shù)據(jù)本身有問題的用2.3節(jié)的自檢腳本把報錯case找出來單獨修掉再重跑。這一步最忌諱反復直接重跑完整命令日志一大片根本看不出哪個case有問題。5.3 重采樣后目標器官變形嚴重spacing不一致先做歸一化現(xiàn)象預處理后的圖像和標簽里目標器官的形狀看起來和原始圖像不一致細小的結(jié)構(gòu)扭曲、斷裂。原因nnU-Net的plan階段會計算所有病例的median spacing把所有圖像重采樣到目標spacing。如果一部分病例是1x1x1mm體素另一部分是5x5x5mm體素重采樣到中間值后后者被放大的倍數(shù)過大細節(jié)就糊了。解決在數(shù)據(jù)準備階段統(tǒng)一spacing。常見做法是先用SimpleITK把所有圖像重采樣到同一個spacing范圍比如CT統(tǒng)一到1x1x1mm然后再交給nnU-Net。注意標簽重采樣必須用最近鄰插值用線性或三次樣條會在邊緣產(chǎn)生中間值破壞標簽語義。重采樣后還要再跑一次自檢確認圖像和標簽的shape仍然一致。另外不同設備采集的同一器官數(shù)據(jù)spacing差異太大時不要硬塞進同一個數(shù)據(jù)集訓練先按spacing分桶處理看各自的分割效果再決定是否合并。5.4 訓練完找不到model_final_checkpointfold和命令沒對應現(xiàn)象訓練日志正常打印但nnUNet_results對應目錄里是空的或者只有partial_checkpoint。原因訓練進程被提前終止或者store_checkpoint的路徑?jīng)]寫對。nnU-Net訓練過程中會周期性保存partial_checkpoint只有正常跑完一個fold才會落model_best和model_final_checkpoint。如果你只是快速驗證模型能跑通訓練被CtrlC中斷自然找不到完整權(quán)重。解決確認訓練日志里出現(xiàn)了類似“Epoch done”的完整周期輸出。如果只是驗證流程建議把epochs調(diào)小、data少放點但最好還是讓它完整跑完一個fold不然測試階段nnUNet_predict找不到checkpoint白忙一場。也可以訓練過程中手動把partial_checkpoint復制出來續(xù)訓但我不建議新手這么做續(xù)訓的路徑和參數(shù)比較容易搞錯我見過有人把partial_checkpoint當成final去推理結(jié)果輸出全黑。5.5 推理結(jié)果全黑或全背景l(fā)abel IDs和JSON定義對不上現(xiàn)象測試集推理完成了結(jié)果圖打開全黑np.unique輸出只有0。原因測試圖和訓練圖強度分布差太遠比如訓練數(shù)據(jù)是增強CT測試數(shù)據(jù)是平掃CT歸一化之后目標區(qū)域的響應完全不同更常見的是labels定義和標注數(shù)據(jù)不一致比如標注文件里類別ID是1到5dataset.json里只定義了0和1兩個類別模型從未見過那些標簽。解決先跑驗證集看看是否正常。如果驗證集正常、測試集全黑優(yōu)先懷疑數(shù)據(jù)分布差異把測試圖像的強度分布和訓練集對比一下看是不是范圍差太多。如果驗證集也全黑檢查標注數(shù)據(jù)里到底有哪些值把labels字典補全。不要先懷疑模型這一步排查順序能省很多時間。還要順帶檢查一下是不是測試圖像命名里的模態(tài)序號和訓練集不一致_0000寫成了_0001模型拿到的通道根本不是它認識的那個。6. 一個讓nnU-Net結(jié)果更穩(wěn)的習慣先讀預測圖再談指標我見過不少同事拿到Dice 0.9就急著交付結(jié)果臨床醫(yī)生打開一看分割結(jié)果比原始圖像小了一圈邊緣完全對不上。Dice是整體重疊率對邊界偏差不敏感但對醫(yī)學分割來說邊界才是最能看出問題的。所以我現(xiàn)在有一個習慣無論訓練還是測試推理出來的預測圖一定先疊加到原圖上肉眼看一遍再去看指標。用SimpleITK或nibabel都能做這個疊加最快速的方法是先把預測轉(zhuǎn)成numpy然后做偽彩色覆蓋import numpy as np import nibabel as nib import matplotlib.pyplot as plt img nib.load(case_001_0000.nii.gz).get_fdata() pred nib.load(case_001_pred.nii.gz).get_fdata() plt.figure(figsize(12, 6)) plt.subplot(1, 2, 1) plt.imshow(img[:, :, img.shape[2] // 2], cmapgray) plt.subplot(1, 2, 2) plt.imshow(img[:, :, img.shape[2] // 2], cmapgray) plt.imshow(np.ma.masked_where(pred[:, :, img.shape[2] // 2] 0, pred[:, :, img.shape[2] // 2]), alpha0.5, cmapautumn) plt.savefig(overlay_check.png)這個腳本每次推理后我都會跑一遍挑幾個典型切片存成圖。如果要系統(tǒng)驗證一個測試集我會從中隨機抽10個病例做疊加圖肉眼檢查有沒有分割區(qū)域跑到圖像外面去、目標器官之間的邊界粘連、小結(jié)構(gòu)被漏掉。這三個問題指標上不一定看得出來但疊加圖一眼就能發(fā)現(xiàn)。做nnU-Net這兩年我最大的教訓就是不要迷信框架的自動化自動化的plan和train只是把常規(guī)參數(shù)調(diào)好了數(shù)據(jù)本身的問題它管不了。每一次拿到新數(shù)據(jù)集先把數(shù)據(jù)質(zhì)量關(guān)卡住再談訓練和推理。希望這個習慣對你也有用。本文還有配套的精品資源點擊獲取