theseus_layer.md 13.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
# TheseusLayer 使用说明

基于 TheseusLayer 构建的网络模型,支持网络截断、返回网络中间层输出和修改网络中间层的功能。

---

## 目录

- [1. 前言](#1)
- [2. 网络层描述符说明](#2)
- [3. 功能介绍](#3)
    - [3.1 网络截断(stop_after)](#3.1)
    - [3.2 返回网络中间层输出(update_res)](#3.2)
    - [3.3 修改网络中间层(upgrade_sublayer)](#3.3)

<a name="1"></a>

## 1. 前言

`TheseusLayer` 是继承了 `nn.Layer` 的子类,使用 `TheseusLayer` 作为父类构建的网络模型,可以通过 `TheseusLayer``stop_after()``update_res()``upgrade_sublayer()` 实现网络截断、返回中间层输出以及修改网络中间层的功能。目前 PaddleClas 中 `ppcls.arch.backbone.legendary_models` 下的所有模型均支持上述操作。

如需基于 `TheseusLayer` 构建新的网络结构,只需继承 `TheseusLayer` 即可:

```python
from ppcls.arch.backbone.base.theseus_layer import TheseusLayer

class net(TheseusLayer):
    def __init__():
        super().__init__()

    def forward(x):
        pass
```

<a name="2"></a>

## 2. 网络层描述符说明

G
gaotingquan 已提交
39
使用 `TheseusLayer` 提供的方法对模型进行操作/修改时,需要通过参数指定网络中间层,因此 `TheseusLayer` 规定了用于描述网络中间层的网络层描述符。
40

G
gaotingquan 已提交
41 42 43 44 45
网络层描述符的使用需要符合以下规则:
* 为 Python 字符串(str)类型;
* 使用网络层对象的变量名指定该网络层;
*`.` 作为网络层级的分隔符;
* 对于 `nn.Sequential` 类型或是 `nn.LayerList` 类型的层,使用 `["index"]` 指定其子层。
46

47
`MobileNetV1` 网络为例,其模型结构定义在 [MobileNetV1](../../../../ppcls/arch/backbone/legendary_models/mobilenet_v1.py),为方便说明,可参考下方网络结构及不同网络层所对应的网络层描述符。可以清晰看出,对于 `MobileNetV1` 网络的任一子层,均可按层级结构逐层指定,不同层级结构间使用 `.` 进行分隔即可。
G
gaotingquan 已提交
48 49 50 51 52 53 54 55 56

```shell
# 网络层对象的变量名(该对象所属类)....................(该网络层对应的网络层描述符)

MobileNetV1
├── conv (ConvBNLayer)............................("conv")
│   ├── conv (nn.Conv2D)..........................("conv.conv")
│   ├── bn (nn.BatchNorm).........................("conv.bn")
│   └── relu (nn.ReLU)............................("conv.relu")
57

G
gaotingquan 已提交
58 59 60 61 62 63 64 65 66 67
├── blocks (nn.Sequential)........................("blocks")
│   ├── blocks0 (DepthwiseSeparable)..............("blocks[0]")
│   │   ├── depthwise_conv (ConvBNLayer)..........("blocks[0].depthwise_conv")
│   │   │   ├── conv (nn.Conv2D)..................("blocks[0].depthwise_conv.conv")
│   │   │   ├── bn (nn.BatchNorm).................("blocks[0].depthwise_conv.bn")
│   │   │   └── relu (nn.ReLU)....................("blocks[0].depthwise_conv.relu")
│   │   └── pointwise_conv (ConvBNLayer)..........("blocks[0].pointwise_conv")
│   │       ├── conv (nn.Conv2D)..................("blocks[0].pointwise_conv.conv")
│   │       ├── bn (nn.BatchNorm).................("blocks[0].pointwise_conv.bn")
│   │       └── relu (nn.ReLU)....................("blocks[0].pointwise_conv.relu")
68 69 70
│   .
│   .
│   .
G
gaotingquan 已提交
71
│   └── blocks12 (DepthwiseSeparable).............("blocks[12]")
G
gaotingquan 已提交
72 73 74 75 76 77 78 79
│       ├── depthwise_conv (ConvBNLayer)..........("blocks[12].depthwise_conv")
│       │   ├── conv (nn.Conv2D)..................("blocks[12].depthwise_conv.conv")
│       │   ├── bn (nn.BatchNorm).................("blocks[12].depthwise_conv.bn")
│       │   └── relu (nn.ReLU)....................("blocks[12].depthwise_conv.relu")
│       └── pointwise_conv (ConvBNLayer)..........("blocks[12].pointwise_conv")
│           ├── conv (nn.Conv2D)..................("blocks[12].pointwise_conv.conv")
│           ├── bn (nn.BatchNorm).................("blocks[12].pointwise_conv.bn")
│           └── relu (nn.ReLU)....................("blocks[12].pointwise_conv.relu")
80

G
gaotingquan 已提交
81
├── avg_pool (nn.AdaptiveAvgPool2D)...............("avg_pool")
82

G
gaotingquan 已提交
83
├── flatten (nn.Flatten)..........................("flatten")
84

G
gaotingquan 已提交
85
└── fc (nn.Linear)................................("fc")
86 87
```

G
gaotingquan 已提交
88
因此,对于 `MobileNetV1` 网络:
89
* 网络层描述符 `flatten`,其指定了网络 `MobileNetV1``flatten` 这一层。
G
gaotingquan 已提交
90 91
* 网络层描述符 `blocks[5]`,其指定了网络 `MobileNetV1``blocks` 层中的第 `6``DepthwiseSeparable` 对象这一层;
* 网络层描述符 `blocks[0].depthwise_conv.conv`,其指定了网络 `MobileNetV1``blocks` 层中的第 `1``DepthwiseSeparable` 对象中的 `depthwise_conv` 中的 `conv` 这一层;
92 93 94 95 96

<a name="3"></a>

## 3. 方法说明

G
gaotingquan 已提交
97
PaddleClas 提供的 backbone 网络均基于图像分类数据集训练得到,因此网络的尾部带有用于分类的全连接层,而在特定任务场景下,需要去掉分类的全连接层。在部分下游任务中,例如目标检测场景,需要获取到网络中间层的输出结果,也可能需要对网络的中间层进行修改,因此 `TheseusLayer` 提供了 3 个接口函数用于实现不同的修改功能。下面基于 PaddleClas whl 进行说明,首先需要安装 PaddleClas:`pip install paddleclas`
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114

<a name="3.1"></a>

### 3.1 网络截断(stop_after)

```python
def stop_after(self, stop_layer_name: str) -> bool:
    """stop forward and backward after 'stop_layer_name'.

    Args:
        stop_layer_name (str): The name of layer that stop forward and backward after this layer.

    Returns:
        bool: 'True' if successful, 'False' otherwise.
    """
```

G
gaotingquan 已提交
115
该方法可通过参数 `stop_layer_name` 指定网络中的特定子层,并停止该层之后的所有层的前后向传输,在逻辑上,该层之后不再有其他网络层。
116

G
gaotingquan 已提交
117 118 119 120
* 参数:
    * `stop_layer_name``str` 类型的对象,用于指定网络子层的网络层描述符。关于网络层描述符的具体规则,请查看[网络层描述符说明](#2)
* 返回值:
    * 当该方法成功执行时,其返回值为 `True`,否则为 `False`
121

G
gaotingquan 已提交
122
`MobileNetV1` 网络为例,参数 `stop_layer_name``"blocks[0].depthwise_conv.conv"`,具体效果可以参考下方代码案例进行尝试。
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156

```python
import paddleclas

net = paddleclas.MobileNetV1()
print("========== the origin mobilenetv1 net arch ==========")
print(net)

res = net.stop_after(stop_layer_name="blocks[0].depthwise_conv.conv")
print("The result returned by stop_after(): ", res)
# The result returned by stop_after(): True

print("\n\n========== the truncated mobilenetv1 net arch ==========")
print(net)
```

<a name="3.2"></a>

### 3.2 返回网络中间层输出(update_res)

```python
def update_res(
        self,
        return_patterns: Union[str, List[str]]) -> Dict[str, nn.Layer]:
    """update the result(s) to be returned.

    Args:
        return_patterns (Union[str, List[str]]): The name of layer to return output.

    Returns:
        Dict[str, nn.Layer]: The pattern(str) and corresponding layer(nn.Layer) that have been set successfully.
    """
```

G
gaotingquan 已提交
157 158 159 160 161 162
该方法可通过参数 `return_patterns` 指定一层(str 对象)或多层(list 对象)网络的中间子层,并在网络前向时,将指定层的输出结果与网络的最终结果一同返回。

* 参数:
    * `return_patterns`:作为网络层描述符的 `str` 对象,或是 `str` 对象所组成的 `list` 对象,其元素为用于指定网络子层的网络层描述符。关于网络层描述符的具体规则,请查看[网络层描述符说明](#2)
* 返回值:
    * 该方法的返回值为 `list` 对象,元素为设置成功的子层的网络层描述符。
163

G
gaotingquan 已提交
164
`MobileNetV1` 网络为例,当 `return_patterns``["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"]`,在网络前向推理时,网络的输出结果将包含以上 4 层的输出和网络最终的输出,具体效果可以参考下方代码案例进行尝试。
165 166 167 168

```python
import numpy as np
import paddle
G
gaotingquan 已提交
169

170 171 172 173 174 175 176 177 178 179 180 181 182
import paddleclas

np_input = np.zeros((1, 3, 224, 224))
pd_input  = paddle.to_tensor(np_input, dtype="float32")

net = paddleclas.MobileNetV1(pretrained=True)

output = net(pd_input)
print("The output's type of origin net: ", type(output))
# The output's type of origin net: <class 'paddle.Tensor'>

res = net.update_res(return_patterns=["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"])
print("The result returned by update_res(): ", res)
G
gaotingquan 已提交
183
# The result returned by update_res():  ['blocks[0]', 'blocks[2]', 'blocks[4]', 'blocks[10]']
184 185 186

output = net(pd_input)
print("The output's keys of processed net: ", output.keys())
G
gaotingquan 已提交
187 188
# The output's keys of net:  dict_keys(['logits', 'blocks[0]', 'blocks[2]', 'blocks[4]', 'blocks[10]'])
# 网络前向输出 output 为 dict 类型对象,其中,output["logits"] 为网络最终输出,output["blocks[0]"] 等为网络中间层输出结果
189 190 191 192 193 194 195 196
```

除了通过调用方法 `update_res()` 的方式之外,也同样可以在实例化网络对象时,通过指定参数 `return_patterns` 实现相同效果:

```python
net = paddleclas.MobileNetV1(pretrained=True, return_patterns=["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"])
```

G
gaotingquan 已提交
197 198 199 200 201 202 203 204 205 206
并且在实例化网络对象时,还可以通过参数 `return_stages` 指定网络不同 `stage` 的输出,如下方代码所示:

```python
# 当 `return_stages` 为 `True`,会将网络所有 stage 的前向输出一并返回,如下所示:
net = paddleclas.MobileNetV1(pretrained=True, return_stages=True)

# 当 `return_stages` 为 list 对象,可以指定需要返回输出结果的 stage 的序号,如下所示:
net = paddleclas.MobileNetV1(pretrained=True, return_stages=[0, 1, 2, 3])
```

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
<a name="3.3"></a>

### 3.3 修改网络中间层(upgrade_sublayer)

```python
def upgrade_sublayer(self,
                        layer_name_pattern: Union[str, List[str]],
                        handle_func: Callable[[nn.Layer, str], nn.Layer]
                        ) -> Dict[str, nn.Layer]:
    """use 'handle_func' to modify the sub-layer(s) specified by 'layer_name_pattern'.

    Args:
        layer_name_pattern (Union[str, List[str]]): The name of layer to be modified by 'handle_func'.
        handle_func (Callable[[nn.Layer, str], nn.Layer]): The function to modify target layer specified by 'layer_name_pattern'. The formal params are the layer(nn.Layer) and pattern(str) that is (a member of) layer_name_pattern (when layer_name_pattern is List type). And the return is the layer processed.

    Returns:
        Dict[str, nn.Layer]: The key is the pattern and corresponding value is the result returned by 'handle_func()'.
    """
```

G
gaotingquan 已提交
227 228 229 230 231 232 233
该方法可通过参数 `layer_name_pattern` 指定一层(str 对象)或多层(list 对象)网络中间子层,并使用参数 `handle_func` 所指定的函数对指定的子层进行修改。

* 参数:
    * `layer_name_pattern`:作为网络层描述符的 `str` 对象,或是 `str` 对象所组成的 `list` 对象,其元素为用于指定网络子层的网络层描述符。关于网络层描述符的具体规则,请查看[网络层描述符说明](#2)
    * `handle_func`:有 2 个形参的可调用对象,第 1 个形参为 `nn.Layer` 类型,第 2 个形参为 `str` 类型,该可调用对象返回值必须为 `nn.Layer` 类型对象或是有 `forward` 方法的对象。
* 返回值:
    * 该方法的返回值为 `list` 对象,元素为修改成功的网络子层的网络层描述符。
234

G
gaotingquan 已提交
235
`upgrade_sublayer` 方法会根据 `layer_name_pattern` 查找对应的网络子层,并将查找到的子层和其 `pattern` 传入可调用对象 `handle_func`,并使用 `handle_func` 的返回值替换该层。
236 237 238 239 240

`MobileNetV1` 网络为例,将网络最后的 2 个 block 中的深度可分离卷积(depthwise_conv)改为 `5*5` 大小的卷积核,同时将 padding 改为 `2`,如下方代码所示:

```python
from paddle import nn
G
gaotingquan 已提交
241

242 243
import paddleclas

G
gaotingquan 已提交
244 245 246
# 该函数必须有两个形参
# 第一个形参用于接受指定的网络中间子层
# 第二个形参用于接受指定网络子层的网络层描述符
247 248
def rep_func(layer: nn.Layer, pattern: str):
    new_layer = nn.Conv2D(
G
gaotingquan 已提交
249 250 251 252
        # layer 为 blocks[11].depthwise_conv.conv 或
        # blocks[12].depthwise_conv.conv 所对应的网络中间子层
        # 因此,新的网络层(new_layer)与被替换掉的网络层具有相同的
        # in_channels 属性和 out_channels 属性
253 254 255 256 257
        in_channels=layer._in_channels,
        out_channels=layer._out_channels,
        kernel_size=5,
        padding=2
    )
G
gaotingquan 已提交
258 259
    # 该函数返回值为新的网络层
    # upgrade_sublayer() 方法将使用该返回值替换对应的网络中间子层
260 261 262 263 264 265 266 267
    return new_layer

net = paddleclas.MobileNetV1(pretrained=True)
print("========== the origin mobilenetv1 net arch ==========")
print(net)

res = net.upgrade_sublayer(layer_name_pattern=["blocks[11].depthwise_conv.conv", "blocks[12].depthwise_conv.conv"], handle_func=rep_func)
print("The result returned by upgrade_sublayer() is", res)
G
gaotingquan 已提交
268
# The result returned by upgrade_sublayer() is ['blocks[11].depthwise_conv.conv', 'blocks[12].depthwise_conv.conv']
269 270 271 272

print("\n\n========== the upgraded mobilenetv1 net arch ==========")
print(net)
```