Repository: PaddlePaddle/PaddleDetection

Issue:
[BUG]the nums of out channel is not equal to the declaration of the class in the darknet module
**the nums of out channel is not equal to the declaration of the class in the darknet module**

## LOCATION:
PaddleDetection/ppdet/modeling/backbones/darknet.py >>> class BasicBlock(nn.Layer)

## VERSION 
release 2.3

## 描述问题/Describe the bug
below is the definition codes about the BasicBlock
```python
class BasicBlock(nn.Layer):
    def __init__(self,
                 ch_in,
                 ch_out,
                 norm_type='bn',
                 norm_decay=0.,
                 freeze_norm=False,
                 data_format='NCHW'):
        super(BasicBlock, self).__init__()

        self.conv1 = ConvBNLayer(
            ch_in=ch_in,
            ch_out=ch_out,
            filter_size=1,
            stride=1,
            padding=0,
            norm_type=norm_type,
            norm_decay=norm_decay,
            freeze_norm=freeze_norm,
            data_format=data_format)
        self.conv2 = ConvBNLayer(
            ch_in=ch_out,
            ch_out=ch_out * 2,
            filter_size=3,
            stride=1,
            padding=1,
            norm_type=norm_type,
            norm_decay=norm_decay,
            freeze_norm=freeze_norm,
            data_format=data_format)

    def forward(self, inputs):
        conv1 = self.conv1(inputs)
        conv2 = self.conv2(conv1)
        out = paddle.add(x=inputs, y=conv2)
        return out
```
Suppose that i have a input variable with **shape [5,10,9,9]** and a **BasicBlock** instance named **block with the ch_in=10, ch_out=5**,
that mean i want have a **output with shape [5,5,9,9]**, but the actually the **output is a tensor with shape [5,10,9,9]**
Below is the codes that i have test in jupyternotebook
```python
import os
import paddle
os.chdir("/home/PaddleDetection")
from ppdet.modeling.backbones.darknet import BasicBlock

input = paddle.rand([5,10,9,9])
block = BasicBlock(10,5)
output = block(input)
print(f"input shape : {input.shape}\n")
print(f"layer: \n{block}\n")
print(f"output shape : {output.shape}")
```
and the print result is:
```python
input shape : [5, 10, 9, 9]

layer: 
BasicBlock(
  (conv1): ConvBNLayer(
    (conv): Conv2D(10, 5, kernel_size=[1, 1], data_format=NCHW)
    (batch_norm): BatchNorm2D(num_features=5, momentum=0.9, epsilon=1e-05)
  )
  (conv2): ConvBNLayer(
    (conv): Conv2D(5, 10, kernel_size=[3, 3], padding=1, data_format=NCHW)
    (batch_norm): BatchNorm2D(num_features=10, momentum=0.9, epsilon=1e-05)
  )
)

output shape : [5, 10, 9, 9]
```
and if I  input variable with shape [5,10,9,9] to the block with the ch_in=10, ch_out=20 or the block with the ch_in=10, ch_out=20, there will encounter a ValuError:
```python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_17986/1117496369.py in <module>
      6 input = paddle.rand([5,10,9,9])
      7 block = BasicBlock(10,10)
----> 8 output = block(input)
      9 output.shape
     10 print(f"input shape : {input.shape}\n")

/usr/local/lib/python3.7/dist-packages/paddle/fluid/dygraph/layers.py in __call__(self, *inputs, **kwargs)
    912                 self._built = True
    913 
--> 914             outputs = self.forward(*inputs, **kwargs)
    915 
    916             for forward_post_hook in self._forward_post_hooks.values():

/home/PaddleDetection/ppdet/modeling/backbones/darknet.py in forward(self, inputs)
    174         conv1 = self.conv1(inputs)
    175         conv2 = self.conv2(conv1)
--> 176         out = paddle.add(x=inputs, y=conv2)
    177         return out
    178 

/usr/local/lib/python3.7/dist-packages/paddle/tensor/math.py in add(x, y, name)
    238 
    239     if in_dygraph_mode():
--> 240         return _C_ops.elementwise_add(x, y)
    241 
    242     return _elementwise_op(LayerHelper('elementwise_add', **locals()))

ValueError: (InvalidArgument) Broadcast dimension mismatch. Operands could not be broadcast together with the shape of X = [5, 10, 9, 9] and the shape of Y = [5, 20, 9, 9]. Received [10] in X is not equal to [20] in Y at i:1.
  [Hint: Expected x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1 == true, but received x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1:0 != true:1.] (at /paddle/paddle/fluid/operators/elementwise/elementwise_op_function.h:240)
  [operator < elementwise_add > error]
```
and

