DSMIL: 双流多实例学习网络用于全切片图像中的肿瘤检测
Pytorch 实现了论文 Dual-stream Multiple Instance Learning Network for Whole Slide Image Classification with Self-supervised Contrastive Learning(CVPR 2021,口头报告接收)中描述的多实例学习模型。
2024 更新
- 训练速度提升 10 倍。
- 训练脚本中的交叉验证和训练/验证/测试(交叉验证 + 独立测试集)。
- 稳定的模型初始化方法。
- 多标签任务的更好指标。
- 修复了生成色彩图的脚本中的几个错误。
安装
安装 anaconda/miniconda
所需包
$ conda env create --name dsmil --file env.yml
$ conda activate dsmil
安装 PyTorch
安装 OpenSlide 和 openslide-python。
Tutorial 1 和 Tutorial 2 (Windows)。
下载 MIL 网络的特征向量
MIL 基准数据集可通过以下方式下载:
$ python download.py --dataset=mil
预计算的 TCGA 肺癌数据集 的特征可通过以下方式下载:
$ python download.py --dataset=tcga
预计算的 Camelyon16 数据集
$ python download.py --dataset=c16
该数据集需要 30GB 的可用磁盘空间。
在默认数据集上训练
MIL 基准数据集
在标准 MIL 基准数据集上训练 DSMIL:
$ python train_mil.py
在 MIL 基准数据集中切换,使用选项:
[--datasets] # musk1, musk2, elephant, fox, tiger
其他可选项有学习率 (--lr=0.0002)、交叉验证折数 (--cv_fold=10)、权重衰减 (--weight_decay=5e-3) 和轮数 (--num_epoch=40)。
WSI 数据集
在 TCGA 肺癌数据集(预计算特征)上训练 DSMIL:
$ python train_tcga.py --dataset=TCGA-lung-default
在 Camelyon16 数据集(预计算特征)上训练 DSMIL:
$ python train_tcga.py --dataset=Camelyon16 --num_classes=1
理解不同的评估方案和指标
通过设置参数 (--eval_scheme) 可以选择不同的训练和评估方案。
--eval_scheme=5-fold-cv
一个5折交叉验证。对于每一折,将在验证集上计算 AUC 和准确率。在所有折完成后,将计算各折的平均值。
--eval_scheme=5-fold-cv-standalone-test
一个单独的测试集包含 20% 的样本,剩下的 80% 样本用于构造5折交叉验证。
对于每一折,将保存最佳模型和相应的阈值。
在5折交叉验证后,将获得5个最佳模型及相应的最佳阈值,这些模型将用于在保留的测试集上进行推理。测试样本的最终预测是5个模型的多数票。
对于二分类,将计算准确率和平衡准确率分数。对于多标签分类,将计算汉明损失(值越小越好)和子集准确率。
预期性能
由于随机拆分,你可能会看到略有不同的性能,但差异应在2%以内
使用5折交叉验证的 Camelyon16。
指标 | 准确率 | AUC |
---|---|---|
值 | 94.9% | 0.961 |
使用5折交叉验证和单独测试集的 Camelyon16。
指标 | 准确率 | AUC |
---|---|---|
值 | 92.4% | 0.915 |
使用5折交叉验证的 TCGA 肺数据。
指标 | 准确率 | AUC |
---|---|---|
值 | 93.78% | 0.981 |
使用5折交叉验证和单独测试集的 TCGA 肺数据。
指标 | 子集准确率 | 汉明损失 |
---|---|---|
值 | 90.9% | 0.086 |
有用的参数:
[--num_classes] # 非负类的数量,对于二分类(正/负),此值设为 1
[--feats_size] # 特征向量的大小(取决于 CNN 主干)
[--lr] # 初始学习率 [0.0001]
[--num_epochs] # 训练轮数 [50]
[--stop_epochs] # 如果训练在 N 轮后未改善,则跳过剩余轮数 [10]
[--weight_decay] # 权重衰减 [1e-3]
[--dataset] # 数据集文件夹名称
[--split] # 训练/验证拆分 [0.2]
[--dropout_patch] # 在训练期间随机丢弃部分补丁并用重复补丁替代 [0]
[--dropout_node] # 在生成数值向量网络中随机丢弃部分节点 [0]
在 WSI 中测试和生成检测图
TCGA 数据集
下载一些测试切片:
$ python download.py --dataset=tcga-test
$ python test_crop_single.py --dataset=tcga
每个 WSI 的所有补丁将被创建在
./test/patches
文件夹中。
在 WSI 被裁剪后,运行测试脚本:
$ python testing_tcga.py
WSI 的缩略图将保存在
./test/thumbnails
中。
检测色彩图将保存在./test/output
中。
测试管道将处理放置在./test/input
文件夹中的每个 WSI。该切片将被检测为 LUAD、LUSC 或良性样本。
Camelyon16 数据集
生成 Camelyon16 的检测图类似。样本切片的直接下载 链接。
$ python download.py --dataset=c16-test
$ python test_crop_single.py --dataset=c16
$ python testing_c16.py
处理原始的 WSI 数据
如果你正在处理原始图像的 WSI 数据,你需要先下载 WSI 数据。
下载 WSI 数据。
-
从 GDC 数据门户。 你可以使用带有清单文件和配置文件的 GDC 数据门户。原始 WSI 需要大约 1TB 的磁盘空间,下载可能需要几天时间。请查看 详情 以了解如何使用 TCGA 数据门户。否则,可以在 GDC 数据门户 存储库 中手动下载各个 WSI。
-
从 Google Drive。 svs 文件也已 上传。数据集总共有 1053 张切片,包括 512 张 LUSC 和 541 张 LUAD。丢弃了 10 张低质量的 LUAD 切片。
在文件夹中分类 svs 文件。
根据 ID 将 LUAD 和 LUSC 切片分开,并将文件放入 WSI/TCGA-lung/LUAD
或 WSI/TCGA-lung/LUSC
文件夹中。
准备补丁。
我们将使用 OpenSlide,一个提供简单接口读取 WSI 数据的 C 库及 Python API。我们推荐用户查看 OpenSlide Python API 文档 以了解如何使用该工具。
补丁可以在./WSI/TCGA-lung/pyramid
中以两种放大级别的金字塔结构保存。第一级放大级别是 0 级,对应于由-b
指定的基础放大率。例如,要提取 20x 和 5x 放大率的补丁,运行:
$ python deepzoom_tiler.py -m 0 2 -b 20
或在单一的放大级别裁剪补丁并保存在
./WSI/TCGA-lung/single
中。例如,提取 10x 放大率的补丁:
$ python deepzoom_tiler.py -m 0 -b 10
Camelyon16 包括混合放大率,重现情况运行:
$ python deepzoom_tiler.py -m 1 -b 20 -d Camelyon16 -v tif
训练嵌入器。
我们提供了一个修改后的脚本,该脚本来自 Pytorch 实现的 SimCLR 用于训练嵌入器。 导航到
./simclr
并编辑配置文件config.yaml
中的属性。你需要确定一个适合你的 GPU 的批量大小。我们建议使用至少 512 的批量大小以获得好的 simclr 特征。训练后的模型权重和损失日志保存在./simclr/runs
文件夹中。
如果在单一放大率下裁剪补丁。
cd simclr
$ python run.py
如果在多种放大率下裁剪补丁,则每种放大率的嵌入器需要单独训练以获得更好的结果。
$ python run.py --multiscale=1 --level=low
$ python run.py --multiscale=1 --level=high
否则,你可以使用训练好的嵌入器 Camelyon16 和 TCGA 。
计算特征。
计算单一放大率的特征:
$ cd ..
$ python compute_feats.py --dataset=TCGA-lung
默认情况下将使用最后训练的嵌入器。要使用特定的嵌入器,设置选项
--weights=[RUN_NAME]
,其中[RUN_NAME]
是simclr/runs/
内的文件夹名。 或计算多种放大率的特征:
$ python compute_feats.py --dataset=TCGA-lung --magnification=tree
要为每种放大率使用特定的嵌入器,设置选项 --weights_low=[RUN_NAME]
(低放大率的嵌入器)和 --weights_high=[RUN_NAME]
(高放大率的嵌入器)。
要使用 ImageNet 预训练的 CNN 进行特征计算(需要批量归一化):
$ python compute_feats.py --weights=ImageNet --norm_layer=batch
如果多种放大率下裁剪的补丁,但只需计算一种放大率的特征,设置参数
--magnification=high
或--magnification=low
。
开始训练。
$ python train_tcga.py --dataset=TCGA-lung
关于多尺度特征
你可以在此下载预计算的特征: Camelyon16 TCGA
在训练时使用
$ python train_tcga.py --feats_size=1024 --dataset=[DATASET_NAME] --num_classes=[NUMBER_OF_CLASSES]
使用您自己的数据集进行训练
- 存放WSI文件为
WSI\[DATASET_NAME]\[CATEGORY_NAME]\[SLIDE_FOLDER_NAME] (optional)\SLIDE_NAME.svs
。
对于二分类器,当按字母顺序排序时,负类应在索引
0
位置。对于多分类器,如果有负类(不属于任何正类),文件夹应在按字母顺序排序时位于最后一个索引。如果没有负类,类文件夹的命名无关紧要。
- 裁剪补丁。
$ python deepzoom_tiler.py -m 0 -b 20 -d [DATASET_NAME]
设置标志
-m [LEVEL 1] [LEVEL 2]
以从多个放大倍率裁剪补丁。
- 训练嵌入器。
$ cd simclr
$ python run.py --dataset=[DATASET_NAME]
如果补丁是从多个放大倍率裁剪的,设置标志
--multiscale=1
和标志--level=low
或--level=high
分别为每个倍率训练一个嵌入器。
- 使用嵌入器计算特征。
$ cd ..
$ python compute_feats.py --dataset=[DATASET_NAME]
设置标志
--magnification=tree
来计算多个放大倍率的特征。如果要使用特定运行中的嵌入器,请添加选项--weights=[RUN_NAME]
,其中[RUN_NAME]
是simclr/runs/
中的文件夹名称。如果有要使用的嵌入器,可以将权重文件放置为simclr/runs/[RUN_NAME]/checkpoints/model.pth
并将[RUN_NAME]
传递给此选项。要为每个倍率使用特定的嵌入器,设置选项--weights_low=[RUN_NAME]
(低倍率嵌入器)和--weights_high=[RUN_NAME]
(高倍率嵌入器)。嵌入器架构是带有实例归一化的ResNet18。
- 训练。
$ python train_tcga.py --dataset=[DATASET_NAME]
如果数据集中包含超过2个正类或仅有1个正类和1个负类(二分类器),您需要调整
--num_classes
选项。详情请参见下一节。
- 测试。
$ python attention_map.py --bag_path test/patches --map_path test/output --thres 0.73 0.28
有用的参数:
[--num_classes] # 非负类的数量。
[--feats_size] # 特征向量的大小(取决于CNN骨干网络)。
[--thres] # 训练函数返回的类的阈值列表。
[--embedder_weights] # 嵌入器权重文件的路径(由SimCLR保存)。如果使用ImageNet预训练的嵌入器,请使用“ImageNet”。
[--aggregator_weights] # 聚合器权重文件的路径。
[--bag_path] # 包含补丁文件夹的路径。
[--patch_ext] # 补丁的文件扩展名。
[--map_path] # 输出注意力图的路径。
文件夹结构
数据被组织在两个文件夹中,WSI
和datasets
。WSI
文件夹包含图片,datasets
文件夹包含计算出的特征。
root
|-- WSI
| |-- DATASET_NAME
| | |-- CLASS_1
| | | |-- SLIDE_1.svs
| | | |-- ...
| | |-- CLASS_2
| | | |-- SLIDE_1.svs
| | | |-- ...
一旦完成补丁提取,single
文件夹或pyramid
文件夹将出现。
root
|-- WSI
| |-- DATASET_NAME
| | |-- single
| | | |-- CLASS_1
| | | | |-- SLIDE_1
| | | | | |-- PATCH_1.jpeg
| | | | | |-- ...
| | | | |-- ...
| | |-- pyramid
| | | |-- CLASS_1
| | | | |-- SLIDE_1
| | | | | |-- PATCH_LOW_1
| | | | | | |-- PATCH_HIGH_1.jpeg
| | | | | | |-- ...
| | | | | |-- ...
| | | | | |-- PATCH_LOW_1.jpeg
| | | | | |-- ...
| | | | |-- ...
一旦特征计算完成,datasets
文件夹中将出现DATASET_NAME
文件夹。
root
|-- datasets
| |-- DATASET_NAME
| | |-- CLASS_1
| | | |-- SLIDE_1.csv
| | | |-- ...
| | |-- CLASS_2
| | | |-- SLIDE_1.csv
| | | |-- ...
| | |-- CLASS_1.csv
| | |-- CLASS_2.csv
| | |-- DATASET_NAME.csv
特征向量csv文件解释
- 对于每个包,有一个.csv文件,其中每行包含一个实例的特征。该.csv文件命名为"bagID.csv",放置在一个名为"dataset-name/category/"的文件夹中。
- 有一个"dataset-name.csv"文件,包含两列,第一列包含所有_bagID_.csv文件的路径,第二列包含包标签。
- 标签。
对于二分类器,使用
1
表示正包,0
表示负包。训练时使用--num_classes=1
。 对于多类分类器(N
个正类和一个可选的负类),正类使用0~(N-1)
。如果有负类(不属于任何正类),用N
表示其标签。训练时使用--num_classes=N
(N
等于正类的数量)。
引用
如果在研究中使用了该代码或结果,请使用以下BibTeX条目。
@inproceedings{li2021dual,
title={Dual-stream multiple instance learning network for whole slide image classification with self-supervised contrastive learning},
author={Li, Bin and Li, Yin and Eliceiri, Kevin W},
booktitle={Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition},
pages={14318--14328},
year={2021}
}