论文链接:https://arxiv.org/abs/2206.06665
代码链接:https://github.com/xmed-lab/OEEM
第一步:新建虚拟环境
第二步:下载百度网盘资源,包括数据集及权重,并建立软连接
此处建议将原有权重文件复制一份做备份,因为新生成的权重文件可能会覆盖掉原始的文件。这里需要注意的是,classification的weights和segmentation的weights是link到同一个地方的,这是没有问题的。
第三步:训练
1、训练分类网络
python classification/train.py -d 0 -m res38d
其中d是device的意思,如果想使用第5 6 7号GPU,则代码为:
CUDA_VISIBLE_DEVICES=5,6,7 python classification/train.py -d 0 1 2 -m res38d
而不建议使用:
python classification/train.py -d 5 6 7 -m res38d
默认是batch_size=20, epoch=20,一个epoch大约8分钟,根据实际GPU使用情况可以适当增大batch_size,我将batch_size设置为64,即在命令行添加-b 64(我这里batch_size最多设置64,再多就报错内存不够了)。如果是在后台执行命令,可以执行:
CUDA_VISIBLE_DEVICES=5,6,7 nohup python classification/train.py -d 0 1 2 -m res38d -b 64 >cls_train.log 2>&1 &
运行完后去./classification/result查看train_loss和valid_iou两个图像,发现20个epoch没收敛啊,还能变得更好,因此再试个120个epoch的:
CUDA_VISIBLE_DEVICES=0,1,2,3,4,5 nohup python classification/train.py -d 0 1 2 3 4 5 -m res38d -b 64 -test_every 2 -epoch 120 >cls_train.log 2>&1 &
这里虽然提供了测试图像,但是我更希望写入tensorboard的summary_writer里生成events.out.tfevents文件,下次看的时候改一下代码。与此同时,运行完后再./classification/weights文件夹下面会有res38d_best.pth和res38d_last.pth。如果你直接用res38d的话,这两个新生成的pth会覆盖掉官方提供的pth。因为这里的weights文件夹和下载权重资源的路径在之前通过命令行link到一起了。
2、生成伪掩码(对我而言这是非常重要的一步!)
CUDA_VISIBLE_DEVICES=8,9 python classification/prepare_seg_inputs.py -batch 512 -d 0 1 -ckpt res38d_best_240221
这里虽然可以用多个GPU并行训练,但是实际上只有一个GPU在工作,不需要用多个GPU。这个步骤耗时约3.5小时。代码分为两部分:代码最后两行之前执行情况是会在classification/res38d_best_240221_train_pseudo_mask文件夹下生成9703个伪掩码npy文件;而代码最后两行执行glas_join_crops_back()函数,是将npy文件整合成png伪彩图。在执行结束后全部npy文件都将被删除!可以看到该函数调用的是./classification/utils/pyutils.py下的相应函数。如果想保留npy文件,可以将该函数最后的os.remove换成os.move,将其移动到某个文件夹下即可。此外,通过观察代码可以发现,这里的ckpt只需要输入文件名,与classification/weights下的pth文件名一致即可,不要写完整路径。官方提供了res38d_best和res38d_last两个版本。而res38d是一阶段分类网络初始化权重,并不是推理时所用的权重。
3、将生成的伪掩码切成patch(只有一行进度条)
python segmentation/tools/crop_img_and_gt.py segmentation/glas/images classification/res38d_240221_best_train_pseudo_mask segmentation/glas
执行结束后会在segmentation/glas文件夹下生成img_train_256_192和pesudo_train_256_192两个文件夹,文件夹里存放着对应的patch文件。这个步骤生成的图片看起来都是黑色的,需要额外进行颜色变换。
4、训练分割模型
cd segmentation
bash tools/dist_train.sh configs/pspnet_oeem/pspnet_wres38-d8_10k_histo.py 1 runs/oeem
提示没有安装pydensecrf库,网上说python版本大于3.5不能直接安装。执行:
pip install git+https://github.com/lucasb-eyer/pydensecrf.git
完美运行!另外,据说执行:conda install -c conda-forge pydensecrf也是可以的,但是我这里不行,是服务器本身的问题。
运行报错:
text, _ = FormatCode(text, style_config=yapf_style, verify=True)
TypeError: FormatCode() got an unexpected keyword argument 'verify'
解决办法:最新的yapf=0.40.2,重新安装yapf=0.40.1即可。感谢大哥:mmdetection 报错 TypeError: FormatCode() got an unexpected keyword argument ‘verify‘-CSDN博客
debug后即可完美运行,默认迭代次数是10000轮,平均3分钟迭代100轮,全部运行需要5个小时。
不过命令行中的“1”对应使用1块GPU,我肯定希望多运行,因此执行代码:
CUDA_VISIBLE_DEVICES=2,3,4,5,6 nohup bash tools/dist_train.sh configs/pspnet_oeem/pspnet_wres38-d8_10k_histo.py 5 runs/oeem >seg_train.log 2>&1 &
上述代码执行后,在某个时刻报错:
WARNING:torch.distributed.elastic.agent.server.api:Received 1 death signal, shutting down workers
...
raise SignalException(f"Process {os.getpid()} got signal: {sigval}", sigval=sigval)
torch.distributed.elastic.multiprocessing.api.SignalException: Process 32664 got signal: 1
网上看可能和nohup有关,解决方法未知,先不探索了,找个电脑后台挂着,先把程序跑完吧。
第四步:测试
1、测试分割模型
cd segmentation
bash tools/dist_test.sh configs/pspnet_oeem/pspnet_wres38-d8_10k_histo_test.py runs/oeem/[name of best ckpt] 1
将[name of best ckpt]替换为iter_10000.pth,也可以换成官方在weight里提供的res38d_oeem_release.pth。即:
bash tools/dist_test.sh configs/pspnet_oeem/pspnet_wres38-d8_10k_histo_test.py weights/res38d_oeem_release.pth 1
最后一个gpu数量为1时,日志为:
2024-02-28 22:09:24,175 - mmseg - INFO - Loaded 924 images
load checkpoint from local path: runs/oeem/iter_10000.pth
[ ] 3/924, 0.1 task/s, elapsed: 48s, ETA: 14584s
最后一个gpu数量为6时,日志为:
[ ] 18/924, 0.3 task/s, elapsed: 53s, ETA: 2646s
最后一个gpu数量为10时,日志为:
[ ] 30/924, 0.5 task/s, elapsed: 59s, ETA: 1746s
改gpu数量后,推理剩余时间确实有变快,没人用gpu的时候最好把gpu都用上,会省很多时间。不过实话实说,在测试中这个推理的步骤耗时实在太多了,虽然GPU利用率是满的,但是推理速度实在太慢了,0.5task/s,这个真太慢了。而且这还是第一步,后面还有一步merge。隔壁的真AutoSAM算法已经能推理很多张图了,这里还没推理完,而且隔壁只需要一个GPU,这个多GPU才能快点。除非有好的方法把推理速度提升上去,否则这个方法根本不可能落地应用。
之后会得出推理结果
per class results:
Class IoU Acc
0 72.44 93.24
1 64.93 69.58
Summary:
Scope mIoU mAcc aAcc
global 68.68 81.41 81.75
此步会在glas/test_patches里生成很多npy文件,便于下一步整合。
2、整合patches并评估
python tools/merge_patches.py glas/test_patches glas/test_wsi 2
对应代码可以看出,后三个arguments分别是输入文件夹、输出文件夹、分类类别数。因此此步会在glas/test_wsi文件夹下生成若干图。
python tools/count_miou.py glas/test_wsi glas/gt_val 2
对应代码可以看出,后三个arguments分别是预测文件夹、标注ground truth文件夹、分类类别数。此步仅输出mIoU和dice。
mIoU 0.6855933256228037, dice 0.8134741817033315
代码基本没变,可以跑通,但是我最终输出的mIoU和dice与论文中的还是有不小差距,尚不知道问题出在哪里。