```python
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_17986/3869671924.py in <module>
      6 input = paddle.rand([5,10,9,9])
      7 block = BasicBlock(10,20)
----> 8 output = block(input)
      9 print(f"input shape : {input.shape}\n")
     10 print(f"layer: \n{block}\n")

/usr/local/lib/python3.7/dist-packages/paddle/fluid/dygraph/layers.py in __call__(self, *inputs, **kwargs)
    912                 self._built = True
    913 
--> 914             outputs = self.forward(*inputs, **kwargs)
    915 
    916             for forward_post_hook in self._forward_post_hooks.values():

/home/PaddleDetection/ppdet/modeling/backbones/darknet.py in forward(self, inputs)
    174         conv1 = self.conv1(inputs)
    175         conv2 = self.conv2(conv1)
--> 176         out = paddle.add(x=inputs, y=conv2)
    177         return out
    178 

/usr/local/lib/python3.7/dist-packages/paddle/tensor/math.py in add(x, y, name)
    238 
    239     if in_dygraph_mode():
--> 240         return _C_ops.elementwise_add(x, y)
    241 
    242     return _elementwise_op(LayerHelper('elementwise_add', **locals()))

ValueError: (InvalidArgument) Broadcast dimension mismatch. Operands could not be broadcast together with the shape of X = [5, 10, 9, 9] and the shape of Y = [5, 40, 9, 9]. Received [10] in X is not equal to [40] in Y at i:1.
  [Hint: Expected x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1 == true, but received x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1:0 != true:1.] (at /paddle/paddle/fluid/operators/elementwise/elementwise_op_function.h:240)
  [operator < elementwise_add > error]
```
I know that is a tiny issue, so would you please fix that in the feature, pleaseure



Discussion:
`ch_out` config the output channel of `conv1` currently， output channel number of `conv2` is twice as `conv1`, if we set `ch_out` to config the output channel of `conv2`， which means output channel of `conv1` will be `ch_out / 2`， this will introduce a restriction that `chi_out` should be a even number, which is not user friendly
@heavengate, thanks your explanations about the design of the block from the user view, but actually these is still a implicit restriction that user just allowed to init a block  with `ch_out` is equal to the half of `ch_in`, which is result from the implementations of `conv2`  and  `forward` function:
You can see that `conv2` implement a process that double the channels,
```python
        self.conv2 = ConvBNLayer(
            ch_in=ch_out,
            ch_out=ch_out * 2,
            filter_size=3,
            stride=1,
            padding=1,
            norm_type=norm_type,
            norm_decay=norm_decay,
            freeze_norm=freeze_norm,
            data_format=data_format)
```
and the `output` of `conv2` need have same shape with the `input` of the `conv1` due to the use of the `paddle.add` operator not the `paddle.concat` operator.
```python
    def forward(self, inputs):
        conv1 = self.conv1(inputs)
        conv2 = self.conv2(conv1)
        out = paddle.add(x=inputs, y=conv2) # inpus and conv2 should be the same shape
        return out
```
so if i give the `ch_in = 10, ch_out = 6` , there will be a ValueError from the `add` operator
```python
import os
import paddle
os.chdir("/home/PaddleDetection")
from ppdet.modeling.backbones.darknet import BasicBlock

input = paddle.rand([5,10,9,9])
block = BasicBlock(10,6)
output = block(input)
print(f"input shape : {input.shape}\n")
print(f"layer: \n{block}\n")
print(f"output shape : {output.shape}")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/tmp/ipykernel_17986/3074278531.py in <module>
      6 input = paddle.rand([5,10,9,9])
      7 block = BasicBlock(10,6)
----> 8 output = block(input)
      9 print(f"input shape : {input.shape}\n")
     10 print(f"layer: \n{block}\n")

/usr/local/lib/python3.7/dist-packages/paddle/fluid/dygraph/layers.py in __call__(self, *inputs, **kwargs)
    912                 self._built = True
    913 
--> 914             outputs = self.forward(*inputs, **kwargs)
    915 
    916             for forward_post_hook in self._forward_post_hooks.values():

/home/PaddleDetection/ppdet/modeling/backbones/darknet.py in forward(self, inputs)
    174         conv1 = self.conv1(inputs)
    175         conv2 = self.conv2(conv1)
--> 176         out = paddle.add(x=inputs, y=conv2)
    177         return out
    178 

/usr/local/lib/python3.7/dist-packages/paddle/tensor/math.py in add(x, y, name)
    238 
    239     if in_dygraph_mode():
--> 240         return _C_ops.elementwise_add(x, y)
    241 
    242     return _elementwise_op(LayerHelper('elementwise_add', **locals()))

ValueError: (InvalidArgument) Broadcast dimension mismatch. Operands could not be broadcast together with the shape of X = [5, 10, 9, 9] and the shape of Y = [5, 12, 9, 9]. Received [10] in X is not equal to [12] in Y at i:1.
  [Hint: Expected x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1 == true, but received x_dims_array[i] == y_dims_array[i] || x_dims_array[i] <= 1 || y_dims_array[i] <= 1:0 != true:1.] (at /paddle/paddle/fluid/operators/elementwise/elementwise_op_function.h:240)
  [operator < elementwise_add > error]
```
Even thought the design concept of the **ResNet** block have demand the same shape of `x` and `f(f(x))` from the y = x+f(f(x)), i think is better to set the `ch_in` and `ch_out` to the **same even value**, and modify the channel variables of the `conv1` and `conv2` , which may be more friendly to user.*_^


