胞核分割數(shù)據(jù)集實(shí)戰(zhàn):從掩碼可視化到U-Net訓(xùn)練)
簡介面向醫(yī)學(xué)圖像分割任務(wù)這份數(shù)據(jù)集聚焦乳腺腫瘤細(xì)胞核分割2類適用于深度學(xué)習(xí)分割模型的訓(xùn)練、驗(yàn)證與算法對比。圖像與mask均為png格式分辨率涵蓋256×256與1000×1000mask采用0/1閾值模板分別對應(yīng)背景與腫瘤細(xì)胞核訓(xùn)練集含66對圖像與掩膜測試集單獨(dú)劃分可直接支持U-Net、DeepLab等常見分割網(wǎng)絡(luò)的訓(xùn)練與評估。樣本文件名帶TCGA病例標(biāo)識便于追溯來源與后續(xù)擴(kuò)展。壓縮包共167個(gè)文件以png圖片為主體另含1個(gè)txt類別說明和1個(gè)py可視化腳本腳本無需修改即可運(yùn)行隨機(jī)抽取一張樣本同屏展示原始圖像、GT掩膜及GT疊加效果幫助快速核對標(biāo)簽質(zhì)量。資源整體約62MB已有194人學(xué)習(xí)適合醫(yī)學(xué)影像分析方向的研究者、競賽選手及深度學(xué)習(xí)初學(xué)者可作為模型訓(xùn)練與基準(zhǔn)測試的現(xiàn)成數(shù)據(jù)基礎(chǔ)。1. 乳腺腫瘤細(xì)胞核分割2類數(shù)據(jù)集的定位一個(gè)能直接上手的醫(yī)學(xué)圖像分割起手式做乳腺病理圖像的細(xì)胞核分割第一步往往不是選網(wǎng)絡(luò)而是找一個(gè)標(biāo)注靠譜、結(jié)構(gòu)簡單的數(shù)據(jù)集練手。這個(gè)標(biāo)題給的就是這類東西乳腺腫瘤細(xì)胞核的 2 類分割兩類指「背景」和「細(xì)胞核」不是把上皮、淋巴細(xì)胞、間質(zhì)等核類型細(xì)分。它自帶圖像、標(biāo)簽文件和數(shù)據(jù)可視化代碼意味著你能在幾小時(shí)內(nèi)跑通「讀圖—看掩碼—訓(xùn)練—評估」的閉環(huán)不用把時(shí)間耗在格式轉(zhuǎn)換和手動(dòng)標(biāo)注上。適合剛進(jìn)醫(yī)學(xué)圖像分割方向的研究生也適合想把數(shù)字病理快速落地驗(yàn)證的工程團(tuán)隊(duì)。這類二分類核分割是病理 AI 里最值得先做透的基礎(chǔ)任務(wù)核密度、核形態(tài)、Ki67 陽性率這些下游指標(biāo)全都建立在它上面。2. 拆解數(shù)據(jù)集與標(biāo)簽文件目錄結(jié)構(gòu)、掩碼取值與格式轉(zhuǎn)換拿到數(shù)據(jù)集先別急著訓(xùn)練用五分鐘把目錄和標(biāo)簽的真實(shí)格式看清楚后面能省幾小時(shí)排查。乳腺腫瘤細(xì)胞核分割數(shù)據(jù)集最常見的組織方式有兩種一種是images/與masks/兩個(gè)平級目錄圖像和掩碼同名不同后綴另一種是全部放在同一目錄靠文件名后綴區(qū)分。先用一條命令看全貌。tree -L 2 dataset/# Windows 下沒有 tree 時(shí) dir /s dataset邏輯說明tree -L 2只看兩層夠判斷目錄結(jié)構(gòu)不會(huì)因?yàn)椴±頀呙鑸D的大文件把終端刷爆。Windows 上dir /s是等效替代。看的時(shí)候重點(diǎn)確認(rèn)三件事圖像和掩碼是否一一對應(yīng)、掩碼是不是單通道灰度圖、文件名是否帶病例前綴。乳腺腫瘤數(shù)據(jù)集的命名通常類似case12_slide03_region07.png這個(gè)前綴后面做數(shù)據(jù)劃分有大用。2.1 標(biāo)簽文件的真實(shí)格式單通道掩碼和 0/255 約定很多第一次接觸分割數(shù)據(jù)集的人會(huì)在掩碼讀取上翻車。乳腺腫瘤細(xì)胞核分割的標(biāo)簽文件一般是 PNG 格式的單通道灰度圖像素只有兩種取值0 表示背景255 表示細(xì)胞核。有的版本存成 0 和 1有的是三通道彩色圖但三個(gè)通道內(nèi)容完全一樣。讀取方式必須顯式指定灰度模式import cv2 import numpy as np mask cv2.imread(dataset/masks/mask_001.png, cv2.IMREAD_GRAYSCALE) print(np.unique(mask)) # 輸出可能是 [0 255]也可能是 [0 1]邏輯說明IMREAD_GRAYSCALE強(qiáng)制以單通道讀入。很多教程里直接cv2.imread(path)默認(rèn)按三通道 BGR 讀灰度 PNG 會(huì)被復(fù)制成三通道np.unique出來的是[0 255]的三維組合后面不管訓(xùn)練還是可視化都會(huì)踩坑。這里要養(yǎng)成的習(xí)慣是所有用到掩碼的地方讀入后先print(np.unique(mask))確認(rèn)取值。參數(shù)說明0/255 是標(biāo)注工具導(dǎo)出時(shí)的常見格式但訓(xùn)練時(shí)多數(shù)框架要求類別索引從 0 開始因此讀入后要么除以 255要么用astype(np.uint8)后重映射。我一般統(tǒng)一轉(zhuǎn)成 0/1少一層換算。2.2 分割掩碼與目標(biāo)檢測標(biāo)注的本質(zhì)差別如果你之前用 YOLO 訓(xùn)練過自己的數(shù)據(jù)集對.txt里的一串歸一化框坐標(biāo)很熟。分割不一樣標(biāo)簽是逐像素的沒有「框住目標(biāo)」這回事。用 COCO 2017 數(shù)據(jù)集結(jié)構(gòu)來類比會(huì)更清楚COCO 的實(shí)例分割標(biāo)簽是 JSON 里存多邊形點(diǎn)坐標(biāo)訓(xùn)練前要把點(diǎn)柵格化成 mask而這個(gè)標(biāo)題下的數(shù)據(jù)集已經(jīng)幫你做完了柵格化打開就是 PNG 掩碼省掉了 polygon-to-mask 這一步。這既是優(yōu)點(diǎn)也是風(fēng)險(xiǎn)——你看不到原始標(biāo)注過程只能信任掩碼本身。所以拿到數(shù)據(jù)后的第一件事是可視化抽查而不是直接開訓(xùn)。另外要留意「2類」的定義邊界。有的公開數(shù)據(jù)比如多類核標(biāo)注版本會(huì)把上皮核、淋巴細(xì)胞核、基質(zhì)核分開標(biāo)標(biāo)題里說 2 類說明已經(jīng)合并成「背景 細(xì)胞核」的語義。但如果原始素材是多類標(biāo)合并時(shí)不能簡單mask 0一把梭見下一節(jié)。2.3 把多類別掩碼重映射成 2 類一個(gè)通用轉(zhuǎn)換腳本從朋友那拷來的數(shù)據(jù)、或從公開項(xiàng)目如 MoNuSeg 系列拿到的原始標(biāo)注常常是多類的。要做 2 類分割就得先合并。一個(gè)足夠通用的腳本如下import cv2 import glob import os import numpy as np src_masks sorted(glob.glob(raw_masks/*.png)) os.makedirs(masks, exist_okTrue) for p in src_masks: m cv2.imread(p, cv2.IMREAD_GRAYSCALE) m2 (m 0).astype(np.uint8) * 255 out os.path.join(masks, os.path.basename(p)) cv2.imwrite(out, m2) print(f{p} - {out}, classes{np.unique(m2)})邏輯說明m 0把所有非背景像素統(tǒng)一變成 Trueastype(np.uint8)得到 0/1* 255后存成 0/255 的 PNG。這一段最關(guān)鍵的假設(shè)是所有非零像素都算「細(xì)胞核」。如果你的原始標(biāo)注里 1 是上皮核、2 是纖維核、3 是炎性核想保留全部這個(gè)腳本成立但如果原始標(biāo)注里有 255 表示「待確認(rèn)」或「背景遮罩」直接合并會(huì)把噪聲也當(dāng)成前景。參數(shù)說明輸出格式建議統(tǒng)一 0/255方便cv2.imwrite直接落盤。如果之后用 PyTorch 的CrossEntropyLoss訓(xùn)練時(shí)再除以 255 即可。合并前用np.unique(m)看一眼原始取值確認(rèn)沒有中間灰階這是最容易忽略的一步。2.4 遇到 HDF5 打包的數(shù)據(jù)集先解包再動(dòng)手有些醫(yī)學(xué)圖像分割數(shù)據(jù)集為了減少海量小文件的 IO 開銷會(huì)打包成單個(gè).h5文件。結(jié)構(gòu)通常是images和masks兩個(gè) datasetshape 是(樣本數(shù), 高, 寬, 通道數(shù))。讀取和拆包用h5pyimport h5py import cv2 import numpy as np import os os.makedirs(images, exist_okTrue) os.makedirs(masks, exist_okTrue) with h5py.File(dataset.h5, r) as f: print(keys:, list(f.keys())) n f[masks].shape[0] for i in range(n): img f[images][i] mask f[masks][i] img (img * 255).astype(np.uint8) if img.max() 1.01 else img.astype(np.uint8) mask (mask * 255).astype(np.uint8) if mask.max() 1.01 else mask.astype(np.uint8) cv2.imwrite(fimages/img_{i:04d}.png, img) cv2.imwrite(fmasks/mask_{i:04d}.png, mask)邏輯說明HDF5 的好處是單文件、支持按索引隨機(jī)讀取但實(shí)際訓(xùn)練時(shí) PyTorch 的DataLoader不太喜歡直接讀 h5頻繁隨機(jī)訪問會(huì)卡。我一般先解包成 PNG訓(xùn)練時(shí)用小文件隨機(jī)讀速度更穩(wěn)。代碼里的歸一化判斷很關(guān)鍵醫(yī)學(xué)數(shù)據(jù)集的 HDF5 里圖像經(jīng)常存成 0~1 的 float掩碼也如此直接astype(np.uint8)會(huì)得到全黑圖。參數(shù)說明i:04d保證文件名按四位序號補(bǔ)零排序不亂。解包后務(wù)必抽查幾張確認(rèn) mask 和 image 的內(nèi)容對齊不要只看 shape 就對上了。3. 數(shù)據(jù)可視化代碼落地疊加顯示、類別統(tǒng)計(jì)與超大圖瀏覽標(biāo)題里明確帶了「數(shù)據(jù)可視化代碼」說明這個(gè)數(shù)據(jù)集希望你在訓(xùn)練前先把數(shù)據(jù)看透。這個(gè)環(huán)節(jié)做扎實(shí)后面訓(xùn)練中的很多反?,F(xiàn)象一眼就能認(rèn)出來。3.1 圖像與掩碼疊加顯示最小可運(yùn)行腳本先給一個(gè)最直接的疊加可視化腳本。它能把細(xì)胞核區(qū)域用半透明紅色蓋在原圖上三張子圖分別是原圖、掩碼、融合結(jié)果一張圖看清標(biāo)簽質(zhì)量。import cv2 import numpy as np from matplotlib import pyplot as plt img cv2.imread(dataset/images/img_001.png) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) mask cv2.imread(dataset/masks/mask_001.png, cv2.IMREAD_GRAYSCALE) overlay img.copy() overlay[mask 0] (255, 0, 0) # 核區(qū)域染紅 blend cv2.addWeighted(img, 0.6, overlay, 0.4, 0) fig, axes plt.subplots(1, 3, figsize(15, 5)) axes[0].imshow(img) axes[0].set_title(Image) axes[1].imshow(mask, cmapgray) axes[1].set_title(Mask) axes[2].imshow(blend) axes[2].set_title(Overlay) plt.show()邏輯說明overlay直接拷貝原圖把掩碼非零像素位改成純紅再用addWeighted做 alpha 融合。核心是addWeighted(img, alpha1, overlay, alpha2, 0)輸出強(qiáng)度由兩個(gè)權(quán)重加權(quán)既能看清核的位置又不遮擋原圖紋理。參數(shù)說明0.6 / 0.4是原圖與紅層的混合比例。細(xì)胞核排布密集時(shí)把紅層權(quán)重調(diào)到 0.3 以下否則紅色連成片看不出單個(gè)核的輪廓核稀疏時(shí)用 0.5~0.6視覺上更清楚。mask 0兼容 0/1 和 0/255 兩種掩碼不用特意改。另外注意cvtColor那行OpenCV 讀圖是 BGRplt.imshow按 RGB 解釋不轉(zhuǎn)的話整張圖會(huì)偏藍(lán)偏紅這是疊加可視化最常見的翻車點(diǎn)。3.2 類別占比統(tǒng)計(jì)訓(xùn)練前先判斷類別失衡程度乳腺腫瘤細(xì)胞核分割里背景占比通常遠(yuǎn)高于核。不量化這個(gè)值就選損失函數(shù)大概率要走彎路。用幾行腳本把前景占比算出來import cv2 import glob import numpy as np ratios [] for p in sorted(glob.glob(dataset/masks/*.png)): m cv2.imread(p, cv2.IMREAD_GRAYSCALE) fg (m 0).sum() total m.size ratios.append(fg / total) ratios np.array(ratios) print(f樣本數(shù): {len(ratios)}, 前景占比均值: {ratios.mean():.4f}, 中位數(shù): {np.median(ratios):.4f}) print(f前景占比最低: {ratios.min():.4f}, 最高: {ratios.max():.4f})邏輯說明m 0統(tǒng)計(jì)前景像素除以總像素就是每張圖的前景占比。均值和中位數(shù)同時(shí)看能區(qū)分「整體都低」和「個(gè)別圖高、多數(shù)圖極低」兩種情況。病理圖中核密度差異很大乳腺腫瘤切片里不同視野的核占比可以差出 5 倍以上。參數(shù)說明如果前景占比均值低于 0.1整圖喂進(jìn)去訓(xùn)練大概率被背景主導(dǎo)。這個(gè)腳本的輸出是你決定「整圖訓(xùn)練還是 patch 采樣」的直接依據(jù)別跳過。3.3 超大病理圖的切片瀏覽網(wǎng)格預(yù)覽避免黑屏和 OOM病理掃描圖經(jīng)常是幾千乘幾千像素直接plt.imshow(img)要么內(nèi)存爆掉要么顯示出來糊成一片。常見做法是把圖像切成固定大小的 tile網(wǎng)格顯示前幾塊def grid_preview(img, mask, tile512, max_cols3): h, w mask.shape[:2] rows min(h // tile, 3) cols min(w // tile, max_cols) fig, axes plt.subplots(rows, cols, figsize(4 * cols, 4 * rows)) for i in range(rows): for j in range(cols): p img[i*tile:(i1)*tile, j*tile:(j1)*tile] m mask[i*tile:(i1)*tile, j*tile:(j1)*tile] axes[i, j].imshow(p) axes[i, j].imshow(m, cmapjet, alpha0.3, interpolationnearest) plt.show() grid_preview(img, mask, tile512, max_cols3)邏輯說明按tile尺寸把圖切成網(wǎng)格只顯示前三行前幾列。用cmapjet疊加在圖上核區(qū)域被高亮成偽彩色邊界看得比純紅更清楚。interpolationnearest避免縮放時(shí)出現(xiàn)插值模糊讓縮略圖保持銳利。參數(shù)說明tile512適合顯示中等細(xì)節(jié)想看核形態(tài)用 256想看組織排布用 512 或 1024。max_cols3控制列數(shù)網(wǎng)格總數(shù)控制在 9 個(gè)以內(nèi)顯示速度才跟得上。這個(gè)函數(shù)后續(xù)每次訓(xùn)練迭代后輸出預(yù)測結(jié)果時(shí)也能復(fù)用。4. 把乳腺腫瘤細(xì)胞核分割2類數(shù)據(jù)集跑進(jìn) U-Net數(shù)據(jù)劃分、損失函數(shù)與評估指標(biāo)可視化確認(rèn)過標(biāo)簽質(zhì)量之后再進(jìn)訓(xùn)練。這一章給出能直接抄的數(shù)據(jù)劃分、Dataset、損失函數(shù)和評估代碼按順序組合就能跑通一個(gè)基礎(chǔ) U-Net。4.1 數(shù)據(jù)劃分按病例分組而不是隨機(jī)打散乳腺病理圖像有一個(gè)容易被忽略的問題同一個(gè)病人的多張切片非常相似隨機(jī)打散會(huì)讓同一病例出現(xiàn)在訓(xùn)練集和驗(yàn)證集造成指標(biāo)虛高。正確做法是按文件名前綴里的病例 ID 分組。import glob import os from collections import defaultdict imgs sorted(glob.glob(dataset/images/*.png)) groups defaultdict(list) for p in imgs: base os.path.basename(p) case base.split(_)[0] # 按文件名前綴取病例 ID groups[case].append(p) cases sorted(groups.keys()) print(f病例數(shù): {len(cases)}, 圖像總數(shù): {len(imgs)}) train_cases cases[:int(len(cases) * 0.8)] val_cases cases[int(len(cases) * 0.8):int(len(cases) * 0.9)] test_cases cases[int(len(cases) * 0.9):] train_imgs [p for c in train_cases for p in groups[c]] val_imgs [p for c in val_cases for p in groups[c]] test_imgs [p for c in test_cases for p in groups[c]]邏輯說明base.split(_)[0]依賴文件命名帶病例前綴。實(shí)際數(shù)據(jù)集命名如果不規(guī)則先打印前 20 個(gè)文件名人工確認(rèn)分隔符。按病例分組劃分后同病人的所有切片只進(jìn)一個(gè)集合驗(yàn)證結(jié)果才具備參考意義。參數(shù)說明8:1:1 是醫(yī)學(xué)圖像分割的常見比例樣本量小于幾百張時(shí)可以把測試集并進(jìn)驗(yàn)證集留出更多訓(xùn)練數(shù)據(jù)。cases列表要先排序再切片保證每次運(yùn)行劃分一致。4.2 預(yù)處理與在線增強(qiáng)翻轉(zhuǎn)旋轉(zhuǎn)安全隨機(jī)裁剪要謹(jǐn)慎病理圖像沒有上下左右的方向性翻轉(zhuǎn)和 90 度旋轉(zhuǎn)是絕對安全的增強(qiáng)。隨機(jī)裁剪則要小心整圖里背景太多時(shí)隨機(jī)裁到的地方大概率不含核相當(dāng)于給模型喂了大量空白樣本。import random import numpy as np def train_aug(img, mask): if random.random() 0.5: img img[:, ::-1] mask mask[:, ::-1] k random.choice([0, 1, 2, 3]) img np.rot90(img, k) mask np.rot90(mask, k) return img.copy(), mask.copy()邏輯說明水平翻轉(zhuǎn)和 90 度旋轉(zhuǎn)只改變像素位置、不改變像素值對染色組織切片的語義完全友好。np.rot90的k取 0~3相當(dāng)于隨機(jī)旋轉(zhuǎn) 0/90/180/270 度。返回拷貝是防止后續(xù) numpy 操作共享內(nèi)存導(dǎo)致的隱式修改。參數(shù)說明這套增強(qiáng)只適合訓(xùn)練階段。驗(yàn)證和測試時(shí)不要做任何隨機(jī)操作最多做固定的中心裁剪。醫(yī)學(xué)圖像分割里「翻轉(zhuǎn)增強(qiáng)」幾乎無成本但不要加過強(qiáng)的顏色抖動(dòng)——病理圖的染色有一定標(biāo)準(zhǔn)顏色擾動(dòng)過大反而破壞核與背景的判別特征。4.3 損失函數(shù)單獨(dú)用 BCE 為什么會(huì)在核分割上失效細(xì)胞核分割的前景占比常低于 10%直接BCEWithLogitsLoss會(huì)讓模型傾向于把一切預(yù)測為背景因?yàn)檫@樣 loss 已經(jīng)很低。Dice Loss 直接優(yōu)化重合度對類別失衡天然穩(wěn)健。常用做法是 BCE 和 Dice 組合。import torch import torch.nn as nn class DiceLoss(nn.Module): def __init__(self, smooth1.0): super().__init__() self.smooth smooth def forward(self, logits, target): prob torch.sigmoid(logits).view(logits.size(0), -1) target target.view(target.size(0), -1).float() intersection (prob * target).sum(dim1) dice (2 * intersection self.smooth) / ( prob.sum(dim1) target.sum(dim1) self.smooth ) return 1 - dice.mean() class BCEPlusDice(nn.Module): def __init__(self, bce_weight0.5): super().__init__() self.bce nn.BCEWithLogitsLoss() self.dice DiceLoss() self.bce_weight bce_weight def forward(self, logits, target): return self.bce_weight * self.bce(logits, target) (1 - self.bce_weight) * self.dice(logits, target)邏輯說明DiceLoss把 logits 經(jīng) sigmoid 轉(zhuǎn)成概率然后壓平成(batch, -1)計(jì)算每張圖的 Dice 系數(shù)取平均后返回1 - dice作為損失。BCEPlusDice把兩個(gè)損失按權(quán)重相加BCE 提供像素級梯度Dice 緩解類別失衡。參數(shù)說明smooth1.0防止分子分母都為 0 時(shí)的除零錯(cuò)誤。bce_weight常見取 0.5如果你的數(shù)據(jù)集前景占比特別低低于 5%可以把bce_weight調(diào)到 0.3讓 Dice 主導(dǎo)前景占比較高時(shí)回到 0.5~0.7。4.4 評估指標(biāo)Dice、IoU 與像素精度別只看準(zhǔn)確率細(xì)胞核分割這種極不平衡場景準(zhǔn)確率沒有任何參考價(jià)值——全預(yù)測成背景也能到 90% 以上。評估至少看三個(gè)指標(biāo)def compute_metrics(pred, target, threshold0.5): pred_bin (pred threshold).astype(np.uint8) target_bin (target 0).astype(np.uint8) inter np.logical_and(pred_bin, target_bin).sum() pred_sum pred_bin.sum() target_sum target_bin.sum() dice 2 * inter / (pred_sum target_sum 1e-6) iou inter / (pred_sum target_sum - inter 1e-6) acc (pred_bin target_bin).mean() return {dice: dice, iou: iou, acc: acc}邏輯說明pred是模型輸出的概率圖 0~1target是掩碼。比較前先把預(yù)測按閾值二值化把 target 統(tǒng)一成 0/1。三個(gè)指標(biāo)一起看尤其是 IoU 和 Dice 同時(shí)偏低而 acc 很高時(shí)說明模型在退化到背景預(yù)測。參數(shù)說明threshold0.5不是唯一選擇。核分割里提高閾值到 0.6~0.7 能顯著減少假陽性代價(jià)是召回下降。判斷閾值是否合理的方法很簡單看驗(yàn)證集上預(yù)測概率的直方圖如果大部分預(yù)測值集中在 0.1 以下和 0.9 以上說明模型很自信閾值怎么選都不太影響如果大量預(yù)測值在 0.4~0.6 徘徊調(diào)閾值就是在碰運(yùn)氣不如回去修損失函數(shù)。這一步很玄學(xué)但先把數(shù)據(jù)看透就能少調(diào)參。5. 避坑與排查乳腺腫瘤細(xì)胞核分割數(shù)據(jù)集上的 5 個(gè)翻車點(diǎn)5.1 圖像和掩碼錯(cuò)位訓(xùn)練正常但可視化完全對不上現(xiàn)象訓(xùn)練 loss 在下降但把原圖和掩碼疊加顯示發(fā)現(xiàn)掩碼的核位置和圖像里的核位置根本對不上有時(shí)掩碼里出現(xiàn)圖像中沒有的結(jié)構(gòu)。原因兩個(gè)常見來源。一是glob.glob和os.listdir的排序不一致圖像按文件名排序、掩碼按目錄讀入順序拼接時(shí)錯(cuò)位二是數(shù)據(jù)集本身在打包時(shí) images 和 masks 不是按同一順序?qū)С龅?。解決用文件名顯式匹配而不是依賴目錄讀入順序。統(tǒng)一走sorted(glob.glob())并且訓(xùn)練腳本里加一個(gè)斷言檢查圖像與掩碼的文件名一一對應(yīng)后把路徑配對。進(jìn)階做法是隨機(jī)抽 20 組圖疊加保存成一張大圖人眼掃一遍比任何斷言都可靠。5.2 背景占比太高準(zhǔn)確率 98% 但 Dice 為 0現(xiàn)象訓(xùn)練完成后驗(yàn)證集準(zhǔn)確率很高Dice 卻是 0 或接近 0??雌饋聿豢伤甲h其實(shí)模型把整張圖都預(yù)測成了背景。原因背景像素占比超過 90% 時(shí)BCEWithLogitsLoss會(huì)走進(jìn)「全預(yù)測為背景」的局部最優(yōu)因?yàn)檫@個(gè)策略已經(jīng)把 loss 壓得很低。準(zhǔn)確率高只是類別失衡的假象。解決換用 4.3 節(jié)的 DiceLoss 或 BCEDice 組合損失更徹底的辦法是放棄整圖訓(xùn)練改為按核密度采樣 patch。我一般讓可視化統(tǒng)計(jì)腳本先輸出前景占比低于 10% 就直接走 patch 采樣路線不進(jìn)整圖訓(xùn)練。5.3 掩碼邊界粗細(xì)不一致Dice 卡在 0.7 左右上不去現(xiàn)象驗(yàn)證 Dice 從頭到尾卡在 0.7 附近換更強(qiáng)的網(wǎng)絡(luò)也突破不了??梢暬A(yù)測結(jié)果發(fā)現(xiàn)預(yù)測的核邊界普遍比真實(shí)掩碼細(xì)或粗一圈。原因標(biāo)注源的邊界一致性差。顯微鏡圖像里細(xì)胞核邊界本身就模糊標(biāo)注工具導(dǎo)出的輪廓線粗細(xì)不一致加上不同標(biāo)注人員的標(biāo)準(zhǔn)不同會(huì)導(dǎo)致 GT 邊界出現(xiàn)系統(tǒng)性偏差。模型學(xué)到的「正確邊界」和 GT 之間有固定偏移Dice 被抬高不了。解決先做標(biāo)注一致性檢查——統(tǒng)計(jì)所有掩碼里連通域的面積分布如果同一類結(jié)構(gòu)的面積標(biāo)準(zhǔn)差異常大考慮對掩碼統(tǒng)一做形態(tài)學(xué)腐蝕或膨脹校準(zhǔn)。訓(xùn)練層面可以引入邊界損失或在 Dice 基礎(chǔ)上加邊界感知項(xiàng)。最務(wù)實(shí)的做法是把 GT 統(tǒng)一腐蝕 1~2 個(gè)像素再做訓(xùn)練犧牲一點(diǎn)邊界精度換取訓(xùn)練穩(wěn)定性。5.4 同一病例被隨機(jī)劃分到訓(xùn)練和驗(yàn)證集訓(xùn)練指標(biāo)虛高現(xiàn)象訓(xùn)練時(shí)驗(yàn)證 Dice 接近 0.9自認(rèn)為模型已經(jīng)收斂拿到新數(shù)據(jù)上一測只有 0.6落差巨大。原因文件命名帶病例前綴時(shí)隨機(jī)劃分會(huì)把同一病人的多張切片分到訓(xùn)練集和驗(yàn)證集。病理切片不同區(qū)域紋理高度相似模型在訓(xùn)練時(shí)已經(jīng)「見過」驗(yàn)證集的同源圖像評估結(jié)果虛高。解決按 4.1 節(jié)的病例分組劃分保證同一個(gè)case前綴的所有圖只進(jìn)一個(gè)集合。如果數(shù)據(jù)集本身沒有病例 ID按圖像采集時(shí)間或批次近似分組也行——重點(diǎn)是不讓模型「背題」。5.5 推理輸出全黑或全是噪點(diǎn)歸一化和閾值問題現(xiàn)象訓(xùn)練正常推理時(shí)積壓出的掩碼要么全黑、要么全白、要么滿是孤立噪點(diǎn)。同一個(gè)腳本在驗(yàn)證集上表現(xiàn)正常換到新圖上就失控。原因三個(gè)典型來源。一是推理時(shí)輸入的預(yù)處理和訓(xùn)練不一致比如訓(xùn)練用的 0~1 歸一化推理時(shí)忘了除以 255二是模型輸出的是 logits 而不是概率直接用 0.5判閾值logits 都在 0 附近時(shí)全被判成背景三是滑窗推理拼接時(shí)沒有處理重疊區(qū)的邊界。解決把預(yù)處理和推理封裝成同一個(gè)函數(shù)禁止在推理腳本里手動(dòng)寫一遍。模型輸出先過sigmoid再判閾值?;巴评頃r(shí)重疊區(qū)域取平均值而不是覆蓋能顯著減少拼接接縫處的噪點(diǎn)。如果二值化后仍有大量孤立小點(diǎn)加一個(gè)基于連通域面積的后處理去掉小于 10 像素的連通域。6. 進(jìn)階驗(yàn)證技巧用錯(cuò)誤疊加圖和滑窗推理反推數(shù)據(jù)集質(zhì)量6.1 錯(cuò)誤疊加圖十秒定位難例和標(biāo)注缺陷只在下游用「預(yù)測對了幾張」來評價(jià)數(shù)據(jù)集是不夠的。我習(xí)慣把每個(gè)驗(yàn)證樣本輸出成一張錯(cuò)誤疊加圖真陽性畫綠、假陰性畫紅、假陽性畫黃。看分布比看數(shù)字有信息量得多。def error_map(gt, pred): h, w gt.shape vis np.zeros((h, w, 3), dtypenp.uint8) tp np.logical_and(gt 1, pred 1) fn np.logical_and(gt 1, pred 0) fp np.logical_and(gt 0, pred 1) vis[tp] (0, 255, 0) vis[fn] (255, 0, 0) vis[fp] (0, 255, 255) return vis邏輯說明三通道分別標(biāo)記三類區(qū)域。紅色集中在某個(gè)固定角落基本可以判斷是染色不均或漏標(biāo)黃色連成片說明閾值偏低模型在正常組織區(qū)域產(chǎn)生了系統(tǒng)性誤報(bào)。6.2 滑窗推理處理超大病理圖重疊融合去接縫整張病理圖直接推理顯存不夠切塊推理又看這種錯(cuò)位現(xiàn)象。常見做法是滑窗帶重疊輸出時(shí)對重疊區(qū)域取平均值。tile 取 512、重疊 64能平衡顯存和拼接質(zhì)量重疊太小接縫明顯重疊太大推理時(shí)間翻倍。我一般保留最后一張實(shí)驗(yàn)記錄卡包含數(shù)據(jù)版本、病例劃分方式、增強(qiáng)開關(guān)、損失函數(shù)、優(yōu)化器、學(xué)習(xí)率和三次重復(fù)實(shí)驗(yàn)的 Dice 均值。項(xiàng)記錄值數(shù)據(jù)版本解包后 PNG v1前景占比 11.2%劃分方式按 case 前綴8:1:1損失BCEDicebce_weight0.4優(yōu)化器AdamWlr1e-4Dice / IoU0.842 / 0.731這套驗(yàn)證習(xí)慣幫我避過很多次「指標(biāo)好看但實(shí)際不可用」的假收尾。最后給一點(diǎn)個(gè)人經(jīng)驗(yàn)?zāi)玫竭@類數(shù)據(jù)集先花一小時(shí)做可視化和統(tǒng)計(jì)再?zèng)Q定網(wǎng)絡(luò)和損失函數(shù)這個(gè)順序省下的返工時(shí)間比想象中多得多。希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取