FInish rs_research example

own
Bobholamovic 2 years ago
parent 132df1a8dd
commit fc56e9afba
  1. 205
      examples/rs_research/README.md
  2. 12
      examples/rs_research/custom_model.py
  3. BIN
      examples/rs_research/params_versus_f1.png
  4. 3
      examples/rs_research/scripts/run_ablation.sh
  5. 30
      examples/rs_research/scripts/run_benchmark.sh
  6. 29
      examples/rs_research/tools/visualize_feats.py

@ -16,27 +16,24 @@ cd examples/rs_research
## 2 数据准备 ## 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 ```bash
mkdir data/ mkdir data/
python ../../tools/prepare_dataset/prepare_levircd.py \ python ../../tools/prepare_dataset/prepare_levircd.py \
--in_dataset_dir "{LEVIR-CD数据集存放目录路径}" \ --in_dataset_dir "{LEVIR-CD数据集存放目录路径}" \
--out_dataset_dir 'data/levircd' \ --out_dataset_dir "data/levircd" \
--crop_size 256 \ --crop_size 256 \
--crop_stride 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 模型设计
### 3.1 问题分析与思路拟定 ### 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) ![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[4]为baseline网络,利用通道和时间注意力模块对网络的中间层特征进行优化,从而减小特征冗余,提升检测效果。在具体的模块设计方面,选用论文[5]中提出的通道注意力模块实现通道和时间维度的特征增强。
FC-Siam-conc的网络结构如图所示:
![fc_siam_conc](fc_siam_conc.png)
本案例计划在解码器中首个Concat模块之前添加通道与时间注意力模块组合而成的混合注意力模块以优化从编码器传来的特征,并将新模型称为CustomModel。
### 3.2 模型定义 ### 3.2 模型定义
本小节基于PaddlePaddle框架与PaddleRS库实现[3.1节](#3.1-问题分析与思路拟定)中提出的想法。 本小节基于PaddlePaddle框架与PaddleRS库实现[3.1节](#3.1-问题分析与思路拟定)中提出的想法。
@ -104,17 +107,11 @@ class MixedAttention(nn.Layer):
# 每个注意力模块都是可选的 # 每个注意力模块都是可选的
if self.has_att_c: if self.has_att_c:
self.att_c = ChannelAttention(in_channels, ratio=1) self.att_c = ChannelAttention(in_channels, ratio=1)
# 在时间注意力模块之后增加归一化层
# 利用BN层中的可学习参数增强模型的拟合能力
self.norm_c1 = nn.BatchNorm(in_channels)
self.norm_c2 = nn.BatchNorm(in_channels)
else: else:
self.att_c = Identity() self.att_c = Identity()
self.norm_c1 = Identity()
self.norm_c2 = Identity()
# 时间注意力模块部分复用通道注意力的逻辑,在`forward()`中将具体解释
if has_att_t: if has_att_t:
# 时间注意力模块部分复用通道注意力的逻辑,在`forward()`中将具体解释
self.att_t = ChannelAttention(2, ratio=1) self.att_t = ChannelAttention(2, ratio=1)
else: else:
self.att_t = Identity() self.att_t = Identity()
@ -124,11 +121,10 @@ class MixedAttention(nn.Layer):
if self.has_att_c: if self.has_att_c:
# 首先使用通道注意力模块对特征进行优化 # 首先使用通道注意力模块对特征进行优化
# 两个时相的编码特征共享通道注意力模块,但使用各自的归一化层 # 两个时相的编码特征共享通道注意力模块
x1 = self.att_c(x1) * x1 # 添加残差连接以加速收敛
x1 = self.norm_c1(x1) x1 = (1 + self.att_c(x1)) * x1
x2 = self.att_c(x2) * x2 x2 = (1 + self.att_c(x2)) * x2
x2 = self.norm_c2(x2)
if self.has_att_t: if self.has_att_t:
b, c = x1.shape[:2] b, c = x1.shape[:2]
@ -138,7 +134,8 @@ class MixedAttention(nn.Layer):
# 将b和c两个维度合并,输出tensor形状为[b*c, t, h, w] # 将b和c两个维度合并,输出tensor形状为[b*c, t, h, w]
y = paddle.flatten(y, stop_axis=1) y = paddle.flatten(y, stop_axis=1)
# 此时,时间维度已经替代了原先的通道维度,将四维tensor输入ChannelAttention模块进行处理 # 此时,时间维度已经替代了原先的通道维度,将四维tensor输入ChannelAttention模块进行处理
y = self.att_t(y) * y # 同样添加残差连接
y = (1 + self.att_t(y)) * y
# 从处理结果中分离两个时相的信息 # 从处理结果中分离两个时相的信息
y = y.reshape((b, c, 2, *y.shape[2:])) y = y.reshape((b, c, 2, *y.shape[2:]))
y1, y2 = y[:, :, 0], y[:, :, 1] y1, y2 = y[:, :, 0], y[:, :, 1]
@ -159,10 +156,10 @@ class MixedAttention(nn.Layer):
在编写组网相关代码时请注意以下两点: 在编写组网相关代码时请注意以下两点:
1. 所有模型必须为`paddle.nn.Layer`的子类; 1. 所有模型必须为`paddle.nn.Layer`的子类;
2. 包含模型整体逻辑结构的最外层模块须用`@attach`装饰; 2. 包含模型整体逻辑结构的最外层模块(如本例中的`CustomModel`类)须用`@attach`装饰;
3. 对于变化检测任务,`forward()`方法除`self`参数外还接受两个参数`t1`、`t2`,分别表示第一时相和第二时相影像。 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 自定义训练器 #### 3.2.2 自定义训练器
@ -203,21 +200,21 @@ class CustomTrainer(BaseChangeDetector):
## 4 对比实验 ## 4 对比实验
为了验证模型设计的有效性,通常需要开展对比实验,在一个或多个数据集上比较所提出模型与其它模型的精度和性能。在本案例中,将自定义模型与FC-EF、FC-Siam-diff、FC-Siam-conc三种结构进行比较,这三个模型均来自论文[4]。 为了验证模型设计的有效性,通常需要开展对比实验,在一个或多个数据集上比较所提出模型与其它模型的精度和性能。在本案例中,将自定义模型CustomModel与FC-EF、FC-Siam-diff、FC-Siam-conc三种结构进行比较,这三个模型均来自论文[4]。
### 4.1 实验过程 ### 4.1 实验过程
使用如下指令在LEVIR-CD与SVCD数据集上执行对所有参与对比的模型的训练: 使用如下指令在LEVIR-CD数据集上执行对所有参与对比的模型的训练:
```bash ```bash
bash scripts/run_benchmark.sh bash scripts/run_benchmark.sh
``` ```
或者,可以按照以下格式执行对某个模型在某一数据集上的训练: 或者,可以按照以下格式执行对某个模型的训练:
```bash ```bash
python run_task.py train cd \ python run_task.py train cd \
--config "configs/{数据集名称}/{配置文件名称}" \ --config "configs/levircd/{配置文件名称}" \
2>&1 | tee "{日志路径}" 2>&1 | tee "{日志路径}"
``` ```
@ -225,9 +222,9 @@ python run_task.py train cd \
```bash ```bash
python run_task.py eval cd \ python run_task.py eval cd \
--config "configs/{数据集名称}/{配置文件名称}" \ --config "configs/levircd/{配置文件名称}" \
--datasets.eval.args.file_list "data/{数据集名称}/test.txt" \ --datasets.eval.args.file_list "data/levircd/test.txt" \
--resume_checkpoint "exp/{数据集名称}/{模型名称}/best_model" --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)。 训练程序默认开启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 ```bash
python predict_cd.py \ python predict_cd.py \
--model_dir "exp/{数据集名称}/{模型名称}/best_model" \ --model_dir "exp/levircd/{模型名称}/best_model" \
--data_dir "data/{数据集名称}" \ --data_dir "data/levircd" \
--file_list "data/{数据集名称}/test.txt" \ --file_list "data/levircd/test.txt" \
--save_dir "exp/predict/{数据集名称}/{模型名称}" --save_dir "exp/predict/levircd/{模型名称}"
``` ```
之后,可在`exp/predict/{数据集名称}/{模型名称}`目录查看保存的输出结果。 之后,可在`exp/predict/levircd/{模型名称}`目录查看保存的输出结果。
可以通过`tools/collect_imgs.py`脚本将输入图像、真值标签以及多个模型的预测结果放置在一个目录下以便于观察比较。该脚本接受三个命令行选项: 可以通过`tools/collect_imgs.py`脚本将输入图像、变化标签以及多个模型的预测结果放置在一个目录下以便于观察比较。该脚本接受三个命令行选项:
- 使用`--globs`指定一系列通配符(可用于Python的[`glob.glob()`函数](https://docs.python.org/zh-cn/3/library/glob.html#glob.glob),用于匹配需要收集的图像; - `--globs`指定一系列通配符(可用于Python的[`glob.glob()`函数](https://docs.python.org/zh-cn/3/library/glob.html#glob.glob),用于匹配需要收集的图像;
- 使用`--tags`为`--globs`中的每一项指定一个别名,在存储目录中,相应的图像名将被替换为存储的别名; - `--tags`为`--globs`中的每一项指定一个别名,在存储目录中,相应的图像名将被替换为存储的别名;
- 使用`--save_dir`指定输出目录路径,若目录不存在将被自动创建。 - `--save_dir`指定输出目录路径,若目录不存在将被自动创建。
例如,对于LEVIR-CD数据集,执行如下指令: 例如,对于LEVIR-CD数据集,执行如下指令:
@ -262,74 +259,51 @@ python tools/collect_imgs.py \
--save_dir "exp/collect/levircd" --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 ```bash
python tools/collect_imgs.py --globs "exp/predict/levircd/{新增模型名称}/*.png" --tags '{新增模型名称}' --save_dir "exp/collect/levircd" 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)和模型参数量: 此外,为了从精度和性能两个方面综合评估变化检测算法,可以通过如下指令计算变化检测模型的[浮点计算数(floating point operations, FLOPs)](https://blog.csdn.net/IT_flying625/article/details/104898152)和模型参数量:
```bash ```bash
python tools/analyze_model.py --model_dir "exp/{数据集名称}/{模型名称}/best_model" python tools/analyze_model.py --model_dir "exp/levircd/{模型名称}/best_model"
``` ```
### 4.2 实验结果 ### 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)作为定量评价指标。在每个数据集上,从目视效果和定量指标两个方面对算法效果进行评判。 本案例使用变化类的[交并比(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数据集上的对比结果
**目视效果对比** #### 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%| |模型名称|FLOPs(G)|参数量(M)|IoU%|F1%|
|:-:|:-:|:-:|:-:|:-:| |:-:|:-:|:-:|:-:|:-:|
|FC-EF|3.57|1.35|79.05|88.30| |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|<u>81.33</u>|<u>89.70</u>|
|FC-Siam-conc|5.31|1.55|81.31|89.69| |FC-Siam-conc|5.31|1.55|81.31|89.69|
|CustomModel|5.31|1.58|**82.27**|**90.27**| |CustomModel|5.31|1.58|**82.14**|**90.19**|
#### 4.2.2 SVCD数据集上的对比结果
**目视效果对比** 表中最高的精度指标用粗体表示、次高的指标用下划线标示。从表中可以看出,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 消融实验 ## 5 消融实验
在科研过程中,为了验证在baseline上所做修改的有效性,常常需要开展消融实验。例如,在本案例中,自定义模型在FC-Siam-conc模型的基础上添加了通道和时间两种注意力模块,因此需要通过消融实验探讨各个注意力模块对最终精度的贡献。具体而言,包括以下4种实验情形(配置文件均存储在`configs/levircd/ablation`目录): 在科研过程中,为了验证在baseline上所做修改的有效性,常常需要开展消融实验。例如,在本案例中,CustomModel在FC-Siam-conc模型的基础上添加了通道和时间两种注意力模块,因此需要通过消融实验探讨各个注意力模块对最终精度的贡献。具体而言,包括以下4种实验情形(配置文件均存储在`configs/levircd/ablation`目录):
1. 基础情况:不使用任何注意力模块,即baseline模型FC-Siam-conc 1. 基础情况:不使用任何注意力模块,即baseline模型FC-Siam-conc;
2. 仅添加通道注意力模块,对应的配置文件名称为`custom_model_c.yaml` 2. 仅添加通道注意力模块,对应的配置文件名称为`custom_model_c.yaml`;
3. 仅添加时间注意力模块,对应的配置文件名称为`custom_model_t.yaml` 3. 仅添加时间注意力模块,对应的配置文件名称为`custom_model_t.yaml`;
4. 标准情况:同时添加通道和时间注意力模块的完整模型。 4. 标准情况:同时添加通道和时间注意力模块的完整模型。
其中第1和第4个模型,即baseline和完整模型,在[第4节](#4-对比实验)中已经得到了训练、验证和测试。因此,本节只需要关注情形2、3。 其中第1和第4个模型,即baseline和完整模型,在[第4节](#4-对比实验)中已经得到了训练、验证和测试。因此,本节只需要关注情形2、3。
@ -355,7 +329,7 @@ python run_task.py train cd \
```bash ```bash
python run_task.py eval cd \ python run_task.py eval cd \
--config "configs/levircd/ablation/{配置文件名称}" \ --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" --resume_checkpoint "exp/levircd/ablation/{消融模型名称}/best_model"
``` ```
@ -370,32 +344,81 @@ python run_task.py eval cd \
|通道注意力模块|时间注意力模块|IoU%|F1%| |通道注意力模块|时间注意力模块|IoU%|F1%|
|:-:|:-:|:-:|:-:| |:-:|:-:|:-:|:-:|
|||81.31|89.69| |||81.31|89.69|
|✓||81.32|89.70| |✓||<u>81.97</u>|<u>90.09</u>|
||✓|81.61|89.88| ||✓|81.59|89.86|
|✓|✓|**82.27**|**90.27**| |✓|✓|**82.14**|**90.19**|
其中,最高的指标用粗体表示。从表中数据可知,有限 从表中数据可知,无论是通道注意力模块还是时间注意力模块都能对算法的IoU和F1分数指标带来正面贡献,而同时添加两种注意力模块带来的增益是最大的(相比baseline模型IoU增加0.83%,F1分数增加0.50%)
## 6 特征可视化实验 ## 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 总结与展望
### 5.1 总结 ### 5.1 总结
本案例以为经典的FC-Siam-conc模型添加注意力模块为例,演示了使用PaddleRS开展科研实验的典型流程。 - 本案例以为经典的FC-Siam-conc模型添加注意力模块为例,演示了使用PaddleRS开展科研工作的典型流程。
- 精度提升十分有限,算法设计。 - 本案例中对模型的改进带来了一定的目视效果的改善和检测精度提升。
- 本案例通过消融实验和特征可视化实验证实了所提出改进的有效性。
### 5.2 展望 ### 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. > [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). [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] 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. [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. [5] Woo, Sanghyun, et al. "Cbam: Convolutional block attention module." *Proceedings of the European conference on computer vision (ECCV)*. 2018.

@ -193,12 +193,8 @@ class MixedAttention(nn.Layer):
if self.has_att_c: if self.has_att_c:
self.att_c = ChannelAttention(in_channels, ratio=1) self.att_c = ChannelAttention(in_channels, ratio=1)
self.norm_c1 = nn.BatchNorm(in_channels)
self.norm_c2 = nn.BatchNorm(in_channels)
else: else:
self.att_c = Identity() self.att_c = Identity()
self.norm_c1 = Identity()
self.norm_c2 = Identity()
if self.has_att_t: if self.has_att_t:
self.att_t = ChannelAttention(2, ratio=1) self.att_t = ChannelAttention(2, ratio=1)
@ -207,16 +203,14 @@ class MixedAttention(nn.Layer):
def forward(self, x1, x2): def forward(self, x1, x2):
if self.has_att_c: if self.has_att_c:
x1 = self.att_c(x1) * x1 x1 = (1 + self.att_c(x1)) * x1
x1 = self.norm_c1(x1) x2 = (1 + self.att_c(x2)) * x2
x2 = self.att_c(x2) * x2
x2 = self.norm_c2(x2)
if self.has_att_t: if self.has_att_t:
b, c = x1.shape[:2] b, c = x1.shape[:2]
y = paddle.stack([x1, x2], axis=2) y = paddle.stack([x1, x2], axis=2)
y = paddle.flatten(y, stop_axis=1) 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:])) y = y.reshape((b, c, 2, *y.shape[2:]))
y1, y2 = y[:, :, 0], y[:, :, 1] y1, y2 = y[:, :, 0], y[:, :, 1]
else: else:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