Resolving patch:
diff --git a/ppdet/modeling/backbones/darknet.py b/ppdet/modeling/backbones/darknet.py
--- a/ppdet/modeling/backbones/darknet.py
+++ b/ppdet/modeling/backbones/darknet.py
@@ -149,9 +149,14 @@ def __init__(self,
 
         super(BasicBlock, self).__init__()
 
+        assert ch_in == ch_out and (ch_in % 2) == 0, \
+            f"ch_in and ch_out should be the same even int, but the input \'ch_in is {ch_in}, \'ch_out is {ch_out}"
+        # example:
+        # --------------{conv1} --> {conv2}
+        # channel route: 10-->5 --> 5-->10
         self.conv1 = ConvBNLayer(
             ch_in=ch_in,
-            ch_out=ch_out,
+            ch_out=int(ch_out/2),
             filter_size=1,
             stride=1,
             padding=0,
@@ -160,8 +165,8 @@ def __init__(self,
             freeze_norm=freeze_norm,
             data_format=data_format)
         self.conv2 = ConvBNLayer(
-            ch_in=ch_out,
-            ch_out=ch_out * 2,
+            ch_in=int(ch_out/2),
+            ch_out=ch_out ,
             filter_size=3,
             stride=1,
             padding=1,
@@ -215,7 +220,7 @@ def __init__(self,
             res_out = self.add_sublayer(
                 block_name,
                 BasicBlock(
-                    ch_out * 2,
+                    ch_out,
                     ch_out,
                     norm_type=norm_type,
                     norm_decay=norm_decay,
@@ -296,7 +301,7 @@ def __init__(self,
                 name,
                 Blocks(
                     int(ch_in[i]),
-                    32 * (2**i),
+                    int(ch_in[i]),
                     stage,
                     norm_type=norm_type,
                     norm_decay=norm_decay,
@@ -305,14 +310,14 @@ def __init__(self,
                     name=name))
             self.darknet_conv_block_list.append(conv_block)
             if i in return_idx:
-                self._out_channels.append(64 * (2**i))
+                self._out_channels.append(int(ch_in[i]))
         for i in range(num_stages - 1):
             down_name = 'stage.{}.downsample'.format(i)
             downsample = self.add_sublayer(
                 down_name,
                 DownSample(
-                    ch_in=32 * (2**(i + 1)),
-                    ch_out=32 * (2**(i + 2)),
+                    ch_in=int(ch_in[i]),
+                    ch_out=int(ch_in[i+1]),
                     norm_type=norm_type,
                     norm_decay=norm_decay,
                     freeze_norm=freeze_norm,


Test patch: