在虚拟环境中安装nnUNet
创建虚拟环境
#进入conda环境:从anaconda3/bin/中进入终端
#创建虚拟环境nnunet
conda create nnunet python=3.8
#nnunet在anaconda3的envs文件夹中
#进入虚拟环境
source activate nnunet
#退出虚拟环境
conda deactivate
查看配置
>>> nvidia-smi
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 515.86.01 Driver Version: 515.86.01 CUDA Version: 11.7 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|===============================+======================+======================|
| 0 NVIDIA GeForce ... Off | 00000000:01:00.0 On | N/A |
| 0% 49C P8 39W / 370W | 575MiB / 24576MiB | 0% Default |
| | | N/A |
+-------------------------------+----------------------+----------------------+
+-----------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=============================================================================|
| 0 N/A N/A 1083 G /usr/lib/xorg/Xorg 53MiB |
| 0 N/A N/A 2122 G /usr/lib/xorg/Xorg 385MiB |
| 0 N/A N/A 2265 G /usr/bin/gnome-shell 51MiB |
| 0 N/A N/A 149785 G ...nlogin/bin/sunloginclient 13MiB |
| 0 N/A N/A 150173 G ...nlogin/bin/sunloginclient 10MiB |
| 0 N/A N/A 486236 G ...mviewer/tv_bin/TeamViewer 20MiB |
| 0 N/A N/A 486392 G /usr/lib/firefox/firefox 11MiB |
+-----------------------------------------------------------------------------+
进入python环境
python
#输入python就可以进入python环境
import torch
>>> print(torch.__version__)
1.13.1
#退出python
quit()
在.Bashrc中设置环境变量export OMP_NUM_THREADS=1
验证是否安装了适合的pytorch版本:
python -c 'import torch;print(torch.backends.cudnn.version())'
python -c 'import torch;print(torch.__version__)'
8200 1.11.0+cu113
安装nnUNet:
用作标准化基线、开箱即用的分割算法或使用预训练模型运行推理:
pip install nnunet
用作集成框架(这将在您的计算机上创建 nnU-Net 代码的副本,以便您可以根据需要对其进行修改)
git clone https://github.com/MIC-DKFZ/nnUNet.git
cd nnUNet
pip install -e .
设置文件保存路径
创建隐藏层(隐藏层的作用??)
pip install --upgrade git+https://github.com/FabianIsensee/hiddenlayer.git@more_plotted_details#egg=hiddenlayer
数据的用法
预处理
nnunet首先会提取数据指纹,根据数据大小等性质创建三个Unet:2D-Unet,3D-Unet and 3D U-Net cascade
数据存放在:nnUNet_raw_data_base/nnUNet_raw_data/TaskXXX_MYTASK
运行:
nnUNet_plan_and_preprocess -t XXX --verify_dataset_integrity
XXX是Task的整数名称
输出数据存放在:nnUNet_preprocessed,这一步仅仅处理了training cases
同时会生成plan.pkl,这个文件包含分割的管线配置,并且会被nnUNetTrainer读取。
如果在预处理期间 RAM 用完,则可能需要调整进程数 与 和 选项一起使用。nnUNet_plan_and_preprocess``-h``-tl``-tf
创建的U-net配置会储存在nnUNet_preprocessed/TaskXXX_MYTASK
模型训练
对于特定的数据,不一定会创建所有的三个网络,因为对于图像尺寸较小的数据集,UNet可以省略级联,因为全分辨率的patch已经能够覆盖大部分输入图像。
nnUNet_train CONFIGURATION TRAINER_CLASS_NAME TASK_NAME_OR_ID FOLD --npz (additional options)
CONFIGURATION:可以指定2d,3d_fullres, 3d_lowers。TRAINER_CLASS_NAME:一般指定nnUNetTrainerV2;如果自定义训练器,可以加在 TRAINER_CLASS_NAME 处自定义指定。TASK_NAME_OR_ID:训练的数据集,FOLD:整数
nnUNet每50个epoch储存一个检查点,--npz:使得模型在最后的验证中保存softmax输出。
2d UNet
nnUNet_train 2d nnUNetTrainerV2 TaskXXX_MYTASK FOLD --npz
3D full resolution U-Net
nnUNet_train 3d_fullres nnUNetTrainerV2 TaskXXX_MYTASK FOLD --npz
3D U-Net cascade
3D low resolution UNet
nnUNet_train 3d_lowres nnUNetTrainerV2 TaskXXX_MYTASK FOLD --npz
3D full resolition UNet
nnUNet_train 3d_cascade_fullres nnUNetTrainerV2CascadeFullRes TaskXXX_MYTASK FOLD --npz
※ 3D full resolution需要把3D low resolution UNet的所有训练集跑完才能运行。
训练好的模型被保存在:RESULTS_FOLDER/nnUNet folder
每次训练都得到一个自动命名的文件夹:nnUNet_preprocessed/CONFIGURATION/TaskXXX_MYTASKNAME/TRAINER_CLASS_NAME__PLANS_FILE_NAME/FOLD
以Task002_Heart为例:
RESULTS_FOLDER/nnUNet/
├── 2d
│ └── Task02_Heart
│ └── nnUNetTrainerV2__nnUNetPlansv2.1
│ ├── fold_0
│ ├── fold_1
│ ├── fold_2
│ ├── fold_3
│ └── fold_4
├── 3d_cascade_fullres
├── 3d_fullres
│ └── Task02_Heart
│ └── nnUNetTrainerV2__nnUNetPlansv2.1
│ ├── fold_0
│ │ ├── debug.json
│ │ ├── model_best.model
│ │ ├── model_best.model.pkl
│ │ ├── model_final_checkpoint.model
│ │ ├── model_final_checkpoint.model.pkl
│ │ ├── network_architecture.pdf
│ │ ├── progress.png
│ │ └── validation_raw
│ │ ├── la_007.nii.gz
│ │ ├── la_007.pkl
│ │ ├── la_016.nii.gz
│ │ ├── la_016.pkl
│ │ ├── la_021.nii.gz
│ │ ├── la_021.pkl
│ │ ├── la_024.nii.gz
│ │ ├── la_024.pkl
│ │ ├── summary.json
│ │ └── validation_args.json
│ ├── fold_1
│ ├── fold_2
│ ├── fold_3
│ └── fold_4
└── 3d_lowres
3d_lowers和3d_cascade_fullres文件夹没有输出是因为训练数据集没有触发级联。
每个模型训练输出文件夹都会创建一下文件:
debug.json:包含用于训练此模型的蓝图和推断参数的摘要。不容易阅读, 但对于调试非常有用
model_best.model / model_best.model.pkl:训练期间识别的最佳模型的检查点文件。现在不用。
model_final_checkpoint.model / model_final_checkpoint.model.pkl:最终模型的检查点文件(训练后) 已结束)。这是用于验证和推理的内容。
network_architecture.pdf(仅当安装了隐藏层时!):一个PDF文档,其中包含网络架构图。
progress.png:训练期间训练(蓝色)和验证(红色)的损失图。
扩展/更改nnUNet
要扩展更改nnUNet,必须使用git clone pip install -e命令安装nnunet。
对蓝图参数的更改
本节提供如何实现损失函数、训练计划、学习率、优化器、一些架构参数、数据增强的改变。这些参数都是nnU-Net trainer class的一部分。适用于2D\3D低分辨率和3D全分辨率的默认训练器类别是nnUNetTrainerV2。级联训练中,3D全分辨率的默认训练器类别是nnUNetTrainerV2CascadeFullRes 。nnUNetTrainerV2CascadeFullRes 继承了nnUNetTrainerV2。
创建一个新的训练器,只需要继承nnUNetTrainerV2或nnUNetTrainerV2CascadeFullRes。创建的训练器需要在nnunet.training.network_training文件夹下。
对inferred parameters的修改
推断参数是在数据集指纹上生成的,是训练集的低维特征显示:图像形状、体素间距、强度信息。数据集指纹由DatasetAnalyzer生成(nnunet/preprocessing/nnUNet_plan_and_preprocess)
nnUNet_plan_and_preprocess使用ExperimentPlanners来运行适配过程。默认的ExperimentPlanner:2D U-Net使用ExperimentPlanner2D_v21,3D全分辨率U-Net和级联U-Net使用ExperimentPlanner3D_v21。ExperimentPlanner之间也互相继承,只需要给ExperimentPlanners一个自定义的名字,并且将其放在nnunet/experment_planning文件夹下。继承ExperimentPlanners的时候需要重写类变量。省略这步骤会重写plans file和处理后的数据。nnUNet_plan_and_preprocessself.data_identifierself.plans_fname
To train with your custom configuration, simply specify the correct plans identifier with when you call the command. The plans file also contains the data_identifier specified in your ExperimentPlanner, so the trainer class will automatically know what data should be used.-p``nnUNet_train(这段不太明白)
一种可行的改变inferred parameters的方法:改变批大小和patch大小的优先级(现在是patch size优先);改变空间信息处理,改变目标spacing的定义,使用不同策略找到3d低分辨率UNet配置。
nnunet/experiment_planning中的文件夹包含几个ExperimentPlanner,可以修改推断参数的各个方面。
如果希望运行不同的预处理,需要实现自己的预处理器类。ExperimentPlanner使用的预处理器类定义在preprocessor_name 这个类变量中。默认3D U-Net是使用preprocesor_name="GenericPreprocessor",2D U-Net 默认使用PreprocessorFor2D. 所有自定义的处理器都必须位于nnunet/preprocessing文件夹中。preprocessor_name被保存在plan文件中。对预处理的修改可以是对MRI图像添加偏置场矫正,CT与处理方案等。
使用不同的网络构架
实现任何新的分割网络都需要了解nnunet的结构(需要多少降采样操作、是否使用深度学习、卷积内核大小是多少),它要能够动态的改变拓补结构,此外还要能够生成一个可用于估计内存消耗的值。
为了解释这个过程,实现了一个带有参与编码器的UNet(FabiansUNet in generic_modular_residual_UNet.py)这个UNet有一个叫做use_this_for_3D_configuration的类变量(位于find_3d_configuration中的代码)。相应的ExperimentPlanner:ExperimentPlanner3DFabiansResUNet_v21(FabiansUNet.compute_approx_vram_consumption生成)将该值与当前配置的网络拓补生成的值比较,以确保满足GPU内存目标。
教程:edit_plans_filse
custom_spacing
custom_preprocessing
手动编辑plans文件
您可以脱离nnU-Net的默认配置,并使用不同的U-Net拓扑结构、批处理大小和patch大小。
nnUNet_plan_and_preprocess 的输出如下:
[{'batch_size': 2,
'num_pool_per_axis': [8, 8], #每个轴有八个池化操作
'patch_size': array([1280, 1024]),
'median_patient_size_in_voxels': array([ 1, 1500, 1500]),
'current_spacing': array([999., 1., 1.]),
'original_spacing': array([999., 1., 1.]),
'pool_op_kernel_sizes': [[2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]], #池化核个数和大小,如果'num_pool_per_axis'变化为7,池化核个数相应的需要变化为7个
'conv_kernel_sizes': [[3, 3], [3, 3], [3, 3], [3, 3], [3, 3], [3, 3], [3, 3], [3, 3], [3, 3]], #卷积核大小和个数,好像必须比池化核多一个,
'do_dummy_2D_data_aug': False}]
这些都保存在计划文件的关键字“plans_per_stage”中。
决定一个像素是否属于‘road’并不依赖于大patch提供的大型上下文信息,相反,可以通过更多的定位信息确定。而batch太小可能导致网络的变化小,因此可以增大batch_size减小patch大小。
from batchgenerators.utilities.file_and_folder_operations import *
import numpy as np
from nnunet.paths import preprocessing_output_dir
task_name = 'Task120_MassRoadsSeg'
'''
这个文件需要在运行了data_conversion和nnUNet_plan_and_preproces之后运行
'''
plans_fname = join(preprocessing_output_dir, task_name, 'nnUNetPlansv2.1_plans_2D.pkl')
plans = load_pickle(plans_fname)
plans['plans_per_stage'][0]['batch_size'] = 12
plans['plans_per_stage'][0]['patch_size'] = np.array((512, 512))
plans['plans_per_stage'][0]['num_pool_per_axis'] = [7, 7]
plans['plans_per_stage'][0]['pool_op_kernel_sizes'] = [[2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]]
plans['plans_per_stage'][0]['conv_kernel_sizes'] = [[3, 3], [3, 3], [3, 3], [3, 3], [3, 3], [3, 3], [3, 3], [3, 3]]
'''
池化核大小为1会导致轴向没有池化,为3会导致丢失信息太多
'''
'''
用新名字保存plans文件,新文件需要以'_plans_2D.pkl'结尾
'''
# save the plans under a new plans name. Note that the new plans file must end with _plans_2D.pkl!
save_pickle(plans, join(preprocessing_output_dir, task_name, 'nnUNetPlansv2.1_ps512_bs12_plans_2D.pkl'))
nnUNet_train通过-p参数支持自定义计划,自定义计划的名称是除了'plans_2D.pkl'的一切。
nnUNet_train 2d nnUNetTrainerV2 120 FOLD -p nnUNetPlansv2.1_ps512_bs
对每个plan(变体和原始plan)运行5折运算,比较结果可以使用如下命令:
nnUNet_determine_postprocessing -t 120 -tr nnUNetTrainerV2 -p nnUNetPlansv2.1_ps512_bs12 -m 2d
这将在训练输出目录中创建一个cv_niftis_raw和cv_niftis_postprocessed子文件夹。每个文件夹中都有一个summary.json文件。文件最底部的矩阵是平均值,可以用来判断实验优劣。建议使用non_postprocessed summary.json,(位于cv_niftis_raw)。因为确定的后处理后的数据集可能overfit。
当使用3d_plans('nnUNetPlansv2.1_plans_3D.pk')时,3D_fullres和3D_lowers被编码在同一个文件中,如果len(plans['plans_per_stage']) == 2则[0]是3d_lowers,[1]是3d_fullres。如果len(plans['plans_per_stage']) == 1[0]就是3d_fullres和3d_cascade_fullres共用的参数。
pool_op_kernel_sizes和 patch_size共同决定bottleneck处的feature map。这里展示feature map的大小
print(plans['plans_per_stage'][0]['patch_size'] / np.prod(plans['plans_per_stage'][0]['pool_op_kernel_sizes'], 0))
[4., 4.]
这里看到的数字一定要是整数,并且bottleneck永远不会小于4.
不能改变'current_spacing'
feature map
由通道数决定,一个通道对应的三维图像就是一个feature map。因此也可以说有几个卷积核就有几个feature map。
因为一个卷积核代表的是需要识别的一个特征,而卷积操作后的体素数值越大就与这个卷积核所代表的特征越相似。
上文中指的是feature map的大小[2,2]
bottleneck
这里可以理解为池化层,用来降低图像大小。