Merge branch 'develop' into refactor_res

own
Lin Manhui 2 years ago committed by GitHub
commit 6bffd14166
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 32
      .github/workflows/build.yaml
  2. 4
      README.md
  3. 2
      docs/apis/train.md
  4. 2
      paddlers/rs_models/cd/__init__.py
  5. 478
      paddlers/rs_models/cd/fccdn.py
  6. 15
      paddlers/rs_models/cd/losses/__init__.py
  7. 170
      paddlers/rs_models/cd/losses/fccdn_loss.py
  8. 32
      paddlers/tasks/change_detector.py
  9. 2
      paddlers/tasks/segmenter.py
  10. 6
      paddlers/transforms/operators.py
  11. 13
      test_tipc/configs/cd/fccdn/fccdn.yaml
  12. 53
      test_tipc/configs/cd/fccdn/train_infer_python.txt
  13. 2
      test_tipc/configs/seg/unet/unet.yaml
  14. 11
      tests/rs_models/test_cd_models.py
  15. 36
      tests/rs_models/test_model.py
  16. 41
      tests/run_ci_dev.sh
  17. 13
      tests/run_tipc_lite.sh
  18. 94
      tutorials/train/change_detection/fccdn.py

@ -1,4 +1,4 @@
name: build and test
name: build
on:
push:
@ -17,7 +17,7 @@ concurrency:
cancel-in-progress: true
jobs:
build_and_test_cpu:
build_cpu:
runs-on: ${{ matrix.os }}
strategy:
matrix:
@ -53,29 +53,5 @@ jobs:
python -m pip install -e .
- name: Install GDAL
run: python -m pip install ${{ matrix.gdal-whl-url }}
- name: Run unittests
run: |
cd tests
bash run_fast_tests.sh
shell: bash
build_and_test_cuda102:
runs-on: ubuntu-18.04
container:
image: registry.baidubce.com/paddlepaddle/paddle:2.3.1-gpu-cuda10.2-cudnn7
steps:
- uses: actions/checkout@v3
- name: Upgrade pip
run: python3.7 -m pip install pip --upgrade --user
- name: Install PaddleRS
run: |
python3.7 -m pip install -r requirements.txt
python3.7 -m pip install -e .
- name: Install GDAL
run: python3.7 -m pip install https://versaweb.dl.sourceforge.net/project/gdal-wheels-for-linux/GDAL-3.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl
# Do not run unittests, because there is NO GPU in the machine.
# - name: Run unittests
# run: |
# cd tests
# bash run_fast_tests.sh
# shell: bash
- name: Test installation
run: python -c "import paddlers; print(paddlers.__version__)"