@ -12,9 +12,6 @@ for config_file in $(ls "${CONFIG_DIR}"/*.yaml); do
printf '=%.0s' {1..100} && echo printf '=%.0s' {1..100} && echo
echo -e "\033[33m ${config_file} \033[0m" echo -e "\033[33m ${config_file} \033[0m"
printf '=%.0s' {1..100} && echo printf '=%.0s' {1..100} && echo
if [ ${filename} = 'custom_model_cs.yaml' ] || [ ${filename} = 'custom_model_ct.yaml' ]; then
continue
fi
python run_task.py train cd --config "${config_file}" 2>&1 | tee "${LOG_DIR}/${filename%.*}.log" python run_task.py train cd --config "${config_file}" 2>&1 | tee "${LOG_DIR}/${filename%.*}.log"
echo echo
done done

@ -2,21 +2,21 @@
set -e set -e
for dataset in levircd svcd; do DATASET='levircd'
config_dir="configs/${dataset}"
log_dir="exp/logs/${dataset}"
mkdir -p "${log_dir}" config_dir="configs/${DATASET}"
log_dir="exp/logs/${DATASET}"
for config_file in $(ls "${config_dir}"/*.yaml); do mkdir -p "${log_dir}"
filename="$(basename ${config_file})"
if [ "${filename}" = "${dataset}.yaml" ]; then for config_file in $(ls "${config_dir}"/*.yaml); do
continue filename="$(basename ${config_file})"
fi if [ "${filename}" = "${DATASET}.yaml" ]; then
printf '=%.0s' {1..100} && echo continue
echo -e "\033[33m ${config_file} \033[0m" fi
printf '=%.0s' {1..100} && echo printf '=%.0s' {1..100} && echo
python run_task.py train cd --config "${config_file}" 2>&1 | tee "${log_dir}/${filename%.*}.log" echo -e "\033[33m ${config_file} \033[0m"
echo printf '=%.0s' {1..100} && echo
done python run_task.py train cd --config "${config_file}" 2>&1 | tee "${log_dir}/${filename%.*}.log"
echo
done done

@ -10,6 +10,7 @@ import numpy as np
import cv2 import cv2
import paddle import paddle
import paddlers import paddlers
from sklearn.decomposition import PCA
_dir = osp.dirname(osp.abspath(__file__)) _dir = osp.dirname(osp.abspath(__file__))
sys.path.append(osp.abspath(osp.join(_dir, '../'))) sys.path.append(osp.abspath(osp.join(_dir, '../')))
@ -45,7 +46,12 @@ class FeatureContainer:
class HookHelper: 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 # 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. # persist in memory since it may keep references to some very large objects.
self.model = model self.model = model
@ -53,6 +59,7 @@ class HookHelper:
self.out_dict = out_dict self.out_dict = out_dict
self._handles = [] self._handles = []
self.hook_type = hook_type self.hook_type = hook_type
self.auto_key = auto_key
def __enter__(self): def __enter__(self):
def _hook_proto(x, entry): def _hook_proto(x, entry):
@ -62,7 +69,12 @@ class HookHelper:
for key, f in zip(entry, x): for key, f in zip(entry, x):
self.out_dict[key] = f.detach().clone() self.out_dict[key] = f.detach().clone()
else: 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': if self.hook_type == 'forward_in':
# NOTE: Register forward hooks for LAYERs # NOTE: Register forward hooks for LAYERs
@ -103,6 +115,9 @@ class HookHelper:
for handle in self._handles: for handle in self._handles:
handle.remove() handle.remove()
def _gen_key(self, key, i):
return key + f'_{i}'
def parse_args(): def parse_args():
parser = argparse.ArgumentParser() 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): def process_fetched_feat(feat, to_pcolor=True):
# Convert tensor to array # Convert tensor to array
feat = feat.squeeze(0).numpy() feat = feat.squeeze(0).numpy()
# Average along channel dimension # Get principal component
feat = normalize_minmax(feat.mean(0)) 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) feat = quantize_8bit(feat)
if to_pcolor: if to_pcolor:
feat = to_pseudo_color(feat) feat = to_pseudo_color(feat)
@ -191,3 +211,4 @@ if __name__ == '__main__':
FILENAME_PATTERN.format( FILENAME_PATTERN.format(
key=key.replace('.', '_'), idx=idx)) key=key.replace('.', '_'), idx=idx))
cv2.imwrite(out_path, im_vis) cv2.imwrite(out_path, im_vis)
print(f"Write feature map to {out_path}")

Loading…
Cancel
Save