From fc56e9afbafbd802b88d3419d582293fc24f32aa Mon Sep 17 00:00:00 2001 From: Bobholamovic Date: Thu, 25 Aug 2022 20:55:29 +0800 Subject: [PATCH] FInish rs_research example --- examples/rs_research/README.md | 205 ++++++++++-------- examples/rs_research/custom_model.py | 12 +- examples/rs_research/params_versus_f1.png | Bin 48900 -> 0 bytes examples/rs_research/scripts/run_ablation.sh | 3 - examples/rs_research/scripts/run_benchmark.sh | 30 +-- examples/rs_research/tools/visualize_feats.py | 29 ++- 6 files changed, 157 insertions(+), 122 deletions(-) delete mode 100644 examples/rs_research/params_versus_f1.png diff --git a/examples/rs_research/README.md b/examples/rs_research/README.md index 715e61b..c7b45cf 100644 --- a/examples/rs_research/README.md +++ b/examples/rs_research/README.md @@ -16,27 +16,24 @@ cd examples/rs_research ## 2 数据准备 -本案例在[LEVIR-CD数据集](https://www.mdpi.com/2072-4292/12/10/1662)[1]和[synthetic images and real season-varying remote sensing images(SVCD)数据集](https://www.int-arch-photogramm-remote-sens-spatial-inf-sci.net/XLII-2/565/2018/isprs-archives-XLII-2-565-2018.pdf)[2]上开展实验。请在[LEVIR-CD数据集下载链接](https://justchenhao.github.io/LEVIR/)和[SVCD数据集下载链接](https://drive.google.com/file/d/1GX656JqqOyBi_Ef0w65kDGVto-nHrNs9/edit)分别下载这两个数据集,解压至本地目录,并执行如下指令: +本案例在[LEVIR-CD数据集](https://www.mdpi.com/2072-4292/12/10/1662)[1]上开展实验。请在[LEVIR-CD数据集下载链接](https://justchenhao.github.io/LEVIR/)下载数据集,解压至本地目录,并执行如下指令: ```bash mkdir data/ python ../../tools/prepare_dataset/prepare_levircd.py \ --in_dataset_dir "{LEVIR-CD数据集存放目录路径}" \ - --out_dataset_dir 'data/levircd' \ + --out_dataset_dir "data/levircd" \ --crop_size 256 \ --crop_stride 256 -python ../../tools/prepare_dataset/prepare_svcd.py \ - --in_dataset_dir "{SVCD数据集存放目录路径}" \ - --out_dataset_dir 'data/svcd' ``` -以上指令利用PaddleRS提供的数据集准备工具完成数据集切分、file list创建等操作。具体而言,对于LEVIR-CD数据集,使用官方的训练/验证/测试集划分,并将原始的`1024x1024`大小的影像切分为无重叠的`256x256`的小块(参考[3]中的做法);对于SVCD数据集,使用官方的训练/验证/测试集划分,不做其它额外处理。 +以上指令利用PaddleRS提供的数据集准备工具完成数据集切分、file list创建等操作。具体而言,使用LEVIR-CD数据集官方的训练/验证/测试集划分,并将原始的`1024x1024`大小的影像切分为无重叠的`256x256`的小块(参考[2]中的做法). ## 3 模型设计 ### 3.1 问题分析与思路拟定 -随着深度学习技术应用的不断深入,近年来,变化检测领域涌现了许多基于全卷积神经网络(fully convolutional network, FCN)的遥感影像变化检测算法。与基于特征和基于影像块的方法相比,基于FCN的方法具有处理效率高、依赖超参数少等优势,但其缺点在于参数量往往较大,因而对训练样本的数量更为依赖。尽管中、大型变化检测数据集的数量与日俱增,训练样本日益丰富,但深度学习变化检测模型的参数量也越来越大。下图显示了从2018年到2021年一些已发表的文献中提出的基于FCN的变化检测模型的参数量与其在SVCD数据集上取得的F1分数(柱状图中bar的高度与模型参数量成正比): +随着深度学习技术应用的不断深入,近年来,变化检测领域涌现了许多基于全卷积神经网络(fully convolutional network, FCN)的遥感影像变化检测算法。与基于特征和基于影像块的方法相比,基于FCN的方法具有处理效率高、依赖超参数少等优势,但其缺点在于参数量往往较大,因而对训练样本的数量更为依赖。尽管中、大型变化检测数据集的数量与日俱增,训练样本日益丰富,但深度学习变化检测模型的参数量也越来越大。下图显示了从2018年到2021年一些已发表的文献中提出的基于FCN的变化检测模型的参数量与其在SVCD数据集[3]上取得的F1分数(柱状图中bar的高度与模型参数量成正比): ![params_versus_f1](params_versus_f1.png) @@ -47,6 +44,12 @@ python ../../tools/prepare_dataset/prepare_svcd.py \ 本案例认为,上述问题的根源在于参数量与数据量的失衡所导致的特征冗余。既然模型的特征存在冗余,也即存在一部分“无用”的特征,是否存在某种手段,能够在固定模型参数量的前提下对特征进行优化,从而“榨取”小模型的更多潜力,获取更多更加有效的特征?基于这个观点,本案例的基本思路是为现有的变化检测模型添加一个“插件式”的特征优化模块,在仅引入较少额外的参数数量的情况下,实现变化特征增强。本案例计划以变化检测领域经典的FC-Siam-conc[4]为baseline网络,利用通道和时间注意力模块对网络的中间层特征进行优化,从而减小特征冗余,提升检测效果。在具体的模块设计方面,选用论文[5]中提出的通道注意力模块实现通道和时间维度的特征增强。 +FC-Siam-conc的网络结构如图所示: + +![fc_siam_conc](fc_siam_conc.png) + +本案例计划在解码器中首个Concat模块之前添加通道与时间注意力模块组合而成的混合注意力模块以优化从编码器传来的特征,并将新模型称为CustomModel。 + ### 3.2 模型定义 本小节基于PaddlePaddle框架与PaddleRS库实现[3.1节](#3.1-问题分析与思路拟定)中提出的想法。 @@ -104,17 +107,11 @@ class MixedAttention(nn.Layer): # 每个注意力模块都是可选的 if self.has_att_c: self.att_c = ChannelAttention(in_channels, ratio=1) - # 在时间注意力模块之后增加归一化层 - # 利用BN层中的可学习参数增强模型的拟合能力 - self.norm_c1 = nn.BatchNorm(in_channels) - self.norm_c2 = nn.BatchNorm(in_channels) else: self.att_c = Identity() - self.norm_c1 = Identity() - self.norm_c2 = Identity() - # 时间注意力模块部分复用通道注意力的逻辑,在`forward()`中将具体解释 if has_att_t: + # 时间注意力模块部分复用通道注意力的逻辑,在`forward()`中将具体解释 self.att_t = ChannelAttention(2, ratio=1) else: self.att_t = Identity() @@ -124,11 +121,10 @@ class MixedAttention(nn.Layer): if self.has_att_c: # 首先使用通道注意力模块对特征进行优化 - # 两个时相的编码特征共享通道注意力模块,但使用各自的归一化层 - x1 = self.att_c(x1) * x1 - x1 = self.norm_c1(x1) - x2 = self.att_c(x2) * x2 - x2 = self.norm_c2(x2) + # 两个时相的编码特征共享通道注意力模块 + # 添加残差连接以加速收敛 + x1 = (1 + self.att_c(x1)) * x1 + x2 = (1 + self.att_c(x2)) * x2 if self.has_att_t: b, c = x1.shape[:2] @@ -138,7 +134,8 @@ class MixedAttention(nn.Layer): # 将b和c两个维度合并,输出tensor形状为[b*c, t, h, w] y = paddle.flatten(y, stop_axis=1) # 此时,时间维度已经替代了原先的通道维度,将四维tensor输入ChannelAttention模块进行处理 - y = self.att_t(y) * y + # 同样添加残差连接 + y = (1 + self.att_t(y)) * y # 从处理结果中分离两个时相的信息 y = y.reshape((b, c, 2, *y.shape[2:])) y1, y2 = y[:, :, 0], y[:, :, 1] @@ -159,10 +156,10 @@ class MixedAttention(nn.Layer): 在编写组网相关代码时请注意以下两点: 1. 所有模型必须为`paddle.nn.Layer`的子类; -2. 包含模型整体逻辑结构的最外层模块须用`@attach`装饰; -3. 对于变化检测任务,`forward()`方法除`self`参数外还接受两个参数`t1`、`t2`,分别表示第一时相和第二时相影像。 +2. 包含模型整体逻辑结构的最外层模块(如本例中的`CustomModel`类)须用`@attach`装饰; +3. 对于变化检测任务,最外层模块的`forward()`方法除`self`参数外还接受两个参数`t1`、`t2`,分别表示第一时相和第二时相影像。 -关于模型定义的更多细节请参考[文档](https://github.com/PaddlePaddle/PaddleRS/blob/develop/docs/dev/dev_guide.md)。 +关于模型定义的更多细节请参考[开发指南](https://github.com/PaddlePaddle/PaddleRS/blob/develop/docs/dev/dev_guide.md)。 #### 3.2.2 自定义训练器 @@ -203,21 +200,21 @@ class CustomTrainer(BaseChangeDetector): ## 4 对比实验 -为了验证模型设计的有效性,通常需要开展对比实验,在一个或多个数据集上比较所提出模型与其它模型的精度和性能。在本案例中,将自定义模型与FC-EF、FC-Siam-diff、FC-Siam-conc三种结构进行比较,这三个模型均来自论文[4]。 +为了验证模型设计的有效性,通常需要开展对比实验,在一个或多个数据集上比较所提出模型与其它模型的精度和性能。在本案例中,将自定义模型CustomModel与FC-EF、FC-Siam-diff、FC-Siam-conc三种结构进行比较,这三个模型均来自论文[4]。 ### 4.1 实验过程 -使用如下指令在LEVIR-CD与SVCD数据集上执行对所有参与对比的模型的训练: +使用如下指令在LEVIR-CD数据集上执行对所有参与对比的模型的训练: ```bash bash scripts/run_benchmark.sh ``` -或者,可以按照以下格式执行对某个模型在某一数据集上的训练: +或者,可以按照以下格式执行对某个模型的训练: ```bash python run_task.py train cd \ - --config "configs/{数据集名称}/{配置文件名称}" \ + --config "configs/levircd/{配置文件名称}" \ 2>&1 | tee "{日志路径}" ``` @@ -225,9 +222,9 @@ python run_task.py train cd \ ```bash python run_task.py eval cd \ - --config "configs/{数据集名称}/{配置文件名称}" \ - --datasets.eval.args.file_list "data/{数据集名称}/test.txt" \ - --resume_checkpoint "exp/{数据集名称}/{模型名称}/best_model" + --config "configs/levircd/{配置文件名称}" \ + --datasets.eval.args.file_list "data/levircd/test.txt" \ + --resume_checkpoint "exp/levircd/{模型名称}/best_model" ``` 训练程序默认开启VisualDL日志记录功能。训练过程中或训练完成后,可使用VisualDL观察损失函数和精度指标的变化情况。在PaddleRS中使用VisualDL的方式请参考[使用教程](https://github.com/PaddlePaddle/PaddleRS/blob/develop/tutorials/train/README.md#visualdl%E5%8F%AF%E8%A7%86%E5%8C%96%E8%AE%AD%E7%BB%83%E6%8C%87%E6%A0%87)。 @@ -236,18 +233,18 @@ python run_task.py eval cd \ ```bash python predict_cd.py \ - --model_dir "exp/{数据集名称}/{模型名称}/best_model" \ - --data_dir "data/{数据集名称}" \ - --file_list "data/{数据集名称}/test.txt" \ - --save_dir "exp/predict/{数据集名称}/{模型名称}" + --model_dir "exp/levircd/{模型名称}/best_model" \ + --data_dir "data/levircd" \ + --file_list "data/levircd/test.txt" \ + --save_dir "exp/predict/levircd/{模型名称}" ``` -之后,可在`exp/predict/{数据集名称}/{模型名称}`目录查看保存的输出结果。 +之后,可在`exp/predict/levircd/{模型名称}`目录查看保存的输出结果。 -可以通过`tools/collect_imgs.py`脚本将输入图像、真值标签以及多个模型的预测结果放置在一个目录下以便于观察比较。该脚本接受三个命令行选项: -- 使用`--globs`指定一系列通配符(可用于Python的[`glob.glob()`函数](https://docs.python.org/zh-cn/3/library/glob.html#glob.glob),用于匹配需要收集的图像; -- 使用`--tags`为`--globs`中的每一项指定一个别名,在存储目录中,相应的图像名将被替换为存储的别名; -- 使用`--save_dir`指定输出目录路径,若目录不存在将被自动创建。 +可以通过`tools/collect_imgs.py`脚本将输入图像、变化标签以及多个模型的预测结果放置在一个目录下以便于观察比较。该脚本接受三个命令行选项: +- `--globs`指定一系列通配符(可用于Python的[`glob.glob()`函数](https://docs.python.org/zh-cn/3/library/glob.html#glob.glob),用于匹配需要收集的图像; +- `--tags`为`--globs`中的每一项指定一个别名,在存储目录中,相应的图像名将被替换为存储的别名; +- `--save_dir`指定输出目录路径,若目录不存在将被自动创建。 例如,对于LEVIR-CD数据集,执行如下指令: @@ -262,74 +259,51 @@ python tools/collect_imgs.py \ --save_dir "exp/collect/levircd" ``` -执行完毕后,可在`exp/collect/levircd`目录中找到两个时相的输入影像、真值标签以及各个模型的预测结果。当新增模型后,可以再次调用`tools/collect_imgs.py`脚本补充结果到`exp/collect/levircd`目录中: +执行完毕后,可在`exp/collect/levircd`目录中找到两个时相的输入影像、变化标签以及各个模型的预测结果。当新增模型后,可以再次调用`tools/collect_imgs.py`脚本补充结果到`exp/collect/levircd`目录中: ```bash python tools/collect_imgs.py --globs "exp/predict/levircd/{新增模型名称}/*.png" --tags '{新增模型名称}' --save_dir "exp/collect/levircd" ``` -对于SVCD数据集,执行如下指令: - -```bash -python tools/collect_imgs.py \ - --globs "data/svcd/ChangeDetectionDataset/Real/subset/test/A/*.jpg" "data/svcd/ChangeDetectionDataset/Real/subset/test/B/*.jpg" "data/svcd/ChangeDetectionDataset/Real/subset/test/OUT/*.jpg" \ - "exp/predict/svcd/fc_ef/*.png" "exp/predict/svcd/fc_siam_conc/*.png" "exp/predict/svcd/fc_siam_diff/*.png" \ - "exp/predict/svcd/custom_model/*.png" \ - --tags 'A' 'B' 'GT' \ - 'fc_ef' 'fc_siam_conc' 'fc_siam_diff' \ - 'custom_model' \ - --save_dir "exp/collect/svcd" -``` - 此外,为了从精度和性能两个方面综合评估变化检测算法,可以通过如下指令计算变化检测模型的[浮点计算数(floating point operations, FLOPs)](https://blog.csdn.net/IT_flying625/article/details/104898152)和模型参数量: ```bash -python tools/analyze_model.py --model_dir "exp/{数据集名称}/{模型名称}/best_model" +python tools/analyze_model.py --model_dir "exp/levircd/{模型名称}/best_model" ``` ### 4.2 实验结果 -本案例使用变化类的[交并比(intersection over union, IoU)](https://paddlepedia.readthedocs.io/en/latest/tutorials/computer_vision/semantic_segmentation/Overview/Overview.html#id6)和[F1分数](https://baike.baidu.com/item/F1%E5%88%86%E6%95%B0/13864979)作为定量评价指标。在每个数据集上,从目视效果和定量指标两个方面对算法效果进行评判。 -#### 4.2.1 LEVIR-CD数据集上的对比结果 +本案例使用变化类的[交并比(intersection over union, IoU)](https://paddlepedia.readthedocs.io/en/latest/tutorials/computer_vision/semantic_segmentation/Overview/Overview.html#id6)和[F1分数](https://baike.baidu.com/item/F1%E5%88%86%E6%95%B0/13864979)作为定量评价指标,这两个指标越高,表示算法的检测效果越好。在每个数据集上,从目视效果和定量指标两个方面对算法效果进行评判。 -**目视效果对比** +#### 4.2.1 目视效果对比 -|时相1影像|时相2影像|FC-EF|FC-Siam-diff|FC-Siam-conc|CustomModel|真值标签| +下图展示了两个时相的输入影像、各算法输出的二值变化图(binary change map)以及变化标签。所选取的样本均来自LEVIR-CD数据集的测试集。 + +|时相1影像|时相2影像|FC-EF|FC-Siam-diff|FC-Siam-conc|CustomModel|变化标签| |:-:|:-:|:-:|:-:|:-:|:-:|:-:| |![]()|![]()|![]()|![]()|![]()|![]()|![]()| +|![]()|![]()|![]()|![]()|![]()|![]()|![]()| -**定量指标对比** +从图中可以看出,虽然结果中仍存在一定程度的漏检与误检,但相比其它算法,CustomModel对变化区域的刻画相对更为准确。 + +#### 4.2.2 定量指标对比 |模型名称|FLOPs(G)|参数量(M)|IoU%|F1%| |:-:|:-:|:-:|:-:|:-:| |FC-EF|3.57|1.35|79.05|88.30| -|FC-Siam-diff|4.71|1.35|81.33|89.70| +|FC-Siam-diff|4.71|1.35|81.33|89.70| |FC-Siam-conc|5.31|1.55|81.31|89.69| -|CustomModel|5.31|1.58|**82.27**|**90.27**| - -#### 4.2.2 SVCD数据集上的对比结果 +|CustomModel|5.31|1.58|**82.14**|**90.19**| -**目视效果对比** +表中最高的精度指标用粗体表示、次高的指标用下划线标示。从表中可以看出,CustomModel取得了所有算法中最高的IoU和F1分数指标(与FC-EF对比IoU增加3.09%,F1增加1.89%),而其相比baseline FC-Siam-conc仅仅引入0.03 M的额外参数量。 -|时相1影像|时相2影像|FC-EF|FC-Siam-diff|FC-Siam-conc|CustomModel|真值标签| -|:-:|:-:|:-:|:-:|:-:|:-:|:-:| -|![]()|![]()|![]()|![]()|![]()|![]()|![]()| - -**定量指标对比** - -|模型名称|FLOPs(G)|参数量(M)|IoU%|F1%| -|:-:|:-:|:-:|:-:|:-:| -|FC-EF|3.57|1.35|84.11|91.37| -|FC-Siam-diff|4.71|1.35|88.75|94.04| -|FC-Siam-conc|5.31|1.55|88.29|93.78| -|CustomModel|5.31|1.58||| ## 5 消融实验 -在科研过程中,为了验证在baseline上所做修改的有效性,常常需要开展消融实验。例如,在本案例中,自定义模型在FC-Siam-conc模型的基础上添加了通道和时间两种注意力模块,因此需要通过消融实验探讨各个注意力模块对最终精度的贡献。具体而言,包括以下4种实验情形(配置文件均存储在`configs/levircd/ablation`目录): +在科研过程中,为了验证在baseline上所做修改的有效性,常常需要开展消融实验。例如,在本案例中,CustomModel在FC-Siam-conc模型的基础上添加了通道和时间两种注意力模块,因此需要通过消融实验探讨各个注意力模块对最终精度的贡献。具体而言,包括以下4种实验情形(配置文件均存储在`configs/levircd/ablation`目录): -1. 基础情况:不使用任何注意力模块,即baseline模型FC-Siam-conc。 -2. 仅添加通道注意力模块,对应的配置文件名称为`custom_model_c.yaml`。 -3. 仅添加时间注意力模块,对应的配置文件名称为`custom_model_t.yaml`。 +1. 基础情况:不使用任何注意力模块,即baseline模型FC-Siam-conc; +2. 仅添加通道注意力模块,对应的配置文件名称为`custom_model_c.yaml`; +3. 仅添加时间注意力模块,对应的配置文件名称为`custom_model_t.yaml`; 4. 标准情况:同时添加通道和时间注意力模块的完整模型。 其中第1和第4个模型,即baseline和完整模型,在[第4节](#4-对比实验)中已经得到了训练、验证和测试。因此,本节只需要关注情形2、3。 @@ -355,7 +329,7 @@ python run_task.py train cd \ ```bash python run_task.py eval cd \ --config "configs/levircd/ablation/{配置文件名称}" \ - --datasets.eval.args.file_list data/levircd/test.txt \ + --datasets.eval.args.file_list "data/levircd/test.txt" \ --resume_checkpoint "exp/levircd/ablation/{消融模型名称}/best_model" ``` @@ -370,32 +344,81 @@ python run_task.py eval cd \ |通道注意力模块|时间注意力模块|IoU%|F1%| |:-:|:-:|:-:|:-:| |||81.31|89.69| -|✓||81.32|89.70| -||✓|81.61|89.88| -|✓|✓|**82.27**|**90.27**| +|✓||81.97|90.09| +||✓|81.59|89.86| +|✓|✓|**82.14**|**90.19**| -其中,最高的指标用粗体表示。从表中数据可知,有限。 +从表中数据可知,无论是通道注意力模块还是时间注意力模块都能对算法的IoU和F1分数指标带来正面贡献,而同时添加两种注意力模块带来的增益是最大的(相比baseline模型IoU增加0.83%,F1分数增加0.50%)。 ## 6 特征可视化实验 -为了更好地探究。 +本节主要对模型的中间特征进行可视化,以进一步验证对baseline模型所做的修改的确实现了增强特征的效果。 + +### 6.1 实验过程 + +通过`tools/visualize_feats.py`脚本实现对模型中间特征的可视化。该脚本接受如下命令行选项: +- `--model_dir`指定需要加载的模型的存储路径。 +- `--im_path`指定输入影像的路径,对于变化检测任务,需要依次指定两幅输入影像的路径。 +- `--save_dir`指定输出目录路径,若目录不存在将被自动创建。 +- `--hook_type`指定抓取的特征类型,有三种取值:当为`forward_in`时,表示抓取指定模块的前向输入特征;当为`forward_out`时,表示抓取指定模块的前向输出特征;当为`backward`时,表示抓取指定参数的梯度。 +- `--layer_names`指定一系列接受或产生需要抓取特征的模块的名称(父模块与子模块间使用`.`分隔)或是模型中权重参数的名称(即[state_dict](https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/load_cn.html)中的key)。 +- `--to_pseudo_color`指定是否将特征图存储为伪彩色图。 +- `--output_size`指定将特征图缩放到的尺寸。 + +`tools/visualize_feats.py`生成的文件遵照`{layer_name}_{j}_vis.png`或`{layer_name}_{i}_{j}_vis.png`格式命名。其中,`{layer_name}`对应`--layer_names`选项中指定的值;`{i}`的数值表示一次抓取到多个输入、输出特征时当前特征所对应的编号;`{j}`的数值在`--hook_type`指定为`forward_in`或`forward_out`时分别表示当前特征图是第几次调用该模块时输入或输出的(模型中的一些模块可能被重复调用,如FC-Siam-conc模型中的`conv4`)。例如,如下指令获取并存储CustomModel模型中`att4`模块的输入与输出特征的可视化结果: + +```bash +IM1_PATH="data/levircd/LEVIR-CD/test/A/test_13/test_13_3.png" +IM2_PATH="data/levircd/LEVIR-CD/test/B/test_13/test_13_3.png" + +python tools/visualize_feats.py \ + --model_dir "exp/levircd/custom_model/best_model" \ + --im_path "${IM1_PATH}" "${IM2_PATH}" \ + --save_dir "exp/vis/test_13_3/in" \ + --hook_type 'forward_in' \ + --layer_names 'att4' \ + --to_pseudo_color \ + --output_size 256 256 + +python tools/visualize_feats.py \ + --model_dir "exp/levircd/custom_model/best_model" \ + --im_path "${IM1_PATH}" "${IM2_PATH}" \ + --save_dir "exp/vis/test_13_3/out" \ + --hook_type 'forward_out' \ + --layer_names 'att4' \ + --to_pseudo_color \ + --output_size 256 256 +``` + +执行上述指令将在`exp/vis/test_13_3/{模型名称}`目录中产生2个子目录,每个子目录中有2个文件,其中`in/att4_0_0_vis.png`和`in/att4_1_0_vis.png`分别表示输入`att4`模块的两个时相特征的可视化结果,`out/att4_0_0_vis.png`和`out/att4_1_0_vis.png`分别表示`att4`模块输出的两个时相特征的可视化结果。 + +### 6.2 实验结果 + +下图从左往右分别为两个时相的输入影像、变化标签、输入混合注意力模块`att4`的两个时相特征图的可视化结果(分别用x1和x2代指)以及`att4`输出的两个时相特征图的可视化结果(分别用y1和y2代指): + +|时相1影像|时相2影像|变化标签|x1|x2|y1|y2| +|:-:|:-:|:-:|:-:|:-:|:-:|:-:| +|||||||| + +对比x2和y2可以看出,经过通道和时间注意力模块处理后,变化特征得到了增强,发生变化的区域在特征图中更加凸显。 ## 5 总结与展望 ### 5.1 总结 -本案例以为经典的FC-Siam-conc模型添加注意力模块为例,演示了使用PaddleRS开展科研实验的典型流程。 -- 精度提升十分有限,算法设计。 +- 本案例以为经典的FC-Siam-conc模型添加注意力模块为例,演示了使用PaddleRS开展科研工作的典型流程。 +- 本案例中对模型的改进带来了一定的目视效果的改善和检测精度提升。 +- 本案例通过消融实验和特征可视化实验证实了所提出改进的有效性。 ### 5.2 展望 - 本案例对所有参与比较的算法使用了相同的训练超参数,但由于模型之间存在差异,使用统一的超参训练往往难以保证所有模型都能取得较好的效果。在后续工作中,可以对每个对比算法进行调参,使其获得最优精度。 -- 本案例只作为 +- 本案例作为使用PaddleRS开展科研工作的简单例子,并未在算法设计上做出较大改进,因此所提出算法相比baseline的精度提升也较为有限。未来可以考虑更复杂的算法设计,以及使用更加先进的模型结构。 ## 参考文献 > [1] Chen, Hao, and Zhenwei Shi. "A spatial-temporal attention-based method and a new dataset for remote sensing image change detection." *Remote Sensing* 12.10 (2020): 1662. -[2] Lebedev, M. A., et al. "CHANGE DETECTION IN REMOTE SENSING IMAGES USING CONDITIONAL ADVERSARIAL NETWORKS." *International Archives of the Photogrammetry, Remote Sensing & Spatial Information Sciences* 42.2 (2018). -[3] Chen, Hao, Zipeng Qi, and Zhenwei Shi. "Remote sensing image change detection with transformers." *IEEE Transactions on Geoscience and Remote Sensing* 60 (2021): 1-14. +[2] Chen, Hao, Zipeng Qi, and Zhenwei Shi. "Remote sensing image change detection with transformers." *IEEE Transactions on Geoscience and Remote Sensing* 60 (2021): 1-14. +[3] Lebedev, M. A., et al. "CHANGE DETECTION IN REMOTE SENSING IMAGES USING CONDITIONAL ADVERSARIAL NETWORKS." *International Archives of the Photogrammetry, Remote Sensing & Spatial Information Sciences* 42.2 (2018). [4] Daudt, Rodrigo Caye, Bertr Le Saux, and Alexandre Boulch. "Fully convolutional siamese networks for change detection." *2018 25th IEEE International Conference on Image Processing (ICIP)*. IEEE, 2018. [5] Woo, Sanghyun, et al. "Cbam: Convolutional block attention module." *Proceedings of the European conference on computer vision (ECCV)*. 2018. diff --git a/examples/rs_research/custom_model.py b/examples/rs_research/custom_model.py index bd11198..b0dbda7 100644 --- a/examples/rs_research/custom_model.py +++ b/examples/rs_research/custom_model.py @@ -193,12 +193,8 @@ class MixedAttention(nn.Layer): if self.has_att_c: self.att_c = ChannelAttention(in_channels, ratio=1) - self.norm_c1 = nn.BatchNorm(in_channels) - self.norm_c2 = nn.BatchNorm(in_channels) else: self.att_c = Identity() - self.norm_c1 = Identity() - self.norm_c2 = Identity() if self.has_att_t: self.att_t = ChannelAttention(2, ratio=1) @@ -207,16 +203,14 @@ class MixedAttention(nn.Layer): def forward(self, x1, x2): if self.has_att_c: - x1 = self.att_c(x1) * x1 - x1 = self.norm_c1(x1) - x2 = self.att_c(x2) * x2 - x2 = self.norm_c2(x2) + x1 = (1 + self.att_c(x1)) * x1 + x2 = (1 + self.att_c(x2)) * x2 if self.has_att_t: b, c = x1.shape[:2] y = paddle.stack([x1, x2], axis=2) y = paddle.flatten(y, stop_axis=1) - y = self.att_t(y) * y + y = (1 + self.att_t(y)) * y y = y.reshape((b, c, 2, *y.shape[2:])) y1, y2 = y[:, :, 0], y[:, :, 1] else: diff --git a/examples/rs_research/params_versus_f1.png b/examples/rs_research/params_versus_f1.png deleted file mode 100644 index f5ba61f44debe0ea4a48e05d09602b3eeb5759f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48900 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 | tee "${LOG_DIR}/${filename%.*}.log" echo done diff --git a/examples/rs_research/scripts/run_benchmark.sh b/examples/rs_research/scripts/run_benchmark.sh index 42f7c39..4b1d375 100644 --- a/examples/rs_research/scripts/run_benchmark.sh +++ b/examples/rs_research/scripts/run_benchmark.sh @@ -2,21 +2,21 @@ set -e -for dataset in levircd svcd; do - config_dir="configs/${dataset}" - log_dir="exp/logs/${dataset}" +DATASET='levircd' - mkdir -p "${log_dir}" +config_dir="configs/${DATASET}" +log_dir="exp/logs/${DATASET}" - for config_file in $(ls "${config_dir}"/*.yaml); do - filename="$(basename ${config_file})" - if [ "${filename}" = "${dataset}.yaml" ]; then - continue - fi - printf '=%.0s' {1..100} && echo - echo -e "\033[33m ${config_file} \033[0m" - printf '=%.0s' {1..100} && echo - python run_task.py train cd --config "${config_file}" 2>&1 | tee "${log_dir}/${filename%.*}.log" - echo - done +mkdir -p "${log_dir}" + +for config_file in $(ls "${config_dir}"/*.yaml); do + filename="$(basename ${config_file})" + if [ "${filename}" = "${DATASET}.yaml" ]; then + continue + fi + printf '=%.0s' {1..100} && echo + echo -e "\033[33m ${config_file} \033[0m" + printf '=%.0s' {1..100} && echo + python run_task.py train cd --config "${config_file}" 2>&1 | tee "${log_dir}/${filename%.*}.log" + echo done diff --git a/examples/rs_research/tools/visualize_feats.py b/examples/rs_research/tools/visualize_feats.py index 62ba93c..bf4ed16 100644 --- a/examples/rs_research/tools/visualize_feats.py +++ b/examples/rs_research/tools/visualize_feats.py @@ -10,6 +10,7 @@ import numpy as np import cv2 import paddle import paddlers +from sklearn.decomposition import PCA _dir = osp.dirname(osp.abspath(__file__)) sys.path.append(osp.abspath(osp.join(_dir, '../'))) @@ -45,7 +46,12 @@ class FeatureContainer: class HookHelper: - def __init__(self, model, fetch_dict, out_dict, hook_type='forward_out'): + def __init__(self, + model, + fetch_dict, + out_dict, + hook_type='forward_out', + auto_key=True): # XXX: A HookHelper object should only be used as a context manager and should not # persist in memory since it may keep references to some very large objects. self.model = model @@ -53,6 +59,7 @@ class HookHelper: self.out_dict = out_dict self._handles = [] self.hook_type = hook_type + self.auto_key = auto_key def __enter__(self): def _hook_proto(x, entry): @@ -62,7 +69,12 @@ class HookHelper: for key, f in zip(entry, x): self.out_dict[key] = f.detach().clone() else: - self.out_dict[entry] = x.detach().clone() + if isinstance(x, tuple) and self.auto_key: + for i, f in enumerate(x): + key = self._gen_key(entry, i) + self.out_dict[key] = f.detach().clone() + else: + self.out_dict[entry] = x.detach().clone() if self.hook_type == 'forward_in': # NOTE: Register forward hooks for LAYERs @@ -103,6 +115,9 @@ class HookHelper: for handle in self._handles: handle.remove() + def _gen_key(self, key, i): + return key + f'_{i}' + def parse_args(): parser = argparse.ArgumentParser() @@ -153,8 +168,13 @@ def to_pseudo_color(gray, color_map=cv2.COLORMAP_JET): def process_fetched_feat(feat, to_pcolor=True): # Convert tensor to array feat = feat.squeeze(0).numpy() - # Average along channel dimension - feat = normalize_minmax(feat.mean(0)) + # Get principal component + shape = feat.shape + x = feat.reshape(shape[0], -1).transpose((1, 0)) + pca = PCA(n_components=1) + y = pca.fit_transform(x) + feat = y.reshape(shape[1:]) + feat = normalize_minmax(feat) feat = quantize_8bit(feat) if to_pcolor: feat = to_pseudo_color(feat) @@ -191,3 +211,4 @@ if __name__ == '__main__': FILENAME_PATTERN.format( key=key.replace('.', '_'), idx=idx)) cv2.imwrite(out_path, im_vis) + print(f"Write feature map to {out_path}")