nnU-Net介绍及使用

2023年08月15日 由 alex 发表 420 0

nnU-Net,它是语义图像分割中一个非常强大的基线。


在本文中,您将了解如下:


1. 对nnU-Net的主要贡献进行简明概述。


2. 了解如何将nnU-Net应用于您自己的数据集。


nnU-Net简史


作为公认的最先进的图像分割模型,nnU-Net在2D和3D图像处理方面都是一股不屈不挠的力量。它的性能非常强大,可以作为新的计算机视觉体系结构的基准。


这个强大的工具基于U-Net模型, U-Net于2015年首次亮相。名称“nnU-Net”代表“No New U-Net”,这是对其设计没有引入革命性建筑变化这一事实的认可。相反,它利用现有的U-Net结构,通过一套巧妙的优化策略,充分发挥其潜力。


与许多现代神经网络不同,nnU-Net不依赖于残差连接、密集连接或注意机制。它的优势在于其细致的优化策略,其中包括重采样、归一化、损失函数的明智选择、优化器设置、数据增强、基于补丁的推理和跨模型集成等技术。这种整体的方法允许nnU-Net突破原有的U-Net架构所能达到的极限。


探索nnU-Net中的多样化架构


虽然它看起来像是一个单一的实体,但nnU-Net实际上是三种不同类型的u - net的总称:


1


1. 2D U-Net:可以说是最著名的变种,它直接操作2D图像。


2. 3D U-Net:这是2D U-Net的扩展,能够通过应用3D卷积直接处理3D图像。


3. U-Net Cascade:该模型生成低分辨率的分割,并随后对其进行细化。


每种体系结构都有其独特的优势,并且不可避免地有一定的局限性。


例如,使用2D U-Net进行3D图像分割可能看起来违反直觉,但在实践中,它仍然可以非常有效。这是通过将3D体切割成2D平面来实现的。


虽然3D U-Net可能看起来更复杂,但考虑到其更高的参数数量,它并不总是最有效的解决方案。特别是,3D U-Net经常与各向异性作斗争,当空间分辨率沿着不同的轴(例如,沿着x轴1mm和沿着z轴1.2 mm)不同时,就会出现各向异性。


当处理大尺寸图像时,U-Net级联变体变得特别方便。它采用一个初步的模型来压缩图像,然后是一个标准的3D U-Net,输出低分辨率的分割。然后,生成的预测被放大,从而产生一个精细的、全面的输出。


1-2


通常,该方法涉及在nnU-Net框架内训练所有三种模型变体。接下来的步骤可能是在三人中选择表现最好的或采用集成技术。其中一项技术可能涉及整合2D和3D U-Nets的预测。


然而,值得注意的是,这个过程可能非常耗时(也很花钱,因为你需要GPU积分)。如果您的约束只允许训练单个模型,请不要担心。您可以选择只训练一个模型,因为集成模型只带来非常小的收益。


下表说明了与特定数据集相关的最佳表现模型变体:


1-3


网络拓扑的动态适应


考虑到图像大小的显著差异(考虑肝脏图像的中位数形状为482 × 512 × 512,而海马图像的中位数形状为36 × 50 × 35), nnU-Net智能地适应输入补丁大小和每轴池化操作的数量。这本质上意味着自动调整每个数据集的卷积层数,促进空间信息的有效聚合。除了适应不同的图像几何形状外,该模型还考虑了技术限制,例如可用内存。


值得注意的是,该模型并不直接对整个图像进行分割,而是对精心提取的具有重叠区域的补丁进行分割。随后对这些补丁的预测进行平均,从而得到最终的分割输出。


但是拥有大的补丁意味着更多的内存使用,并且批处理大小也消耗内存。所采取的权衡是始终优先考虑补丁大小(模型的容量)而不是批处理大小(仅对优化有用)。


下面是用于计算最优补丁大小和批大小的启发式算法:


1-4


这是不同数据集和输入维度的情况:


1-5


现在让我们回顾一下nnU-Net中使用的所有技术:


训练


所有模型都是从头开始训练的,并在训练集上使用五倍交叉验证进行评估,这意味着原始训练数据集被随机分为五个相等的部分,或“折叠”。在这个交叉验证过程中,其中四个折叠用于模型的训练,剩下的一个折叠用于评估或测试。这个过程然后重复五次,五次中的每一次都被用作评估集。


