|
| 1 | +# TheseusLayer 使用说明 |
| 2 | + |
| 3 | +基于 TheseusLayer 构建的网络模型,支持网络截断、返回网络中间层输出和修改网络中间层的功能。 |
| 4 | + |
| 5 | +--- |
| 6 | + |
| 7 | +## 目录 |
| 8 | + |
| 9 | +- [1. 前言](#1) |
| 10 | +- [2. 网络层描述符说明](#2) |
| 11 | +- [3. 功能介绍](#3) |
| 12 | + - [3.1 网络截断(stop_after)](#3.1) |
| 13 | + - [3.2 返回网络中间层输出(update_res)](#3.2) |
| 14 | + - [3.3 修改网络中间层(upgrade_sublayer)](#3.3) |
| 15 | + |
| 16 | +<a name="1"></a> |
| 17 | + |
| 18 | +## 1. 前言 |
| 19 | + |
| 20 | +`TheseusLayer` 是继承了 `nn.Layer` 的子类,使用 `TheseusLayer` 作为父类构建的网络模型,可以通过 `TheseusLayer` 的 `stop_after()`、`update_res()` 和 `upgrade_sublayer()` 实现网络截断、返回中间层输出以及修改网络中间层的功能。目前 PaddleClas 中 `ppcls.arch.backbone.legendary_models` 下的所有模型均支持上述操作。 |
| 21 | + |
| 22 | +如需基于 `TheseusLayer` 构建新的网络结构,只需继承 `TheseusLayer` 即可: |
| 23 | + |
| 24 | +```python |
| 25 | +from ppcls.arch.backbone.base.theseus_layer import TheseusLayer |
| 26 | + |
| 27 | +class net(TheseusLayer): |
| 28 | + def __init__(): |
| 29 | + super().__init__() |
| 30 | + |
| 31 | + def forward(x): |
| 32 | + pass |
| 33 | +``` |
| 34 | + |
| 35 | +<a name="2"></a> |
| 36 | + |
| 37 | +## 2. 网络层描述符说明 |
| 38 | + |
| 39 | +使用 `TheseusLayer` 提供的方法对模型进行操作/修改时,需要通过参数指定网络中间层,因此 `TheseusLayer` 规定了用于描述网络中间层的网络层描述符。网络层描述符为 Python 字符串(str)类型,使用网络层对象的变量名指定子层,以 `.` 作为网络层级的分隔符,对于 `nn.Sequential` 类型的层,使用 `["index"]` 指定其子层。 |
| 40 | + |
| 41 | +以 `MobileNetV1` 网络为例,其模型结构定义在 [MobileNetV1](../../../ppcls/arch/backbone/legendary_models/mobilenet_v1.py)。网络 `MobileNetV1` 由 `conv`、`blocks`、`avg_pool`、`flatten` 和 `fc` 4 个子层组成,其中 `blocks` 为 `nn.Sequential` 类型对象,包括 13 层 `DepthwiseSeparable` 类型的子层,`DepthwiseSeparable` 又由 `depthwise_conv` 和 `pointwise_conv` 2 个子层组成,`depthwise_conv` 和 `pointwise_conv` 均为 `ConvBNLayer` 类型对象,由 `conv`、`bn` 和 `relu` 3 层子层组成,如下图所示: |
| 42 | + |
| 43 | +``` |
| 44 | +MobileNetV1 (TheseusLayer) |
| 45 | +├── conv (ConvBNLayer) |
| 46 | +│ ├── conv (nn.Conv2D) |
| 47 | +│ ├── bn (nn.BatchNorm) |
| 48 | +│ └── relu (nn.ReLU) |
| 49 | +│ |
| 50 | +├── blocks (nn.Sequential) |
| 51 | +│ ├── blocks0 (DepthwiseSeparable) |
| 52 | +│ │ ├── depthwise_conv (ConvBNLayer) |
| 53 | +│ │ │ ├── conv (nn.Conv2D) |
| 54 | +│ │ │ ├── bn (nn.BatchNorm) |
| 55 | +│ │ │ └── relu (nn.ReLU) |
| 56 | +│ │ └── pointwise_conv (ConvBNLayer) |
| 57 | +│ │ ├── conv (nn.Conv2D) |
| 58 | +│ │ ├── bn (nn.BatchNorm) |
| 59 | +│ │ └── relu (nn.ReLU) |
| 60 | +│ . |
| 61 | +│ . |
| 62 | +│ . |
| 63 | +│ └── blocks12 (DepthwiseSeparable) |
| 64 | +│ ├── depthwise_conv (ConvBNLayer) |
| 65 | +│ │ ├── conv (nn.Conv2D) |
| 66 | +│ │ ├── bn (nn.BatchNorm) |
| 67 | +│ │ └── relu (nn.ReLU) |
| 68 | +│ └── pointwise_conv (ConvBNLayer) |
| 69 | +│ ├── conv (nn.Conv2D) |
| 70 | +│ ├── bn (nn.BatchNorm) |
| 71 | +│ └── relu (nn.ReLU) |
| 72 | +│ |
| 73 | +├── avg_pool (nn.AdaptiveAvgPool2D) |
| 74 | +│ |
| 75 | +├── flatten (nn.Flatten) |
| 76 | +│ |
| 77 | +└── fc (nn.Linear) |
| 78 | +``` |
| 79 | + |
| 80 | +因此,对于 `MobileNetV1` 而言: |
| 81 | +* 网络层描述符 `blocks[0].depthwise_conv.conv`,其指定了网络 `MobileNetV1` 的 `blocks` 层中的第 `1` 个 `DepthwiseSeparable` 对象中的 `depthwise_conv` 中的 `conv` 这一层; |
| 82 | +* 网络层描述符 `blocks[5]`,其指定了网络 `MobileNetV1` 的 `blocks` 层中的第 `6` 个 `DepthwiseSeparable` 对象这一层; |
| 83 | +* 网络层描述符 `flatten`,其指定了网络 `MobileNetV1` 的 `flatten` 这一层。 |
| 84 | + |
| 85 | +<a name="3"></a> |
| 86 | + |
| 87 | +## 3. 方法说明 |
| 88 | + |
| 89 | +PaddleClas 提供的 backbone 网络均基于图像分类数据集训练得到,因此网络的尾部带有用于分类的全连接层,而在特定任务场景下,需要去掉分类的全连接层。在部分下游任务中,例如目标检测场景,需要获取到网络中间层的输出结果,也可能需要对网络的中间层进行修改,因此 `TheseusLayer` 提供了 3 个接口函数用于实现不同的修改功能。 |
| 90 | + |
| 91 | +<a name="3.1"></a> |
| 92 | + |
| 93 | +### 3.1 网络截断(stop_after) |
| 94 | + |
| 95 | +```python |
| 96 | +def stop_after(self, stop_layer_name: str) -> bool: |
| 97 | + """stop forward and backward after 'stop_layer_name'. |
| 98 | +
|
| 99 | + Args: |
| 100 | + stop_layer_name (str): The name of layer that stop forward and backward after this layer. |
| 101 | +
|
| 102 | + Returns: |
| 103 | + bool: 'True' if successful, 'False' otherwise. |
| 104 | + """ |
| 105 | +``` |
| 106 | + |
| 107 | +该方法可通过参数 `stop_layer_name` 指定网络中的特定子层,并将该层之后的所有层修改为映射层(`Identity`),从而达到网络截断的目的。映射层(`Identity`)的定义如下: |
| 108 | + |
| 109 | +```python |
| 110 | +class Identity(nn.Layer): |
| 111 | + def __init__(self): |
| 112 | + super(Identity, self).__init__() |
| 113 | + |
| 114 | + def forward(self, inputs): |
| 115 | + return inputs |
| 116 | +``` |
| 117 | + |
| 118 | +当该方法成功执行时,其返回值为 `True`,否则为 `False`。 |
| 119 | + |
| 120 | +以 `MobileNetV1` 网络为例,当 `stop_layer_name` 为 `"blocks[0].depthwise_conv.conv"`,该方法: |
| 121 | +* 将网络 `MobileNetV1` 的 `avg_pool`、`flatten` 和 `fc` 置为 `Identity`; |
| 122 | +* 将 `blocks` 层的第 2 至 第 13 个子层置为 `Identity`; |
| 123 | +* 将 `blocks` 层第 1 个子层的 `pointwise_conv` 置为 `Identity`; |
| 124 | +* 将 `blocks` 层第 1 个子层的 `depthwise_conv` 的 `bn` 和 `relu` 置为 `Identity`; |
| 125 | + |
| 126 | +具体效果可以参考下方代码案例进行尝试。 |
| 127 | + |
| 128 | +```python |
| 129 | +import paddleclas |
| 130 | + |
| 131 | +net = paddleclas.MobileNetV1() |
| 132 | +print("========== the origin mobilenetv1 net arch ==========") |
| 133 | +print(net) |
| 134 | + |
| 135 | +res = net.stop_after(stop_layer_name="blocks[0].depthwise_conv.conv") |
| 136 | +print("The result returned by stop_after(): ", res) |
| 137 | +# The result returned by stop_after(): True |
| 138 | + |
| 139 | +print("\n\n========== the truncated mobilenetv1 net arch ==========") |
| 140 | +print(net) |
| 141 | +``` |
| 142 | + |
| 143 | +<a name="3.2"></a> |
| 144 | + |
| 145 | +### 3.2 返回网络中间层输出(update_res) |
| 146 | + |
| 147 | +```python |
| 148 | +def update_res( |
| 149 | + self, |
| 150 | + return_patterns: Union[str, List[str]]) -> Dict[str, nn.Layer]: |
| 151 | + """update the result(s) to be returned. |
| 152 | +
|
| 153 | + Args: |
| 154 | + return_patterns (Union[str, List[str]]): The name of layer to return output. |
| 155 | +
|
| 156 | + Returns: |
| 157 | + Dict[str, nn.Layer]: The pattern(str) and corresponding layer(nn.Layer) that have been set successfully. |
| 158 | + """ |
| 159 | +``` |
| 160 | + |
| 161 | +该方法可通过参数 `return_patterns` 指定一层或多层网络的中间子层,并在网络前向时,将指定层的输出结果与网络的最终结果一同返回。该方法的返回值为 `dict` 对象,元素为设置成功的层,其中,key 为设置成功的网络层描述符,value 为对应的网络层对象。 |
| 162 | + |
| 163 | +以 `MobileNetV1` 网络为例,当 `return_patterns` 为 `["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"]`,在网络前向推理时,网络的输出结果将包含以上 4 层的输出,具体效果可以参考下方代码案例进行尝试。 |
| 164 | + |
| 165 | +```python |
| 166 | +import numpy as np |
| 167 | +import paddle |
| 168 | +import paddleclas |
| 169 | + |
| 170 | +np_input = np.zeros((1, 3, 224, 224)) |
| 171 | +pd_input = paddle.to_tensor(np_input, dtype="float32") |
| 172 | + |
| 173 | +net = paddleclas.MobileNetV1(pretrained=True) |
| 174 | + |
| 175 | +output = net(pd_input) |
| 176 | +print("The output's type of origin net: ", type(output)) |
| 177 | +# The output's type of origin net: <class 'paddle.Tensor'> |
| 178 | + |
| 179 | +res = net.update_res(return_patterns=["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"]) |
| 180 | +print("The result returned by update_res(): ", res) |
| 181 | +# The result returned by update_res(): {'blocks[0]': ...} |
| 182 | + |
| 183 | +output = net(pd_input) |
| 184 | +print("The output's keys of processed net: ", output.keys()) |
| 185 | +# The output's keys of net: dict_keys(['output', 'blocks[0]', 'blocks[2]', 'blocks[4]', 'blocks[10]']) |
| 186 | +``` |
| 187 | + |
| 188 | +除了通过调用方法 `update_res()` 的方式之外,也同样可以在实例化网络对象时,通过指定参数 `return_patterns` 实现相同效果: |
| 189 | + |
| 190 | +```python |
| 191 | +net = paddleclas.MobileNetV1(pretrained=True, return_patterns=["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"]) |
| 192 | +``` |
| 193 | + |
| 194 | +<a name="3.3"></a> |
| 195 | + |
| 196 | +### 3.3 修改网络中间层(upgrade_sublayer) |
| 197 | + |
| 198 | +```python |
| 199 | +def upgrade_sublayer(self, |
| 200 | + layer_name_pattern: Union[str, List[str]], |
| 201 | + handle_func: Callable[[nn.Layer, str], nn.Layer] |
| 202 | + ) -> Dict[str, nn.Layer]: |
| 203 | + """use 'handle_func' to modify the sub-layer(s) specified by 'layer_name_pattern'. |
| 204 | +
|
| 205 | + Args: |
| 206 | + layer_name_pattern (Union[str, List[str]]): The name of layer to be modified by 'handle_func'. |
| 207 | + 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. |
| 208 | +
|
| 209 | + Returns: |
| 210 | + Dict[str, nn.Layer]: The key is the pattern and corresponding value is the result returned by 'handle_func()'. |
| 211 | + """ |
| 212 | +``` |
| 213 | + |
| 214 | +该方法可通过参数 `layer_name_pattern` 指定一层或多层网络子层,并使用参数 `handle_func` 所指定的函数对指定的子层进行修改。该方法的返回值为 `dict`,元素为修改的层,其中,key 为指定的网络层描述符,value 为 `handle_func` 针对该层的返回结果。 |
| 215 | + |
| 216 | +`upgrade_sublayer` 方法会根据 `layer_name_pattern` 查找对应的网络子层,并将查找到的子层和其 `pattern` 传入可调用对象 `handle_func`,并使用 `handle_func` 的返回值替换该层。需要注意的是,形参 `handle_func` 须为可调用对象,且该对象应有 2 个形参,第 1 个形参为 `nn.Layer` 类型,第 2 个形参为 `str` 类型,该可调用对象返回值必须为 `nn.Layer` 类型对象。 |
| 217 | + |
| 218 | +以 `MobileNetV1` 网络为例,将网络最后的 2 个 block 中的深度可分离卷积(depthwise_conv)改为 `5*5` 大小的卷积核,同时将 padding 改为 `2`,如下方代码所示: |
| 219 | + |
| 220 | +```python |
| 221 | +from paddle import nn |
| 222 | +import paddleclas |
| 223 | + |
| 224 | +def rep_func(layer: nn.Layer, pattern: str): |
| 225 | + new_layer = nn.Conv2D( |
| 226 | + in_channels=layer._in_channels, |
| 227 | + out_channels=layer._out_channels, |
| 228 | + kernel_size=5, |
| 229 | + padding=2 |
| 230 | + ) |
| 231 | + return new_layer |
| 232 | + |
| 233 | +net = paddleclas.MobileNetV1(pretrained=True) |
| 234 | +print("========== the origin mobilenetv1 net arch ==========") |
| 235 | +print(net) |
| 236 | + |
| 237 | +res = net.upgrade_sublayer(layer_name_pattern=["blocks[11].depthwise_conv.conv", "blocks[12].depthwise_conv.conv"], handle_func=rep_func) |
| 238 | +print("The result returned by upgrade_sublayer() is", res) |
| 239 | +# The result returned by replace_sub() is {'blocks[11].depthwise_conv.conv': Conv2D(512, 512, kernel_size=[5, 5], padding=2, data_format=NCHW), 'blocks[12].depthwise_conv.conv': Conv2D(1024, 1024, kernel_size=[5, 5], padding=2, data_format=NCHW)} |
| 240 | + |
| 241 | +print("\n\n========== the upgraded mobilenetv1 net arch ==========") |
| 242 | +print(net) |
| 243 | +``` |
0 commit comments