@ -8,7 +8,7 @@
<!-- [![version](https://img.shields.io/github/release/PaddlePaddle/PaddleRS.svg)](https://github.com/PaddlePaddle/PaddleRS/releases) -->
[![license](https://img.shields.io/badge/license-Apache%202-blue.svg)](LICENSE)
[![build status](https://github.com/PaddlePaddle/PaddleRS/actions/workflows/build_and_test.yaml/badge.svg?branch=develop)](https://github.com/PaddlePaddle/PaddleRS/actions)
[![build status](https://github.com/PaddlePaddle/PaddleRS/actions/workflows/build.yaml/badge.svg?branch=develop)](https://github.com/PaddlePaddle/PaddleRS/actions)
![python version](https://img.shields.io/badge/python-3.7+-orange.svg)
![support os](https://img.shields.io/badge/os-linux%2C%20win%2C%20mac-yellow.svg)
</div>
@ -179,7 +179,7 @@ PaddleRS目录树中关键部分如下:
* 如果您发现任何PaddleRS存在的问题或是对PaddleRS有建议, 欢迎通过[GitHub Issues](https://github.com/PaddlePaddle/PaddleRS/issues)向我们提出。
* 欢迎加入PaddleRS微信群
<div align="center">
<img src="./docs/images/wechat.png" width = "150" />
<img src="https://user-images.githubusercontent.com/21275753/186310647-603f4b1c-5bbe-4b0d-a645-328d85789a5d.png" width = "150" />
</div>
## 使用教程 <img src="./docs/images/teach.png" width="30"/>

@ -29,7 +29,7 @@
### 初始化`BaseSegmenter`子类对象
- 一般支持设置`input_channel`、`num_classes`以及`use_mixed_loss`参数,分别表示输入通道数、输出类别数以及是否使用预置的混合损失。部分模型如`FarSeg`暂不支持对`input_channel`参数的设置。
- 一般支持设置`in_channels`、`num_classes`以及`use_mixed_loss`参数,分别表示输入通道数、输出类别数以及是否使用预置的混合损失。部分模型如`FarSeg`暂不支持对`in_channels`参数的设置。
- `use_mixed_loss`参将在未来被弃用,因此不建议使用。
- 不同的子类支持与模型相关的输入参数,详情请参考[模型定义](https://github.com/PaddlePaddle/PaddleRS/blob/develop/paddlers/rs_models/seg)和[训练器定义](https://github.com/PaddlePaddle/PaddleRS/blob/develop/paddlers/tasks/segmentor.py)。

@ -23,3 +23,5 @@ from .fc_ef import FCEarlyFusion
from .fc_siam_conc import FCSiamConc
from .fc_siam_diff import FCSiamDiff
from .changeformer import ChangeFormer
from .fccdn import FCCDN
from .losses import fccdn_ssl_loss

@ -0,0 +1,478 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
from .layers import BasicConv, MaxPool2x2, Conv1x1, Conv3x3
bn_mom = 1 - 0.0003
class NLBlock(nn.Layer):
def __init__(self, in_channels):
super(NLBlock, self).__init__()
self.conv_v = BasicConv(
in_ch=in_channels,
out_ch=in_channels,
kernel_size=3,
norm=nn.BatchNorm2D(
in_channels, momentum=0.9))
self.W = BasicConv(
in_ch=in_channels,
out_ch=in_channels,
kernel_size=3,
norm=nn.BatchNorm2D(
in_channels, momentum=0.9),
act=nn.ReLU())
def forward(self, x):
batch_size, c, h, w = x.shape[0], x.shape[1], x.shape[2], x.shape[3]
value = self.conv_v(x)
value = value.reshape([batch_size, c, value.shape[2] * value.shape[3]])
value = value.transpose([0, 2, 1]) # B * (H*W) * value_channels
key = x.reshape([batch_size, c, h * w]) # B * key_channels * (H*W)
query = x.reshape([batch_size, c, h * w])
query = query.transpose([0, 2, 1])
sim_map = paddle.matmul(query, key) # B * (H*W) * (H*W)
sim_map = (c**-.5) * sim_map # B * (H*W) * (H*W)
sim_map = nn.functional.softmax(sim_map, axis=-1) # B * (H*W) * (H*W)
context = paddle.matmul(sim_map, value)
context = context.transpose([0, 2, 1])
context = context.reshape([batch_size, c, *x.shape[2:]])
context = self.W(context)
return context
class NLFPN(nn.Layer):
""" Non-local feature parymid network"""
def __init__(self, in_dim, reduction=True):
super(NLFPN, self).__init__()
if reduction:
self.reduction = BasicConv(
in_ch=in_dim,
out_ch=in_dim // 4,
kernel_size=1,
norm=nn.BatchNorm2D(
in_dim // 4, momentum=bn_mom),
act=nn.ReLU())
self.re_reduction = BasicConv(
in_ch=in_dim // 4,
out_ch=in_dim,
kernel_size=1,
norm=nn.BatchNorm2D(
in_dim, momentum=bn_mom),
act=nn.ReLU())
in_dim = in_dim // 4
else:
self.reduction = None
self.re_reduction = None
self.conv_e1 = BasicConv(
in_dim,
in_dim,
kernel_size=3,
norm=nn.BatchNorm2D(
in_dim, momentum=bn_mom),
act=nn.ReLU())
self.conv_e2 = BasicConv(
in_dim,
in_dim * 2,
kernel_size=3,
norm=nn.BatchNorm2D(
in_dim * 2, momentum=bn_mom),
act=nn.ReLU())
self.conv_e3 = BasicConv(
in_dim * 2,
in_dim * 4,
kernel_size=3,
norm=nn.BatchNorm2D(
in_dim * 4, momentum=bn_mom),
act=nn.ReLU())
self.conv_d1 = BasicConv(
in_dim,
in_dim,
kernel_size=3,
norm=nn.BatchNorm2D(
in_dim, momentum=bn_mom),
act=nn.ReLU())
self.conv_d2 = BasicConv(
in_dim * 2,
in_dim,
kernel_size=3,
norm=nn.BatchNorm2D(
in_dim, momentum=bn_mom),
act=nn.ReLU())
self.conv_d3 = BasicConv(
in_dim * 4,
in_dim * 2,
kernel_size=3,
norm=nn.BatchNorm2D(
in_dim * 2, momentum=bn_mom),
act=nn.ReLU())
self.nl3 = NLBlock(in_dim * 2)
self.nl2 = NLBlock(in_dim)
self.nl1 = NLBlock(in_dim)
self.downsample_x2 = nn.MaxPool2D(stride=2, kernel_size=2)
self.upsample_x2 = nn.UpsamplingBilinear2D(scale_factor=2)
def forward(self, x):
if self.reduction is not None:
x = self.reduction(x)
e1 = self.conv_e1(x) # C,H,W
e2 = self.conv_e2(self.downsample_x2(e1)) # 2C,H/2,W/2
e3 = self.conv_e3(self.downsample_x2(e2)) # 4C,H/4,W/4
d3 = self.conv_d3(e3) # 2C,H/4,W/4
nl = self.nl3(d3)
d3 = self.upsample_x2(paddle.multiply(d3, nl)) ##2C,H/2,W/2
d2 = self.conv_d2(e2 + d3) # C,H/2,W/2
nl = self.nl2(d2)
d2 = self.upsample_x2(paddle.multiply(d2, nl)) # C,H,W
d1 = self.conv_d1(e1 + d2)
nl = self.nl1(d1)
d1 = paddle.multiply(d1, nl) # C,H,W
if self.re_reduction is not None:
d1 = self.re_reduction(d1)
return d1
class Cat(nn.Layer):
def __init__(self, in_chn_high, in_chn_low, out_chn, upsample=False):
super(Cat, self).__init__()
self.do_upsample = upsample
self.upsample = nn.Upsample(scale_factor=2, mode="nearest")
self.conv2d = BasicConv(
in_chn_high + in_chn_low,
out_chn,
kernel_size=1,
norm=nn.BatchNorm2D(
out_chn, momentum=bn_mom),
act=nn.ReLU())
def forward(self, x, y):
if self.do_upsample:
x = self.upsample(x)
x = paddle.concat((x, y), 1)
return self.conv2d(x)
class DoubleConv(nn.Layer):
def __init__(self, in_chn, out_chn, stride=1, dilation=1):
super(DoubleConv, self).__init__()
self.conv = nn.Sequential(
nn.Conv2D(
in_chn,
out_chn,
kernel_size=3,
stride=stride,
dilation=dilation,
padding=dilation),
nn.BatchNorm2D(
out_chn, momentum=bn_mom),
nn.ReLU(),
nn.Conv2D(
out_chn, out_chn, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2D(
out_chn, momentum=bn_mom),
nn.ReLU())
def forward(self, x):
x = self.conv(x)
return x
class SEModule(nn.Layer):
def __init__(self, channels, reduction_channels):
super(SEModule, self).__init__()
self.fc1 = nn.Conv2D(
channels,
reduction_channels,
kernel_size=1,
padding=0,
bias_attr=True)
self.ReLU = nn.ReLU()
self.fc2 = nn.Conv2D(
reduction_channels,
channels,
kernel_size=1,
padding=0,
bias_attr=True)
def forward(self, x):
x_se = x.reshape(
[x.shape[0], x.shape[1], x.shape[2] * x.shape[3]]).mean(-1).reshape(
[x.shape[0], x.shape[1], 1, 1])
x_se = self.fc1(x_se)
x_se = self.ReLU(x_se)
x_se = self.fc2(x_se)
return x * F.sigmoid(x_se)
class BasicBlock(nn.Layer):
expansion = 1
def __init__(self,
inplanes,
planes,
downsample=None,
use_se=False,
stride=1,
dilation=1):
super(BasicBlock, self).__init__()
first_planes = planes
outplanes = planes * self.expansion
self.conv1 = DoubleConv(inplanes, first_planes)
self.conv2 = DoubleConv(
first_planes, outplanes, stride=stride, dilation=dilation)
self.se = SEModule(outplanes, planes // 4) if use_se else None
self.downsample = MaxPool2x2() if downsample else None
self.ReLU = nn.ReLU()
def forward(self, x):
out = self.conv1(x)
residual = out
out = self.conv2(out)
if self.se is not None:
out = self.se(out)
if self.downsample is not None:
residual = self.downsample(residual)
out = out + residual
out = self.ReLU(out)
return out
class DenseCatAdd(nn.Layer):
def __init__(self, in_chn, out_chn):
super(DenseCatAdd, self).__init__()
self.conv1 = BasicConv(in_chn, in_chn, kernel_size=3, act=nn.ReLU())
self.conv2 = BasicConv(in_chn, in_chn, kernel_size=3, act=nn.ReLU())
self.conv3 = BasicConv(in_chn, in_chn, kernel_size=3, act=nn.ReLU())
self.conv_out = BasicConv(
in_chn,
out_chn,
kernel_size=1,
norm=nn.BatchNorm2D(
out_chn, momentum=bn_mom),
act=nn.ReLU())
def forward(self, x, y):
x1 = self.conv1(x)
x2 = self.conv2(x1)
x3 = self.conv3(x2 + x1)
y1 = self.conv1(y)
y2 = self.conv2(y1)
y3 = self.conv3(y2 + y1)
return self.conv_out(x1 + x2 + x3 + y1 + y2 + y3)
class DenseCatDiff(nn.Layer):
def __init__(self, in_chn, out_chn):
super(DenseCatDiff, self).__init__()
self.conv1 = BasicConv(in_chn, in_chn, kernel_size=3, act=nn.ReLU())
self.conv2 = BasicConv(in_chn, in_chn, kernel_size=3, act=nn.ReLU())
self.conv3 = BasicConv(in_chn, in_chn, kernel_size=3, act=nn.ReLU())
self.conv_out = BasicConv(
in_ch=in_chn,
out_ch=out_chn,
kernel_size=1,
norm=nn.BatchNorm2D(
out_chn, momentum=bn_mom),
act=nn.ReLU())
def forward(self, x, y):
x1 = self.conv1(x)
x2 = self.conv2(x1)
x3 = self.conv3(x2 + x1)
y1 = self.conv1(y)
y2 = self.conv2(y1)
y3 = self.conv3(y2 + y1)
out = self.conv_out(paddle.abs(x1 + x2 + x3 - y1 - y2 - y3))
return out
class DFModule(nn.Layer):
"""Dense connection-based feature fusion module"""
def __init__(self, dim_in, dim_out, reduction=True):
super(DFModule, self).__init__()
if reduction:
self.reduction = Conv1x1(
dim_in,
dim_in // 2,
norm=nn.BatchNorm2D(
dim_in // 2, momentum=bn_mom),
act=nn.ReLU())
dim_in = dim_in // 2
else:
self.reduction = None
self.cat1 = DenseCatAdd(dim_in, dim_out)
self.cat2 = DenseCatDiff(dim_in, dim_out)
self.conv1 = Conv3x3(
dim_out,
dim_out,
norm=nn.BatchNorm2D(
dim_out, momentum=bn_mom),
act=nn.ReLU())
def forward(self, x1, x2):
if self.reduction is not None:
x1 = self.reduction(x1)
x2 = self.reduction(x2)
x_add = self.cat1(x1, x2)
x_diff = self.cat2(x1, x2)
y = self.conv1(x_diff) + x_add
return y
class FCCDN(nn.Layer):
"""
The FCCDN implementation based on PaddlePaddle.
The original article refers to
Pan Chen, et al., "FCCDN: Feature Constraint Network for VHR Image Change Detection"
(https://arxiv.org/pdf/2105.10860.pdf).
Args:
in_channels (int): Number of input channels. Default: 3.
num_classes (int): Number of target classes. Default: 2.
os (int): Number of output stride. Default: 16.
use_se (bool): Whether to use SEModule. Default: True.
"""
def __init__(self, in_channels=3, num_classes=2, os=16, use_se=True):
super(FCCDN, self).__init__()
if os >= 16:
dilation_list = [1, 1, 1, 1]
stride_list = [2, 2, 2, 2]
pool_list = [True, True, True, True]
elif os == 8:
dilation_list = [2, 1, 1, 1]
stride_list = [1, 2, 2, 2]
pool_list = [False, True, True, True]
else:
dilation_list = [2, 2, 1, 1]
stride_list = [1, 1, 2, 2]
pool_list = [False, False, True, True]
se_list = [use_se, use_se, use_se, use_se]
channel_list = [256, 128, 64, 32]
# Encoder
self.block1 = BasicBlock(in_channels, channel_list[3], pool_list[3],
se_list[3], stride_list[3], dilation_list[3])
self.block2 = BasicBlock(channel_list[3], channel_list[2], pool_list[2],
se_list[2], stride_list[2], dilation_list[2])
self.block3 = BasicBlock(channel_list[2], channel_list[1], pool_list[1],
se_list[1], stride_list[1], dilation_list[1])
self.block4 = BasicBlock(channel_list[1], channel_list[0], pool_list[0],
se_list[0], stride_list[0], dilation_list[0])
# Center
self.center = NLFPN(channel_list[0], True)
# Decoder
self.decoder3 = Cat(channel_list[0],
channel_list[1],
channel_list[1],
upsample=pool_list[0])
self.decoder2 = Cat(channel_list[1],
channel_list[2],
channel_list[2],
upsample=pool_list[1])
self.decoder1 = Cat(channel_list[2],
channel_list[3],
channel_list[3],
upsample=pool_list[2])
self.df1 = DFModule(channel_list[3], channel_list[3], True)
self.df2 = DFModule(channel_list[2], channel_list[2], True)
self.df3 = DFModule(channel_list[1], channel_list[1], True)
self.df4 = DFModule(channel_list[0], channel_list[0], True)
self.catc3 = Cat(channel_list[0],
channel_list[1],
channel_list[1],
upsample=pool_list[0])
self.catc2 = Cat(channel_list[1],
channel_list[2],
channel_list[2],
upsample=pool_list[1])
self.catc1 = Cat(channel_list[2],
channel_list[3],
channel_list[3],
upsample=pool_list[2])
self.upsample_x2 = nn.Sequential(
nn.Conv2D(
channel_list[3], 8, kernel_size=3, stride=1, padding=1),
nn.BatchNorm2D(
8, momentum=bn_mom),
nn.ReLU(),
nn.UpsamplingBilinear2D(scale_factor=2))
self.conv_out = nn.Conv2D(
8, num_classes, kernel_size=3, stride=1, padding=1)
self.conv_out_class = nn.Conv2D(
channel_list[3], 1, kernel_size=1, stride=1, padding=0)
def forward(self, t1, t2):
e1_1 = self.block1(t1)
e2_1 = self.block2(e1_1)
e3_1 = self.block3(e2_1)
y1 = self.block4(e3_1)
e1_2 = self.block1(t2)
e2_2 = self.block2(e1_2)
e3_2 = self.block3(e2_2)
y2 = self.block4(e3_2)
y1 = self.center(y1)
y2 = self.center(y2)
c = self.df4(y1, y2)
y1 = self.decoder3(y1, e3_1)
y2 = self.decoder3(y2, e3_2)
c = self.catc3(c, self.df3(y1, y2))
y1 = self.decoder2(y1, e2_1)
y2 = self.decoder2(y2, e2_2)
c = self.catc2(c, self.df2(y1, y2))
y1 = self.decoder1(y1, e1_1)
y2 = self.decoder1(y2, e1_2)
c = self.catc1(c, self.df1(y1, y2))
y = self.conv_out(self.upsample_x2(c))
if self.training:
y1 = self.conv_out_class(y1)
y2 = self.conv_out_class(y2)
return [y, [y1, y2]]
else:
return [y]

@ -0,0 +1,15 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .fccdn_loss import fccdn_ssl_loss

@ -0,0 +1,170 @@
# Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import paddle
import paddle.nn as nn
import paddle.nn.functional as F
class DiceLoss(nn.Layer):
def __init__(self, batch=True):
super(DiceLoss, self).__init__()
self.batch = batch
def soft_dice_coeff(self, y_pred, y_true):
smooth = 0.00001
if self.batch:
i = paddle.sum(y_true)
j = paddle.sum(y_pred)
intersection = paddle.sum(y_true * y_pred)
else:
i = y_true.sum(1).sum(1).sum(1)
j = y_pred.sum(1).sum(1).sum(1)
intersection = (y_true * y_pred).sum(1).sum(1).sum(1)
score = (2. * intersection + smooth) / (i + j + smooth)
return score.mean()
def soft_dice_loss(self, y_pred, y_true):
loss = 1 - self.soft_dice_coeff(y_pred, y_true)
return loss
def forward(self, y_pred, y_true):
return self.soft_dice_loss(y_pred.astype(paddle.float32), y_true)
class MultiClassDiceLoss(nn.Layer):
def __init__(
self,
weight,
batch=True,
ignore_index=-1,
do_softmax=False,
**kwargs, ):
super(MultiClassDiceLoss, self).__init__()
self.ignore_index = ignore_index
self.weight = weight
self.do_softmax = do_softmax
self.binary_diceloss = DiceLoss(batch)
def forward(self, y_pred, y_true):
if self.do_softmax:
y_pred = paddle.nn.functional.softmax(y_pred, axis=1)
y_true = F.one_hot(y_true.long(), y_pred.shape[1]).permute(0, 3, 1, 2)
total_loss = 0.0
tmp_i = 0.0
for i in range(y_pred.shape[1]):
if i != self.ignore_index:
diceloss = self.binary_diceloss(y_pred[:, i, :, :],
y_true[:, i, :, :])
total_loss += paddle.multiply(diceloss, self.weight[i])
tmp_i += 1.0
return total_loss / tmp_i
class DiceBCELoss(nn.Layer):
"""Binary change detection task loss"""
def __init__(self):
super(DiceBCELoss, self).__init__()
self.bce_loss = nn.BCELoss()
self.binnary_dice = DiceLoss()
def forward(self, scores, labels, do_sigmoid=True):
if len(scores.shape) > 3:
scores = scores.squeeze(1)
if len(labels.shape) > 3:
labels = labels.squeeze(1)
if do_sigmoid:
scores = paddle.nn.functional.sigmoid(scores.clone())
diceloss = self.binnary_dice(scores, labels)
bceloss = self.bce_loss(scores, labels)
return diceloss + bceloss
class McDiceBCELoss(nn.Layer):
"""Multi-class change detection task loss"""
def __init__(self, weight, do_sigmoid=True):
super(McDiceBCELoss, self).__init__()
self.ce_loss = nn.CrossEntropyLoss(weight)
self.dice = MultiClassDiceLoss(weight, do_sigmoid)
def forward(self, scores, labels):
if len(scores.shape) < 4:
scores = scores.unsqueeze(1)
if len(labels.shape) < 4:
labels = labels.unsqueeze(1)
diceloss = self.dice(scores, labels)
bceloss = self.ce_loss(scores, labels)
return diceloss + bceloss
def fccdn_ssl_loss(logits_list, labels):
"""
Self-supervised learning loss for change detection.
The original article refers to
Pan Chen, et al., "FCCDN: Feature Constraint Network for VHR Image Change Detection"
(https://arxiv.org/pdf/2105.10860.pdf).
Args:
logits_list (list[paddle.Tensor]): Single-channel segmentation logit maps for each of the two temporal phases.
labels (paddle.Tensor): Binary change labels.
"""
# Create loss
criterion_ssl = DiceBCELoss()
# Get downsampled change map
h, w = logits_list[0].shape[-2], logits_list[0].shape[-1]
labels_downsample = F.interpolate(x=labels.unsqueeze(1), size=[h, w])
labels_type = str(labels_downsample.dtype)
assert "int" in labels_type or "bool" in labels_type,\
f"Expected dtype of labels to be int or bool, but got {labels_type}"
# Seg map
out1 = paddle.nn.functional.sigmoid(logits_list[0]).clone()
out2 = paddle.nn.functional.sigmoid(logits_list[1]).clone()
out3 = out1.clone()
out4 = out2.clone()
out1 = paddle.where(labels_downsample == 1, paddle.zeros_like(out1), out1)
out2 = paddle.where(labels_downsample == 1, paddle.zeros_like(out2), out2)
out3 = paddle.where(labels_downsample != 1, paddle.zeros_like(out3), out3)
out4 = paddle.where(labels_downsample != 1, paddle.zeros_like(out4), out4)
pred_seg_pre_tmp1 = paddle.where(out1 <= 0.5,
paddle.zeros_like(out1),
paddle.ones_like(out1))
pred_seg_post_tmp1 = paddle.where(out2 <= 0.5,
paddle.zeros_like(out2),
paddle.ones_like(out2))
pred_seg_pre_tmp2 = paddle.where(out3 <= 0.5,
paddle.zeros_like(out3),
paddle.ones_like(out3))
pred_seg_post_tmp2 = paddle.where(out4 <= 0.5,
paddle.zeros_like(out4),
paddle.ones_like(out4))
# Seg loss
labels_downsample = labels_downsample.astype(paddle.float32)
loss_aux = 0.2 * criterion_ssl(out1, pred_seg_post_tmp1, False)
loss_aux += 0.2 * criterion_ssl(out2, pred_seg_pre_tmp1, False)
loss_aux += 0.2 * criterion_ssl(
out3, labels_downsample - pred_seg_post_tmp2, False)
loss_aux += 0.2 * criterion_ssl(out4, labels_downsample - pred_seg_pre_tmp2,
False)
return loss_aux

@ -38,7 +38,7 @@ from .utils.infer_nets import InferCDNet
__all__ = [
"CDNet", "FCEarlyFusion", "FCSiamConc", "FCSiamDiff", "STANet", "BIT",
"SNUNet", "DSIFN", "DSAMNet", "ChangeStar", "ChangeFormer"
"SNUNet", "DSIFN", "DSAMNet", "ChangeStar", "ChangeFormer", "FCCDN"
]
@ -1062,7 +1062,7 @@ class ChangeStar(BaseChangeDetector):
if self.use_mixed_loss is False:
return {
# XXX: make sure the shallow copy works correctly here.
'types': [seglosses.CrossEntropyLoss()] * 4,
'types': [seg_losses.CrossEntropyLoss()] * 4,
'coef': [1.0] * 4
}
else:
@ -1089,3 +1089,31 @@ class ChangeFormer(BaseChangeDetector):
num_classes=num_classes,
use_mixed_loss=use_mixed_loss,
**params)
class FCCDN(BaseChangeDetector):
def __init__(self,
in_channels=3,
num_classes=2,
use_mixed_loss=False,
losses=None,
**params):
params.update({'in_channels': in_channels})
super(FCCDN, self).__init__(
model_name='FCCDN',
num_classes=num_classes,
use_mixed_loss=use_mixed_loss,
losses=losses,
**params)
def default_loss(self):
if self.use_mixed_loss is False:
return {
'types':
[seg_losses.CrossEntropyLoss(), cmcd.losses.fccdn_ssl_loss],
'coef': [1.0, 1.0]
}
else:
raise ValueError(
f"Currently `use_mixed_loss` must be set to False for {self.__class__}"
)

@ -827,7 +827,7 @@ class DeepLabV3P(BaseSegmenter):
if params.get('with_net', True):
with DisablePrint():
backbone = getattr(ppseg.models, backbone)(
input_channel=input_channel, output_stride=output_stride)
input_channel=in_channels, output_stride=output_stride)
else:
backbone = None
params.update({

@ -914,9 +914,9 @@ class Normalize(Transform):
std (list[float] | tuple[float], optional): Standard deviation of input
image(s). Defaults to [0.229, 0.224, 0.225].
min_val (list[float] | tuple[float], optional): Minimum value of input
image(s). Defaults to [0, 0, 0, ].
max_val (list[float] | tuple[float], optional): Max value of input image(s).
Defaults to [255., 255., 255.].
image(s). If None, use 0 for all channels. Defaults to None.
max_val (list[float] | tuple[float], optional): Maximum value of input
image(s). If None, use 255. for all channels. Defaults to None.
apply_to_tar (bool, optional): Whether to apply transformation to the target
image. Defaults to True.
"""

@ -0,0 +1,13 @@
# Basic configurations of FCCDN
_base_: ../_base_/airchange.yaml
save_dir: ./test_tipc/output/cd/fccdn/
model: !Node
type: FCCDN
learning_rate: 0.07
lr_decay_power: 0.6
log_interval_steps: 100
save_interval_epochs: 3

@ -0,0 +1,53 @@
===========================train_params===========================
model_name:cd:fccdn
python:python
gpu_list:0
use_gpu:null|null
--precision:null
--num_epochs:lite_train_lite_infer=15|lite_train_whole_infer=15|whole_train_whole_infer=15
--save_dir:adaptive
--train_batch_size:lite_train_lite_infer=4|lite_train_whole_infer=4|whole_train_whole_infer=4
--model_path:null
train_model_name:best_model
train_infer_file_list:./test_tipc/data/airchange/:./test_tipc/data/airchange/eval.txt
null:null
##
trainer:norm
norm_train:test_tipc/run_task.py train cd --config ./test_tipc/configs/cd/fccdn/fccdn.yaml
pact_train:null
fpgm_train:null
distill_train:null
null:null
null:null
##
===========================eval_params===========================
eval:null
null:null
##
===========================export_params===========================
--save_dir:adaptive
--model_dir:adaptive
--fixed_input_shape:[1,3,256,256]
norm_export:deploy/export/export_model.py
quant_export:null
fpgm_export:null
distill_export:null
export1:null
export2:null
===========================infer_params===========================
infer_model:null
infer_export:null
infer_quant:False
inference:test_tipc/infer.py
--device:cpu|gpu
--enable_mkldnn:True
--cpu_threads:6
--batch_size:1
--use_trt:False
--precision:fp32
--model_dir:null
--file_list:null:null
--save_log_path:null
--benchmark:True
--model_name:fccdn
null:null

@ -7,5 +7,5 @@ save_dir: ./test_tipc/output/seg/unet/
model: !Node
type: UNet
args:
input_channel: 10
in_channels: 10
num_classes: 5

@ -12,11 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import platform
from itertools import cycle
import paddlers
from rs_models.test_model import TestModel
from rs_models.test_model import TestModel, allow_oom
__all__ = [
'TestBITModel', 'TestCDNetModel', 'TestChangeStarModel', 'TestDSAMNetModel',
@ -200,6 +199,7 @@ class TestSNUNetModel(TestCDModel):
] # yapf: disable
@allow_oom
class TestSTANetModel(TestCDModel):
MODEL_CLASS = paddlers.rs_models.cd.STANet
@ -214,6 +214,7 @@ class TestSTANetModel(TestCDModel):
] # yapf: disable
@allow_oom
class TestChangeFormerModel(TestCDModel):
MODEL_CLASS = paddlers.rs_models.cd.ChangeFormer
@ -224,9 +225,3 @@ class TestChangeFormerModel(TestCDModel):
dict(**base_spec, decoder_softmax=True),
dict(**base_spec, embed_dim=56)
] # yapf: disable
# HACK:FIXME: We observe an OOM error when running TestSTANetModel.test_forward() on a Windows machine.
# Currently, we do not perform this test.
if platform.system() == 'Windows':
TestSTANetModel.test_forward = lambda self: None

@ -18,6 +18,7 @@ import paddle
import numpy as np
from paddle.static import InputSpec
from paddlers.utils import logging
from testing_utils import CommonTest
@ -37,20 +38,26 @@ class _TestModelNamespace:
for i, (
input, model, target
) in enumerate(zip(self.inputs, self.models, self.targets)):
with self.subTest(i=i):
try:
if isinstance(input, list):
output = model(*input)
else:
output = model(input)
self.check_output(output, target)
except:
logging.warning(f"Model built with spec{i} failed!")
raise
def test_to_static(self):
for i, (
input, model, target
) in enumerate(zip(self.inputs, self.models, self.targets)):
with self.subTest(i=i):
try:
static_model = paddle.jit.to_static(
model, input_spec=self.get_input_spec(model, input))
except:
logging.warning(f"Model built with spec{i} failed!")
raise
def check_output(self, output, target):
pass
@ -117,4 +124,29 @@ class _TestModelNamespace:
return input_spec
def allow_oom(cls):
def _deco(func):
def _wrapper(self, *args, **kwargs):
try:
func(self, *args, **kwargs)
except (SystemError, RuntimeError, OSError, MemoryError) as e:
# XXX: This may not cover all OOM cases.
msg = str(e)
if "Out of memory error" in msg \
or "(External) CUDNN error(4), CUDNN_STATUS_INTERNAL_ERROR." in msg \
or isinstance(e, MemoryError):
logging.warning("An OOM error has been ignored.")
else:
raise
return _wrapper
for key, value in inspect.getmembers(cls):
if key.startswith('test'):
value = _deco(value)
setattr(cls, key, value)
return cls
TestModel = _TestModelNamespace.TestModel

@ -0,0 +1,41 @@
#!/bin bash
rm -rf /usr/local/python2.7.15/bin/python
rm -rf /usr/local/python2.7.15/bin/pip
ln -s /usr/local/bin/python3.7 /usr/local/python2.7.15/bin/python
ln -s /usr/local/bin/pip3.7 /usr/local/python2.7.15/bin/pip
export PYTHONPATH=`pwd`
python -m pip install --upgrade pip --ignore-installed
# python -m pip install --upgrade numpy --ignore-installed
python -m pip uninstall paddlepaddle-gpu -y
if [[ ${branch} == 'develop' ]];then
echo "checkout develop !"
python -m pip install ${paddle_dev} --no-cache-dir
else
echo "checkout release !"
python -m pip install ${paddle_release} --no-cache-dir
fi
echo -e '*****************paddle_version*****'
python -c 'import paddle;print(paddle.version.commit)'
echo -e '*****************paddleseg_version****'
git rev-parse HEAD
pip install -r requirements.txt --ignore-installed
pip install -e .
pip install https://versaweb.dl.sourceforge.net/project/gdal-wheels-for-linux/GDAL-3.4.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl
git clone https://github.com/LDOUBLEV/AutoLog
cd AutoLog
pip install -r requirements.txt
python setup.py bdist_wheel
pip install ./dist/auto_log*.whl
cd ..
unset http_proxy https_proxy
set -e
cd tests/
bash run_fast_tests.sh

@ -0,0 +1,13 @@
#!/usr/bin/env bash
cd ..
for config in $(ls test_tipc/configs/*/*/train_infer_python.txt); do
bash test_tipc/prepare.sh ${config} lite_train_lite_infer
bash test_tipc/test_train_inference_python.sh ${config} lite_train_lite_infer
task="$(basename $(dirname $(dirname ${config})))"
model="$(basename $(dirname ${config}))"
if grep -q 'failed' "test_tipc/output/${task}/${model}/lite_train_lite_infer/results_python.log"; then
exit 1
fi
done

@ -0,0 +1,94 @@
#!/usr/bin/env python
# 变化检测模型FCCDN训练示例脚本
# 执行此脚本前,请确认已正确安装PaddleRS库
import paddlers as pdrs
from paddlers import transforms as T
# 数据集存放目录
DATA_DIR = './data/airchange/'
# 训练集`file_list`文件路径
TRAIN_FILE_LIST_PATH = './data/airchange/train.txt'
# 验证集`file_list`文件路径
EVAL_FILE_LIST_PATH = './data/airchange/eval.txt'
# 实验目录,保存输出的模型权重和结果
EXP_DIR = './output/fccdn/'
# 下载和解压AirChange数据集
pdrs.utils.download_and_decompress(
'https://paddlers.bj.bcebos.com/datasets/airchange.zip', path='./data/')
# 定义训练和验证时使用的数据变换(数据增强、预处理等)
# 使用Compose组合多种变换方式。Compose中包含的变换将按顺序串行执行
# API说明:https://github.com/PaddlePaddle/PaddleRS/blob/develop/docs/apis/transforms.md
train_transforms = T.Compose([
# 读取影像
T.DecodeImg(),
# 随机裁剪
T.RandomCrop(
# 裁剪区域将被缩放到256x256
crop_size=256,
# 裁剪区域的横纵比在0.5-2之间变动
aspect_ratio=[0.5, 2.0],
# 裁剪区域相对原始影像长宽比例在一定范围内变动,最小不低于原始长宽的1/5
scaling=[0.2, 1.0]),
# 以50%的概率实施随机水平翻转
T.RandomHorizontalFlip(prob=0.5),
# 将数据归一化到[-1,1]
T.Normalize(
mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
T.ArrangeChangeDetector('train')
])
eval_transforms = T.Compose([
T.DecodeImg(),
# 验证阶段与训练阶段的数据归一化方式必须相同
T.Normalize(
mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5]),
T.ReloadMask(),
T.ArrangeChangeDetector('eval')
])
# 分别构建训练和验证所用的数据集
train_dataset = pdrs.datasets.CDDataset(
data_dir=DATA_DIR,
file_list=TRAIN_FILE_LIST_PATH,
label_list=None,
transforms=train_transforms,
num_workers=0,
shuffle=True,
with_seg_labels=False,
binarize_labels=True)
eval_dataset = pdrs.datasets.CDDataset(
data_dir=DATA_DIR,
file_list=EVAL_FILE_LIST_PATH,
label_list=None,
transforms=eval_transforms,
num_workers=0,
shuffle=False,
with_seg_labels=False,
binarize_labels=True)
# 使用默认参数构建FCCDN模型
# 目前已支持的模型及模型输入参数请参考:
# https://github.com/PaddlePaddle/PaddleRS/blob/develop/paddlers/tasks/change_detector.py
model = pdrs.tasks.cd.FCCDN()
# 执行模型训练
model.train(
num_epochs=5,
train_dataset=train_dataset,
train_batch_size=4,
eval_dataset=eval_dataset,
save_interval_epochs=2,
# 每多少次迭代记录一次日志
log_interval_steps=50,
save_dir=EXP_DIR,
# 是否使用early stopping策略,当精度不再改善时提前终止训练
early_stop=False,
# 是否启用VisualDL日志功能
use_vdl=True,
# 指定从某个检查点继续训练
resume_checkpoint=None)
Loading…
Cancel
Save