对于损失,我们使用骰子和交叉熵损失的组合。这是图像分割中非常常见的损失。


1-6


数据增强技术


nnU-Net有非常强大的数据增强管道。作者使用了随机旋转、随机缩放、随机弹性变形、伽马校正和镜像。


1-7


1-8


基于补丁的推理


正如我们所说,该模型不是直接在全分辨率图像上进行预测,而是在提取的斑块上进行预测,然后将预测汇总。


它看起来是这样的:


1-9


注意:图片中间的补丁比旁边的补丁权重更大,因为它们包含更多的信息,模型对它们的处理效果更好


两两模型集成


1-10


如果你还记得的话,我们可以训练3种不同的模型,2D, 3D和级联。但是当我们进行推理时我们一次只能使用一个模型,对吧?


事实证明,不同的模型有不同的优点和缺点。因此,我们实际上可以将几个模型的预测结合起来,如果一个模型非常自信,我们就会优先考虑它的预测。


nnU-Net测试了3个可用模型中的2个模型的每种组合,并挑选出最好的一个。


实际上,有两种方法可以做到这一点:


1. 硬投票:对于每个像素,我们查看2个模型输出的所有概率,并选择概率最高的类。

 

2. 软投票:对于每个像素,我们对模型的概率取平均值,然后取最大概率的类。


实践


如果你不明白第一部分,不用担心,这是实践部分,你只需要跟着我,你仍然会得到最好的结果。


你需要GPU来训练模型,否则模型无法工作。你既可以在本地做,也可以在Google Collab上做,别忘了修改运行时>GPU


首先,你需要有一个数据集准备好输入图像和它们相应的分割。你可以按照我的教程下载这个现成的3D大脑分割数据集,然后你可以用你自己的数据集替换它。


下载数据


首先,您应该下载数据并将其放入data文件夹中,将包含分割的两个文件夹命名为“input”和“ground_truth”。


在本教程的其余部分,我将使用MindBoggle数据集进行图像分割。


我们得到了大脑的3D核磁共振扫描,我们想要分割白质和灰质:


1-11


它应该是这样的:


1-12


设置主目录


如果你在Google Colab上运行这个,设置collab = True,否则设置collab = False


collab = True
import os
import shutil
#libraries
from collections import OrderedDict
import json
import numpy as np
#visualization of the dataset
import matplotlib.pyplot as plt
import nibabel as nib
if collab:
    from google.colab import drive
    drive.flush_and_unmount()
    drive.mount('/content/drive', force_remount=True)
    # Change "neurosciences-segmentation" to the name of your project folder
    root_dir = "/content/drive/MyDrive/neurosciences-segmentation"

else:
    # get the dir of the parent dir
    root_dir = os.getcwd()
input_dir = os.path.join(root_dir, 'data/input')
segmentation_dir = os.path.join(root_dir, 'data/ground_truth')
my_nnunet_dir = os.path.join(root_dir,'my_nnunet')
print(my_nnunet_dir)


现在我们要定义一个为我们创建文件夹的函数:


def make_if_dont_exist(folder_path,overwrite=False):
    """
    creates a folder if it does not exists
    input:
    folder_path : relative path of the folder which needs to be created
    over_write :(default: False) if True overwrite the existing folder
    """
    if os.path.exists(folder_path):
        if not overwrite:
            print(f'{folder_path} exists.')
        else:
            print(f"{folder_path} overwritten")
            shutil.rmtree(folder_path)
            os.makedirs(folder_path)
    else:
      os.makedirs(folder_path)
      print(f"{folder_path} created!")


我们使用这个函数来创建“my_nnunet”文件夹,所有内容都将保存在这里:


os.chdir (root_dir)
make_if_dont_exist (my_nnunet,覆盖= False)
os.chdir(“my_nnunet”)
print(f"当前工作目录:{os.getcwd()}")


库安装


现在我们要安装所有的需求。首先,让我们安装nnunet库。如果你用的是笔记本,在一个单元格里运行:


!pip install nnunet


否则,您可以直接从终端安装nnunet


pip install nnunet


现在我们要克隆nnUnet git库和NVIDIA apex。这包含训练脚本以及GPU加速器。


