From 5f353e6c51362b42a2f38c409cbb496463ca1817 Mon Sep 17 00:00:00 2001 From: Bobholamovic Date: Thu, 18 Aug 2022 02:10:33 +0800 Subject: [PATCH] Update examples/rs_research --- examples/rs_research/README.md | 134 +++++++++- examples/rs_research/attach_tools.py | 20 ++ examples/rs_research/config_utils.py | 253 ++++++++++++++++++ examples/rs_research/configs/levircd/bit.yaml | 6 + .../iterative_bit_iter2_gamma01.yaml | 12 + .../iterative_bit_iter2_gamma02.yaml | 12 + .../iterative_bit_iter2_gamma05.yaml | 12 + .../iterative_bit_iter3_gamma01.yaml | 12 + .../iterative_bit_iter3_gamma02.yaml | 12 + .../iterative_bit_iter3_gamma05.yaml | 12 + .../iterative_bit_iter3_gamma10.yaml | 12 + .../rs_research/configs/levircd/levircd.yaml | 74 +++++ examples/rs_research/custom_model.py | 58 ++++ examples/rs_research/custom_trainer.py | 29 ++ examples/rs_research/params_versus_f1.png | Bin 0 -> 48900 bytes examples/rs_research/run_task.py | 115 ++++++++ .../run_benchmark.sh} | 0 .../run_parameter_analysis.sh} | 0 examples/rs_research/train.py | 0 test_tipc/configs/seg/unet/unet.yaml | 8 +- 20 files changed, 765 insertions(+), 16 deletions(-) create mode 100644 examples/rs_research/attach_tools.py create mode 100644 examples/rs_research/config_utils.py create mode 100644 examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma01.yaml create mode 100644 examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma02.yaml create mode 100644 examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma05.yaml create mode 100644 examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma01.yaml create mode 100644 examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma02.yaml create mode 100644 examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma05.yaml create mode 100644 examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma10.yaml create mode 100644 examples/rs_research/params_versus_f1.png create mode 100644 examples/rs_research/run_task.py rename examples/rs_research/{configs/levircd/custom_model.yaml => scripts/run_benchmark.sh} (100%) rename examples/rs_research/{test.py => scripts/run_parameter_analysis.sh} (100%) delete mode 100644 examples/rs_research/train.py diff --git a/examples/rs_research/README.md b/examples/rs_research/README.md index 4501578..77ae5cf 100644 --- a/examples/rs_research/README.md +++ b/examples/rs_research/README.md @@ -34,32 +34,136 @@ python ../../tools/prepare_dataset/prepare_svcd.py \ ### 3.1 问题分析与思路拟定 -科学研究是为了解决实际问题的,本案例也不例外。本案例的研究动机如下:随着深度学习技术应用的不断深入,变化检测领域涌现了许多。与之相对应的是,模型的参数量也越来越大。 +随着深度学习技术应用的不断深入,近年来,变化检测领域涌现了许多基于全卷积神经网络(fully convolutional network, FCN)的遥感影像变化检测算法。与基于特征和基于影像块的方法相比,基于FCN的方法具有处理效率高、依赖超参数少等优势,但其缺点在于参数量往往较大,因而对训练样本的数量更为依赖。尽管中、大型变化检测数据集的数量与日俱增,训练样本日益丰富,但深度学习变化检测模型的参数量也越来越大。下图显示了从2018年到2021年一些已发表的文献中提出的基于FCN的变化检测模型的参数量与其在SVCD数据集上取得的F1分数(柱状图中bar的高度与模型参数量成正比): -[近年来变化检测模型]() +![params_versus_f1](params_versus_f1.png) -诚然,。 +诚然,增大参数数量在大多数情况下等同于增加模型容量,而模型容量的增加意味着模型拟合能力的提升,从而有助于模型在实验数据集上取得更高的精度指标。但是,“更大”一定意味着“更好”吗?答案显然是否定的。在实际应用中,“更大”的遥感影像变化检测模型常常遭遇如下问题: -1. 存储开销。 -2. 过拟合。 +1. 巨大的参数量意味着巨大的存储开销。在许多实际场景中,硬件资源往往是有限的,过多的模型参数将给部署造成困难。 +2. 在数据有限的情况下,大模型更易遭受过拟合,其在实验数据集上看起来良好的结果也难以泛化到真实场景。 -为了解决上述问题,本案例拟提出一种基于网络迭代优化思想的深度学习变化检测算法。本案例的基本思路是,构造一个轻量级的变化检测模型,并以其作为基础迭代单元。每次迭代开始时,由上一次迭代输出的概率图以及原始的输入影像对构造新的输入,实现coarse-to-fine优化。考虑到增加迭代单元的数量将使模型参数量成倍增加,在迭代过程中始终复用同一迭代单元的参数,充分挖掘变化检测网络的拟合能力,迫使其学习到更加有效的特征。这一做法类似[循环神经网络](https://baike.baidu.com/item/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/23199490)。根据此思路可以绘制框图如下: +本案例认为,上述问题的根源在于参数量与数据量的失衡所导致的特征冗余。既然模型的特征存在冗余,是否存在某种手段,能够在固定模型参数量的前提下对特征进行优化,从而“榨取”小模型的更多潜力?基于这个观点,本案例的基本思路是设计一种基于网络迭代优化思想的深度学习变化检测算法。首先,构造一个轻量级的变化检测模型,并以其作为基础迭代单元。在每次迭代开始时,由上一次迭代输出的概率图以及原始的输入影像对构造新的输入,如此逐级实现coarse-to-fine优化。考虑到增加迭代单元的数量将使模型参数量成倍增加,在迭代过程中应始终复用同一迭代单元的参数以充分挖掘变化检测网络的拟合能力,迫使其学习到更加有效的特征。这一做法类似[循环神经网络](https://baike.baidu.com/item/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C/23199490)。根据此思路可以绘制框图如下: -[思路展示]() +![draft](draft.png) -### 3.2 确定baseline +### 3.2 确定baseline模型 -科研工作往往需要“站在巨人的肩膀上”,在前人工作的基础上做“增量创新”。因此,对模型设计类工作而言,选用一个合适的baseline网络至关重要。考虑到本案例的出发点是解决,并且使用了。 +科研工作往往需要“站在巨人的肩膀上”,在前人工作的基础上做“增量创新”。因此,对模型设计类工作而言,选用一个合适的baseline模型至关重要。考虑到本案例的出发点是解决现有模型参数量过大、冗余特征过多的问题,并且在拟定的解决方案中使用到了循环结构,用作baseline的网络结构必须足够轻量和高效(因为最直接的思路是使用baseline作为基础迭代单元)。为此,本案例选用Bitemporal Image Transformer(BIT)作为baseline。BIT是一个轻量级的深度学习变化检测模型,其基本结构如图所示: + +![bit](bit.png) + +BIT的核心思想在于, ### 3.3 定义新模型 -[算法整体框图]() +确定了基本思路和baseline模型之后,可以绘制如下的算法整体框图: + +![framework](framework.png) + +依据此框图,即可在。 + +#### 3.3.1 自定义模型组网 + +在`custom_model.py`中定义模型的宏观(macro)结构以及组成模型的各个微观(micro)模块。例如,当前`custom_model.py`中定义了迭代版本的BIT模型`IterativeBIT`: +```python +@attach +class IterativeBIT(nn.Layer): + def __init__(self, num_iters=1, gamma=0.1, num_classes=2, bit_kwargs=None): + super().__init__() + + if num_iters <= 0: + raise ValueError(f"`num_iters` should have positive value, but got {num_iters}.") + + self.num_iters = num_iters + self.gamma = gamma + + if bit_kwargs is None: + bit_kwargs = dict() + + if 'num_classes' in bit_kwargs: + raise KeyError("'num_classes' should not be set in `bit_kwargs`.") + bit_kwargs['num_classes'] = num_classes + + self.bit = BIT(**bit_kwargs) + + def forward(self, t1, t2): + rate_map = self._init_rate_map(t1.shape) + + for it in range(self.num_iters): + # Construct inputs + x1 = self._constr_iter_input(t1, rate_map) + x2 = self._constr_iter_input(t2, rate_map) + # Get logits + logits_list = self.bit(x1, x2) + # Construct rate map + prob_map = F.softmax(logits_list[0], axis=1) + rate_map = self._constr_rate_map(prob_map) + + return logits_list + ... +``` + +在编写组网相关代码时请注意以下两点: + +1. 所有模型必须为`paddle.nn.Layer`的子类; +2. 包含模型整体逻辑结构的最外层模块须用`@attach`装饰; +3. 对于变化检测任务,`forward()`方法除`self`参数外还接受两个参数`t1`、`t2`,分别表示第一时相和第二时相影像。 + +关于模型定义的更多细节请参考[API文档]()。 + +#### 3.3.2 自定义训练器 + +在`custom_trainer.py`中定义训练器。例如,当前`custom_trainer.py`中定义了与`IterativeBIT`模型对应的训练器: +```python +@attach +class IterativeBIT(BaseChangeDetector): + def __init__(self, + num_classes=2, + use_mixed_loss=False, + losses=None, + num_iters=1, + gamma=0.1, + bit_kwargs=None, + **params): + params.update({ + 'num_iters': num_iters, + 'gamma': gamma, + 'bit_kwargs': bit_kwargs + }) + super().__init__( + model_name='IterativeBIT', + num_classes=num_classes, + use_mixed_loss=use_mixed_loss, + losses=losses, + **params) +``` + +在编写训练器定义相关代码时请注意以下两点: + +1. 对于变化检测任务,训练器必须为`paddlers.tasks.cd.BaseChangeDetector`的子类; +2. 与模型一样,训练器也须用`@attach`装饰; +3. 训练器和模型可以同名。 + +关于训练器定义的更多细节请参考[API文档]()。 ### 3.4 进行参数分析与消融实验 #### 3.4.1 实验设置 -#### 3.4.2 实验结果 +#### 3.4.2 编写配置文件 + +#### 3.4.3 实验结果 + +### 3.5 \*Magic Behind + +本小节涉及技术细节,对于本案例来说属于进阶内容,您可以选择性了解。 + +#### 3.5.1 延迟属性绑定 + +PaddleRS提供了,只需要。`attach_tools.Attach`对象自动。 + +#### 3.5.2 非侵入式轻量级配置系统 ### 3.5 开展特征可视化实验 @@ -75,10 +179,16 @@ python ../../tools/prepare_dataset/prepare_svcd.py \ #### 4.3.2 SVCD数据集上的对比结果 -精度、FLOPs、运行时间 +精度 ## 5 总结与展望 +### 5.1 总结 + +### 5.2 展望 + +耗时,模型大小,FLOPs + ## 参考文献 > [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. diff --git a/examples/rs_research/attach_tools.py b/examples/rs_research/attach_tools.py new file mode 100644 index 0000000..3b737cf --- /dev/null +++ b/examples/rs_research/attach_tools.py @@ -0,0 +1,20 @@ +class Attach(object): + def __init__(self, dst): + self.dst = dst + + def __call__(self, obj, name=None): + if name is None: + # Automatically get names of functions and classes + name = obj.__name__ + if hasattr(self.dst, name): + raise RuntimeError( + f"{self.dst} already has the attribute {name}, which is {getattr(self.dst, name)}." + ) + setattr(self.dst, name, obj) + if hasattr(self.dst, '__all__'): + self.dst.__all__.append(name) + return obj + + @staticmethod + def to(dst): + return Attach(dst) diff --git a/examples/rs_research/config_utils.py b/examples/rs_research/config_utils.py new file mode 100644 index 0000000..9f1b6fc --- /dev/null +++ b/examples/rs_research/config_utils.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python + +import argparse +import os.path as osp +from collections.abc import Mapping + +import yaml + + +def _chain_maps(*maps): + chained = dict() + keys = set().union(*maps) + for key in keys: + vals = [m[key] for m in maps if key in m] + if isinstance(vals[0], Mapping): + chained[key] = _chain_maps(*vals) + else: + chained[key] = vals[0] + return chained + + +def read_config(config_path): + with open(config_path, 'r', encoding='utf-8') as f: + cfg = yaml.safe_load(f) + return cfg or {} + + +def parse_configs(cfg_path, inherit=True): + if inherit: + cfgs = [] + cfgs.append(read_config(cfg_path)) + while cfgs[-1].get('_base_'): + base_path = cfgs[-1].pop('_base_') + curr_dir = osp.dirname(cfg_path) + cfgs.append( + read_config(osp.normpath(osp.join(curr_dir, base_path)))) + return _chain_maps(*cfgs) + else: + return read_config(cfg_path) + + +def _cfg2args(cfg, parser, prefix=''): + node_keys = set() + for k, v in cfg.items(): + opt = prefix + k + if isinstance(v, list): + if len(v) == 0: + parser.add_argument( + '--' + opt, type=object, nargs='*', default=v) + else: + # Only apply to homogeneous lists + if isinstance(v[0], CfgNode): + node_keys.add(opt) + parser.add_argument( + '--' + opt, type=type(v[0]), nargs='*', default=v) + elif isinstance(v, dict): + # Recursively parse a dict + _, new_node_keys = _cfg2args(v, parser, opt + '.') + node_keys.update(new_node_keys) + elif isinstance(v, CfgNode): + node_keys.add(opt) + _, new_node_keys = _cfg2args(v.to_dict(), parser, opt + '.') + node_keys.update(new_node_keys) + elif isinstance(v, bool): + parser.add_argument('--' + opt, action='store_true', default=v) + else: + parser.add_argument('--' + opt, type=type(v), default=v) + return parser, node_keys + + +def _args2cfg(cfg, args, node_keys): + args = vars(args) + for k, v in args.items(): + pos = k.find('.') + if pos != -1: + # Iteratively parse a dict + dict_ = cfg + while pos != -1: + dict_.setdefault(k[:pos], {}) + dict_ = dict_[k[:pos]] + k = k[pos + 1:] + pos = k.find('.') + dict_[k] = v + else: + cfg[k] = v + + for k in node_keys: + pos = k.find('.') + if pos != -1: + # Iteratively parse a dict + dict_ = cfg + while pos != -1: + dict_.setdefault(k[:pos], {}) + dict_ = dict_[k[:pos]] + k = k[pos + 1:] + pos = k.find('.') + v = dict_[k] + dict_[k] = [CfgNode(v_) for v_ in v] if isinstance( + v, list) else CfgNode(v) + else: + v = cfg[k] + cfg[k] = [CfgNode(v_) for v_ in v] if isinstance( + v, list) else CfgNode(v) + + return cfg + + +def parse_args(*args, **kwargs): + cfg_parser = argparse.ArgumentParser(add_help=False) + cfg_parser.add_argument('--config', type=str, default='') + cfg_parser.add_argument('--inherit_off', action='store_true') + cfg_args = cfg_parser.parse_known_args()[0] + cfg_path = cfg_args.config + inherit_on = not cfg_args.inherit_off + + # Main parser + parser = argparse.ArgumentParser( + conflict_handler='resolve', parents=[cfg_parser]) + # Global settings + parser.add_argument('cmd', choices=['train', 'eval']) + parser.add_argument('task', choices=['cd', 'clas', 'det', 'seg']) + + # Data + parser.add_argument('--datasets', type=dict, default={}) + parser.add_argument('--transforms', type=dict, default={}) + parser.add_argument('--download_on', action='store_true') + parser.add_argument('--download_url', type=str, default='') + parser.add_argument('--download_path', type=str, default='./') + + # Optimizer + parser.add_argument('--optimizer', type=dict, default={}) + + # Training related + parser.add_argument('--num_epochs', type=int, default=100) + parser.add_argument('--train_batch_size', type=int, default=8) + parser.add_argument('--save_interval_epochs', type=int, default=1) + parser.add_argument('--log_interval_steps', type=int, default=1) + parser.add_argument('--save_dir', default='../exp/') + parser.add_argument('--learning_rate', type=float, default=0.01) + parser.add_argument('--early_stop', action='store_true') + parser.add_argument('--early_stop_patience', type=int, default=5) + parser.add_argument('--use_vdl', action='store_true') + parser.add_argument('--resume_checkpoint', type=str) + parser.add_argument('--train', type=dict, default={}) + + # Loss + parser.add_argument('--losses', type=dict, nargs='+', default={}) + + # Model + parser.add_argument('--model', type=dict, default={}) + + if osp.exists(cfg_path): + cfg = parse_configs(cfg_path, inherit_on) + parser, node_keys = _cfg2args(cfg, parser, '') + node_keys = sorted(node_keys, reverse=True) + args = parser.parse_args(*args, **kwargs) + return _args2cfg(dict(), args, node_keys) + elif cfg_path != '': + raise FileNotFoundError + else: + args = parser.parse_args() + return _args2cfg(dict(), args, set()) + + +class _CfgNodeMeta(yaml.YAMLObjectMetaclass): + def __call__(cls, obj): + if isinstance(obj, CfgNode): + return obj + return super(_CfgNodeMeta, cls).__call__(obj) + + +class CfgNode(yaml.YAMLObject, metaclass=_CfgNodeMeta): + yaml_tag = u'!Node' + yaml_loader = yaml.SafeLoader + # By default use a lexical scope + ctx = globals() + + def __init__(self, dict_): + super().__init__() + self.type = dict_['type'] + self.args = dict_.get('args', []) + self.module = dict_.get('module', '') + + @classmethod + def set_context(cls, ctx): + # TODO: Implement dynamic scope with inspect.stack() + old_ctx = cls.ctx + cls.ctx = ctx + return old_ctx + + def build_object(self, mod=None): + if mod is None: + mod = self._get_module(self.module) + cls = getattr(mod, self.type) + if isinstance(self.args, list): + args = build_objects(self.args) + obj = cls(*args) + elif isinstance(self.args, dict): + args = build_objects(self.args) + obj = cls(**args) + else: + raise NotImplementedError + return obj + + def _get_module(self, s): + mod = None + while s: + idx = s.find('.') + if idx == -1: + next_ = s + s = '' + else: + next_ = s[:idx] + s = s[idx + 1:] + if mod is None: + mod = self.ctx[next_] + else: + mod = getattr(mod, next_) + return mod + + @staticmethod + def build_objects(cfg, mod=None): + if isinstance(cfg, list): + return [CfgNode.build_objects(c, mod=mod) for c in cfg] + elif isinstance(cfg, CfgNode): + return cfg.build_object(mod=mod) + elif isinstance(cfg, dict): + return { + k: CfgNode.build_objects( + v, mod=mod) + for k, v in cfg.items() + } + else: + return cfg + + def __repr__(self): + return f"(type={self.type}, args={self.args}, module={self.module or ' '})" + + @classmethod + def from_yaml(cls, loader, node): + map_ = loader.construct_mapping(node) + return cls(map_) + + def items(self): + yield from [('type', self.type), ('args', self.args), ('module', + self.module)] + + def to_dict(self): + return dict(self.items()) + + +def build_objects(cfg, mod=None): + return CfgNode.build_objects(cfg, mod=mod) diff --git a/examples/rs_research/configs/levircd/bit.yaml b/examples/rs_research/configs/levircd/bit.yaml index e69de29..f63b0c3 100644 --- a/examples/rs_research/configs/levircd/bit.yaml +++ b/examples/rs_research/configs/levircd/bit.yaml @@ -0,0 +1,6 @@ +_base_: ./levircd.yaml + +save_dir: ./exp/bit/ + +model: !Node + type: BIT diff --git a/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma01.yaml b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma01.yaml new file mode 100644 index 0000000..0526e94 --- /dev/null +++ b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma01.yaml @@ -0,0 +1,12 @@ +_base_: ../levircd.yaml + +save_dir: ./exp/custom_model/iter2_gamma01/ + +model: !Node + type: IterativeBIT + args: + num_iters: 2 + gamma: 0.1 + num_classes: 2 + bit_kwargs: + in_channels: 4 diff --git a/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma02.yaml b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma02.yaml new file mode 100644 index 0000000..c139498 --- /dev/null +++ b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma02.yaml @@ -0,0 +1,12 @@ +_base_: ../levircd.yaml + +save_dir: ./exp/custom_model/iter2_gamma02/ + +model: !Node + type: IterativeBIT + args: + num_iters: 2 + gamma: 0.2 + num_classes: 2 + bit_kwargs: + in_channels: 4 diff --git a/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma05.yaml b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma05.yaml new file mode 100644 index 0000000..54e87fd --- /dev/null +++ b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter2_gamma05.yaml @@ -0,0 +1,12 @@ +_base_: ../levircd.yaml + +save_dir: ./exp/custom_model/iter2_gamma05/ + +model: !Node + type: IterativeBIT + args: + num_iters: 2 + gamma: 0.5 + num_classes: 2 + bit_kwargs: + in_channels: 4 diff --git a/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma01.yaml b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma01.yaml new file mode 100644 index 0000000..7369ff0 --- /dev/null +++ b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma01.yaml @@ -0,0 +1,12 @@ +_base_: ../levircd.yaml + +save_dir: ./exp/custom_model/iter3_gamma01/ + +model: !Node + type: IterativeBIT + args: + num_iters: 3 + gamma: 0.1 + num_classes: 2 + bit_kwargs: + in_channels: 4 diff --git a/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma02.yaml b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma02.yaml new file mode 100644 index 0000000..7a1a55a --- /dev/null +++ b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma02.yaml @@ -0,0 +1,12 @@ +_base_: ../levircd.yaml + +save_dir: ./exp/custom_model/iter3_gamma02/ + +model: !Node + type: IterativeBIT + args: + num_iters: 3 + gamma: 0.2 + num_classes: 2 + bit_kwargs: + in_channels: 4 diff --git a/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma05.yaml b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma05.yaml new file mode 100644 index 0000000..52d7f51 --- /dev/null +++ b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma05.yaml @@ -0,0 +1,12 @@ +_base_: ../levircd.yaml + +save_dir: ./exp/custom_model/iter3_gamma05/ + +model: !Node + type: IterativeBIT + args: + num_iters: 3 + gamma: 0.5 + num_classes: 2 + bit_kwargs: + in_channels: 4 diff --git a/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma10.yaml b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma10.yaml new file mode 100644 index 0000000..a195e55 --- /dev/null +++ b/examples/rs_research/configs/levircd/custom_model/iterative_bit_iter3_gamma10.yaml @@ -0,0 +1,12 @@ +_base_: ../levircd.yaml + +save_dir: ./exp/custom_model/iter3_gamma10/ + +model: !Node + type: IterativeBIT + args: + num_iters: 3 + gamma: 1.0 + num_classes: 2 + bit_kwargs: + in_channels: 4 diff --git a/examples/rs_research/configs/levircd/levircd.yaml b/examples/rs_research/configs/levircd/levircd.yaml index e69de29..cdc4404 100644 --- a/examples/rs_research/configs/levircd/levircd.yaml +++ b/examples/rs_research/configs/levircd/levircd.yaml @@ -0,0 +1,74 @@ +# Basic configurations of LEVIR-CD dataset + +datasets: + train: !Node + type: CDDataset + args: + data_dir: ./data/levircd/ + file_list: ./data/levircd/train.txt + label_list: null + num_workers: 2 + shuffle: True + with_seg_labels: False + binarize_labels: True + eval: !Node + type: CDDataset + args: + data_dir: ./data/levircd/ + file_list: ./data/levircd/val.txt + label_list: null + num_workers: 0 + shuffle: False + with_seg_labels: False + binarize_labels: True +transforms: + train: + - !Node + type: DecodeImg + - !Node + type: RandomFlipOrRotate + args: + probs: [0.35, 0.35] + probsf: [0.5, 0.5, 0, 0, 0] + probsr: [0.33, 0.34, 0.33] + - !Node + type: Normalize + args: + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + - !Node + type: ArrangeChangeDetector + args: ['train'] + eval: + - !Node + type: DecodeImg + - !Node + type: Normalize + args: + mean: [0.5, 0.5, 0.5] + std: [0.5, 0.5, 0.5] + - !Node + type: ArrangeChangeDetector + args: ['eval'] +download_on: False + +num_epochs: 40 +train_batch_size: 8 +optimizer: !Node + type: Adam + args: + learning_rate: !Node + type: StepDecay + module: paddle.optimizer.lr + args: + learning_rate: 0.002 + step_size: 30 + gamma: 0.2 +save_interval_epochs: 10 +log_interval_steps: 500 +save_dir: ./exp/ +learning_rate: 0.002 +early_stop: False +early_stop_patience: 5 +use_vdl: True +resume_checkpoint: '' diff --git a/examples/rs_research/custom_model.py b/examples/rs_research/custom_model.py index e69de29..feb4238 100644 --- a/examples/rs_research/custom_model.py +++ b/examples/rs_research/custom_model.py @@ -0,0 +1,58 @@ +import paddle +import paddle.nn as nn +import paddle.nn.functional as F +import paddlers +from paddlers.rs_models.cd import BIT +from attach_tools import Attach + +attach = Attach.to(paddlers.rs_models.cd) + + +@attach +class IterativeBIT(nn.Layer): + def __init__(self, num_iters=1, gamma=0.1, num_classes=2, bit_kwargs=None): + super().__init__() + + if num_iters <= 0: + raise ValueError( + f"`num_iters` should have positive value, but got {num_iters}.") + + self.num_iters = num_iters + self.gamma = gamma + + if bit_kwargs is None: + bit_kwargs = dict() + + if 'num_classes' in bit_kwargs: + raise KeyError("'num_classes' should not be set in `bit_kwargs`.") + bit_kwargs['num_classes'] = num_classes + + self.bit = BIT(**bit_kwargs) + + def forward(self, t1, t2): + rate_map = self._init_rate_map(t1.shape) + + for it in range(self.num_iters): + # Construct inputs + x1 = self._constr_iter_input(t1, rate_map) + x2 = self._constr_iter_input(t2, rate_map) + # Get logits + logits_list = self.bit(x1, x2) + # Construct rate map + prob_map = F.softmax(logits_list[0], axis=1) + rate_map = self._constr_rate_map(prob_map) + + return logits_list + + def _constr_iter_input(self, im, rate_map): + return paddle.concat([im.rate_map], axis=1) + + def _init_rate_map(self, im_shape): + b, _, h, w = im_shape + return paddle.zeros((b, 1, h, w)) + + def _constr_rate_map(self, prob_map): + if prob_map.shape[1] != 2: + raise ValueError( + f"`prob_map.shape[1]` must be 2, but got {prob_map.shape[1]}.") + return (prob_map[:, 1:2] * self.gamma) diff --git a/examples/rs_research/custom_trainer.py b/examples/rs_research/custom_trainer.py index e69de29..2829863 100644 --- a/examples/rs_research/custom_trainer.py +++ b/examples/rs_research/custom_trainer.py @@ -0,0 +1,29 @@ +import paddlers +from paddlers.tasks.change_detector import BaseChangeDetector + +from attach_tools import Attach + +attach = Attach.to(paddlers.tasks.change_detector) + + +@attach +class IterativeBIT(BaseChangeDetector): + def __init__(self, + num_classes=2, + use_mixed_loss=False, + losses=None, + num_iters=1, + gamma=0.1, + bit_kwargs=None, + **params): + params.update({ + 'num_iters': num_iters, + 'gamma': gamma, + 'bit_kwargs': bit_kwargs + }) + super().__init__( + model_name='IterativeBIT', + num_classes=num_classes, + use_mixed_loss=use_mixed_loss, + losses=losses, + **params) diff --git a/examples/rs_research/params_versus_f1.png b/examples/rs_research/params_versus_f1.png new file mode 100644 index 0000000000000000000000000000000000000000..f5ba61f44debe0ea4a48e05d09602b3eeb5759f0 GIT binary patch literal 48900 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