参考了“天天炫橘子”的博文:DSMIL论文代码复现,Dual-stream Multiple Instance Learning Network for Whole Slide Image Classification with-CSDN博客
论文原文和原始代码:
https://arxiv.org/abs/2011.08939
第一点:新建虚拟环境,并根据env.yml配置虚拟环境(对应Github上的Installation)
操作和心得:试了它的conda env create -d dsmil...,但是不太行,有"conda Malformed version string ‘~’: invalid character(s)"报错,联系管理员也不太好解决。但我确定conda create肯定是没错的,就没有必要在这上面纠结了。我的做法是先conda creaate -d dsmil python=3.6,然后再根据env.yml里需要的东西分别pip install xxx -i https://pypi.tuna.tsinghua.edu.cn/simple。值得注意的是,如果直接安最新的opencv-python,会出现假死的情况,大概要等一个多小时,因为opencv-python要编译一大堆东西。如果你不想费劲等一个多小时,那就直接安装opencv-python==3.4.2.17,几秒钟就装好,用起来也没有什么问题。如果有问题就到时候再升级吧。
第二点:下载数据集(对应Github上的Download feature vectors for MIL network)
操作和心得:我没有通过代码下载,直接打开download.py文件,在里面找到下载链接下载,一共7个下载链接。下载比较耗时,需要特殊上网技能,大概一个下午完成,主要是dataset文件很大(如果有同学需要的话可以留言或者联系我,我用百度网盘分享给你)
第三点:解压数据集
操作和心得:我是先将压缩包传到服务器上,然后通过unzip xxx.zip ./dir来解压,注意,解压路径要参考download.py!其中c16-dataset-test.zip需要手动解压,直接解压会报错。这点在DSMIL的github网站上也提到了。
另外,一大发现是datasets中的mil的数据集竟然不是图像格式,而是一种神奇的格式(.svm和.svm.info),我以为数据集的格式都是图像,这个格式让我大受震撼。
第四点:使用给定数据集和权重进行训练(对应Github上的Training on default datasets)
操作和心得:在linux服务器上,只要数据集和权重文件路径放的对,能直接跑通。但是需要注意:① epoch可以先用小的试一下,看看会出什么结果,比如设置epoch=5看看。② 如果想同时执行多个train的指令的话(我同时想把tcga和camelyon16都运行了),我强烈建议你先运行一个,看到weight/%Y%m%d文件夹下有权重文件保存之后再执行另一个命令,否则就像我面对的一样,这两个指令保存的结果相互覆盖,分不清哪个是哪个了,很凉凉。此外,我一口气设置了epoch=200,等了快一天只运行到120/200,这个和服务器性能也有一定关系。
MIL benchmark datasets:没太看懂在干什么,很快就跑通了
python train_mil.py [--datasets] # musk1, musk2, elephant, fox, tiger
WSI datasets:TCGA和Camelyon16先后运行,不要同时运行;注意设置epoch,可以先设置小的再设置大的。
python train_tcga.py --dataset=Camelyon16 --num_classes=1 --feats_size=512 --num_epochs=10
作者在readme中对arguments进行了特别说明:这里的num_classes指的是non-negative classes,对于Camelyon16而言,二分类中tumor就是positive,normal就是negative,所以non-negatived的类别数量就是1。另外,此处的feats_size指的是feature vector的size,并且因CNN主干网络的不同而不同(也就对应代码里的ResNet18和34是512,ResNet50和101对应2048)
另外,这里训练的是aggregator,是用于测试的两个关键权重之一(另一个是embedder)。此步生成的文件是./weights/xxx/x.pth。但是,如果你在下一步测试并生成detection maps时下载权重的话,实际上没有用到此步生成的pth文件,而是直接用作者给的权重测试了。以Camelyon16数据集为例,如果希望用训练的权重,则需要将此步生成的pth文件复制到./test-c16/weights里并重命名为aggregator.pth。
第五点:测试并生成detection maps(对应Github上的Testing and generating detection maps from WSI)
对于TCGA数据集,先执行切patch(python test_crop_single.py --dataset=tcga),然后执行python testing_tcga.py时,有报错1:
File "testing_tcga.py", line 87, in test
bag_prediction = (bag_prediction+max_prediction)/2
TypeError: unsupported operand type(s) for +: 'numpy.ndarray' and 'Tensor'
报错1的解决办法:
将原有代码:
bag_prediction = (bag_prediction+max_prediction)/2
变更为:
bag_prediction = (torch.from_numpy(bag_prediction).to('cuda:0')+max_prediction)/2
然后又有报错2:
File "testing_tcga.py", line xxxxx, in test
attentions = attentions.cpu().numpy()
AttributeError: 'numpy.ndarray' object has no attribute 'cpu'
报错2的解决办法:
将原有代码:
attentions = attentions.cpu().numpy()
变更为:
if isinstance(attentions, torch.Tensor):
attentions = attentions.cpu().numpy()
debug完后代码能够调通,会在test/output文件夹下生成5个图片(其中第一张图片之所以是全黑色,是因为它的诊断结果是benign,即良性的):
对于Camelyon16数据集,先执行切patch(python test_crop_single.py --dataset=c16),然后执行python testing_c16.py,直接运行,在test-c16/output中直接运行得到结果(这四张图都有颜色,说明这四张图都诊断为肿瘤):
第六点:使用WSI数据从头训练(对应Github上的Processing raw WSI data)
1、下载WSI 略 本人使用Camelyon16,数据集很大,在Camelyon16官网有百度网盘链接
2、切patch 按要求来即可,完成后需要看看这个文件夹分类对不对。此处注意,deepzoom_tiler.py中默认根目录就是dsmil工程文件,因此保存tiler是在./WSI下面,数据集也应该放在./WSI下面。如果你的数据集和工程文件不在一起,教你一招:
当一个磁盘塞不下所有数据集 但代码里希望该数据集在该目录下的时候,可以建立软连接:ln -s dir_source dir_target,这样dir_target下就有一个链接文件,会链接到dir_source,不需要将整个数据集移动到dir_target下了。
实际上,这个方式也是我非常推荐的方式。数据集和工程文件分离单独存放。
3、训练SimCLR
需要安装tensorboard!天啊!回头看看tensorboard的图生成在哪里,看看能不能查看!据我观察,tensorboard的图是保存在./simclr/runs里面,y64文件就是,可以用tensorboard打开。
注意,如果是使用Camelyon16的话,不能只运行:cd simclr; python run.py
而要运行:cd simclr; python run.py --dataset Camelyon16
安装apex 注意不要安装最新的,可能会有报错。建议下载22.04-dev版本的apex,感谢墨大哥!(已解决)安装apex——报错:AttributeError: module ‘torch.distributed‘ has no attribute ‘_all_gather_base‘_attributeerror: module 'torch.distributed' has no -CSDN博客
使用pip install -v --disable-pip-version-check --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" ./完整安装会报错,但是去掉cpp_ext和cuda_ext,只安装python是ok的,不纠结了,能装上就行。
直接运行run.py可能会出现报错TypeError: 'int' object is not iterable,这里是因为代码读取的config.yaml的gpu_ids初始为(0),但是eval('(0)')为整数0,而不是tuplue,所以报错。建议直接将gpu_ids改为(0,1)或者其他只要不是一个就行。如果非要1个,需要自己把run.py和simclr.py中关于gpu_ids的设置给手动改一下n_gpu仍然是1,不用改),能用两个gpu避免报错还是最好在config.yaml中改为用两个gpu,最好不在run.py里改。此外,在修改gpu号的时候务必同时把config.yaml中的n_gpu也同步成对应的gpu数量!要不然就白忙活了。
此外,batch_size也太大了,config.yaml里是4096,运行报错RuntimeError: CUDA out of memory ,得把batch_size改小一些,比如2048或者1024或者512...() 特别注意,修改batch_size不要在run.py里改config['batch_size'],否则运行到后面会发现不一致而报错,一定要在config.yaml里修改batch_size!
另外,如果希望知道训练的epoch进展到哪里了,极度建议在sinclr.py的第95行for epoch_counter in range(self.config['epochs']) 下面加上:
import logging
import time
cur_time = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime())
logging.basicConfig(level=logging.INFO)
logging.info(epoch_counter)
logging.info(cur_time)
print('epoch:', epoch_counter, 'time:', cur_time)
这样通过前台查看输出或者后台查看nohup_simclr.log知道进展到哪里了(注意,如果你使用print且用nohup的话,后天是看不到输出的,因为你执行的是run.py,而run.py调用的simclr.py,所以simclr.py的打印是不记录在nohup_simclr.log里的)。
时间很长需要后台训练的话,可以执行:nohup python run.py --dataset Camelyon16 >nohup_simclr.log 2>&1 &
训练完成后,查看./simclr/runs里的events.out.tfevents文件:
确认电脑安装了tensorboard:
pip install tensorboard -i https://pypi.douban.com/simple
可视化:
tensorboard --logdir=D:\...simclr\runs\Febxxx --port=8008
D:\xxx是events.out.tfevents文件所在的文件目录;--port=8008 是开启可视化的“地址”,修改 --port= 后面的数字可以同时可视化多个不同的events.out.tfevents文件
观察到cosine_lr_decay, train loss, valid loss三个曲线。
4、按照说明,若训练Camelyon16且为单放大倍数,想用多gpu,使用指令:
python compute_feats.py --dataset=Camelyon16 --gpu 0 1 2 3
--magnification=single(因为默认就是single 所以可省)
只有一行进度条 很快就结束了 不会占用太长时间。此步会加载之前在./simclr/runs/*里生成的pth权重,并将该pth权重进行一些变换并写入到./embedder/Camelyon16/embedder.pth文件中。这个文件将作为推理时所用的两个权重中的一个,非常重要!此外,还将输出各种csv文件到./dataset/Camelyon16文件夹中
此步生成的csv,列数一共有512列,标号0-511,以normal_004.tif为例,其生成csv行数与./WSI/Camelyon16/single/normal/normal_004文件夹下的patch数是一致的。
以上是生成的Camelyon16.csv的部分内容
以上分别是生成的normal.csv和tumor.csv的部分内容。另外,此步的代码第156行中的num_feats=512(resnet18/resnet34)或2048(resnet50/resnet101)应当表示的是一个WSI中的patch所对应的向量维度数量。
5、按照说明,若训练Camelyon16且想用多gpu,我这里想让train和valid来split一下,valid占比0.4,因此使用指令:
nohup python train_tcga.py --dataset Camelyon16 --num_classes 1 --split 0.4 --gpu_index 0 1 2 >nohup_train_tcga.log 2>&1 &
可以使用“--gpu 0 1 2”,但不能用“gpu_index=0 1 2”,否则linux只认为gpu_index=0,其他的1 2是非法输入。
然而不知道是我的什么东西设置小了,GPU0占用了1000MB/15000MB,其他GPU都基本上没用到,无语住了,不知道怎么调让它升上去。另外,经过确认,这里的feats_size和上一步compute_feats.py中的num_feats应该指的是一个意思。
6、最后,就回到一开始:
python test_crop_single.py --dataset=c16
上述代码要测试Camelyon16 必须将测试图片放在./test-c16/input文件夹下,会生成./test-c16/patches和./test-c16/weights
python testing_c16.py
上述代码使用patches和thumbnails作为输入,使用./test-c16/weights进行输出,输出结果在./test-c16/output文件夹中。如果输入切片都不变的话,可以直接新增个weights文件夹替换掉原先的weights文件夹再运行。实际上这个testing_c16.py没有args参数输入,显得不太友好,但是也还尚可运行。
总的来说,因为我之前对Camelyon16比较熟,所以才用的Camelyon16数据集,总的来说也是可以运行下来的。但如果能下载他论文里提到的TCGA会更友好一些。