!git clone https://github.com/MIC-DKFZ/nnUNet.git
!git clone https://github.com/NVIDIA/apex
# repository dir is the path of the github folder
respository_dir = os.path.join(my_nnunet_dir,'nnUNet')
os.chdir(respository_dir)
!pip install -e
!pip install --upgrade git+https://github.com/nanohanno/hiddenlayer.git@bugfix/get_trace_graph#egg=hiddenlayer


创建文件夹


nnUnet需要非常具体的文件夹结构。


task_name = 'Task001' #change here for different task name
# We define all the necessary paths
nnunet_dir = "nnUNet/nnunet/nnUNet_raw_data_base/nnUNet_raw_data"
task_folder_name = os.path.join(nnunet_dir,task_name) 
train_image_dir = os.path.join(task_folder_name,'imagesTr') # path to training images
train_label_dir = os.path.join(task_folder_name,'labelsTr') # path to training labels
test_dir = os.path.join(task_folder_name,'imagesTs') # path to test images
main_dir = os.path.join(my_nnunet_dir,'nnUNet/nnunet') # path to main directory
trained_model_dir = os.path.join(main_dir, 'nnUNet_trained_models') # path to trained models


最初,nnU-Net是为十项全能的不同任务挑战而设计的。如果您有不同的任务,只需为所有任务运行此单元格。


# Creation of all the folders
overwrite = False # Set this to True if you want to overwrite the folders
make_if_dont_exist(task_folder_name,overwrite = overwrite)
make_if_dont_exist(train_image_dir, overwrite = overwrite)
make_if_dont_exist(train_label_dir, overwrite = overwrite)
make_if_dont_exist(test_dir,overwrite= overwrite)
make_if_dont_exist(trained_model_dir, overwrite=overwrite)


你现在应该有一个这样的结构:


1-13


设置环境变量


脚本需要知道将raw_data放在哪里,可以在哪里找到预处理数据,以及必须在哪里保存结果。


os.environ['nnUNet_raw_data_base'] = os.path.join(main_dir,'nnUNet_raw_data_base')
os.environ['nnUNet_preprocessed'] = os.path.join(main_dir,'preprocessed')
os.environ['RESULTS_FOLDER'] = trained_model_dir


将文件移动到正确的存储库中:


我们定义了一个函数,将图像移动到nnunet文件夹中正确的存储库中:


def copy_and_rename(old_location,old_file_name,new_location,new_filename,delete_original = False):
    shutil.copy(os.path.join(old_location,old_file_name),new_location)
    os.rename(os.path.join(new_location,old_file_name),os.path.join(new_location,new_filename))
    if delete_original:
        os.remove(os.path.join(old_location,old_file_name))


现在让我们对输入图像和真实图像运行这个函数:


list_of_all_files = os.listdir(segmentation_dir)
list_of_all_files = [file_name for file_name in list_of_all_files if file_name.endswith('.nii.gz')]

for file_name in list_of_all_files:
    copy_and_rename(input_dir,file_name,train_image_dir,file_name)
    copy_and_rename(segmentation_dir,file_name,train_label_dir,file_name)


现在我们必须重命名要被nnUnet格式接受的文件,例如subject.nii.gz将变成subject_0000.nii.gz


def check_modality(filename):
    """
    check for the existence of modality
    return False if modality is not found else True
    """
    end = filename.find('.nii.gz')
    modality = filename[end-4:end]
    for mod in modality:
        if not(ord(mod)>=48 and ord(mod)<=57): #if not in 0 to 9 digits
            return False
    return True
def rename_for_single_modality(directory):
    for file in os.listdir(directory):
        if check_modality(file)==False:
            new_name = file[:file.find('.nii.gz')]+"_0000.nii.gz"
            os.rename(os.path.join(directory,file),os.path.join(directory,new_name))
            print(f"Renamed to {new_name}")
        else:
            print(f"Modality present: {file}")
rename_for_single_modality(train_image_dir)
# rename_for_single_modality(test_dir)


设置JSON文件


你主要需要修改两件事:


1. 模态(如果是CT或MRI,这会改变归一化)


2. 标签:输入您自己的类


overwrite_json_file = True #make it True if you want to overwrite the dataset.json file in Task_folder
json_file_exist = False
if os.path.exists(os.path.join(task_folder_name,'dataset.json')):
    print('dataset.json already exist!')
    json_file_exist = True
if json_file_exist==False or overwrite_json_file:
    json_dict = OrderedDict()
    json_dict['name'] = task_name
    json_dict['description'] = "Segmentation of T1 Scans from MindBoggle"
    json_dict['tensorImageSize'] = "3D"
    json_dict['reference'] = "see challenge website"
    json_dict['licence'] = "see challenge website"
    json_dict['release'] = "0.0"
    ######################## MODIFY THIS ########################
    #you may mention more than one modality
    json_dict['modality'] = {
        "0": "MRI"
    }
    #labels+1 should be mentioned for all the labels in the dataset
    json_dict['labels'] = {
        "0": "Non Brain",
        "1": "Cortical gray matter",
        "2": "Cortical White matter",
        "3" : "Cerebellum gray ",
        "4" : "Cerebellum white"
    }
    #############################################################

    train_ids = os.listdir(train_label_dir)
    test_ids = os.listdir(test_dir)
    json_dict['numTraining'] = len(train_ids)
    json_dict['numTest'] = len(test_ids)
    #no modality in train image and labels in dataset.json
    json_dict['training'] = [{'image': "./imagesTr/%s" % i, "label": "./labelsTr/%s" % i} for i in train_ids]
    #removing the modality from test image name to be saved in dataset.json
    json_dict['test'] = ["./imagesTs/%s" % (i[:i.find("_0000")]+'.nii.gz') for i in test_ids]
    with open(os.path.join(task_folder_name,"dataset.json"), 'w') as f:
        json.dump(json_dict, f, indent=4, sort_keys=True)
    if os.path.exists(os.path.join(task_folder_name,'dataset.json')):
        if json_file_exist==False:
            print('dataset.json created!')
        else:
            print('dataset.json overwritten!')


将数据预处理为nnU-Net格式


这将创建nnU-Net格式的数据集


# -t 1 means "Task001", if you have a different task change it
!nnUNet_plan_and_preprocess -t 1 --verify_dataset_integrity


训练模型


要训练3D U-Net:


#train 3D full resolution U net
!nnUNet_train 3d_fullres nnUNetTrainerV2 1 0 --npz 


要训练2D U-Net:


# train 2D U net
!nnUNet_train 2d nnUNetTrainerV2 1 0 --npz


训练级联模型:


# train 3D U-net cascade
!nnUNet_train 3d_lowres nnUNetTrainerV2CascadeFullRes 1 0 --npz
!nnUNet_train 3d_fullres nnUNetTrainerV2CascadeFullRes 1 0 --npz


例如:


#train 3D full resolution U net
!nnUNet_train 3d_fullres nnUNetTrainerV2 1 0 --npz 


推理


现在我们可以运行推论:


result_dir = os.path.join(task_folder_name, 'nnUNet_Prediction_Results')
make_if_dont_exist(result_dir, overwrite=True)
# -i is the input folder
# -o is where you want to save the predictions
# -t 1 means task 1, change it if you have a different task number
# Use -m 2d, or -m 3d_fullres, or -m 3d_cascade_fullres
!nnUNet_predict -i /content/drive/MyDrive/neurosciences-segmentation/my_nnunet/nnUNet/nnunet/nnUNet_raw_data_base/nnUNet_raw_data/Task001/imagesTs -o /content/drive/MyDrive/neurosciences-segmentation/my_nnunet/nnUNet/nnunet/nnUNet_raw_data_base/nnUNet_raw_data/Task001/nnUNet_Prediction_Results -t 1 -tr nnUNetTrainerV2 -m 2d -f 0  --num_threads_preprocessing 1


预测的可视化


首先让我们检查一下训练损失。这看起来很健康,我们有一个骰子得分Dice Score > 0.9(绿色曲线)。


这对于如此少的工作和3D神经成像分割任务来说确实是非常出色的。


1-14


我们来看一个示例:


1-15


很明显,这个模型已经有效地学会了如何高精度地分割大脑图像。虽然可能存在一些小的缺陷,但重要的是,图像分割领域正在迅速发展,我们正在朝着完美迈进。






文章来源:https://medium.com/towards-data-science/the-ultimate-guide-to-nnu-net-for-state-of-the-art-image-segmentation-6dda7f44b935
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消