11 KiB
TheseusLayer 使用说明
基于 TheseusLayer 构建的网络模型,支持网络截断、返回网络中间层输出和修改网络中间层的功能。
目录
1. 前言
TheseusLayer
是继承了 nn.Layer
的子类,使用 TheseusLayer
作为父类构建的网络模型,可以通过 TheseusLayer
的 stop_after()
、update_res()
和 upgrade_sublayer()
实现网络截断、返回中间层输出以及修改网络中间层的功能。目前 PaddleClas 中 ppcls.arch.backbone.legendary_models
下的所有模型均支持上述操作。
如需基于 TheseusLayer
构建新的网络结构,只需继承 TheseusLayer
即可:
from ppcls.arch.backbone.base.theseus_layer import TheseusLayer
class net(TheseusLayer):
def __init__():
super().__init__()
def forward(x):
pass
2. 网络层描述符说明
使用 TheseusLayer
提供的方法对模型进行操作/修改时,需要通过参数指定网络中间层,因此 TheseusLayer
规定了用于描述网络中间层的网络层描述符。网络层描述符为 Python 字符串(str)类型,使用网络层对象的变量名指定子层,以 .
作为网络层级的分隔符,对于 nn.Sequential
类型的层,使用 ["index"]
指定其子层。
以 MobileNetV1
网络为例,其模型结构定义在 MobileNetV1。网络 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 层子层组成,如下图所示:
MobileNetV1 (TheseusLayer)
├── conv (ConvBNLayer)
│ ├── conv (nn.Conv2D)
│ ├── bn (nn.BatchNorm)
│ └── relu (nn.ReLU)
│
├── blocks (nn.Sequential)
│ ├── blocks0 (DepthwiseSeparable)
│ │ ├── depthwise_conv (ConvBNLayer)
│ │ │ ├── conv (nn.Conv2D)
│ │ │ ├── bn (nn.BatchNorm)
│ │ │ └── relu (nn.ReLU)
│ │ └── pointwise_conv (ConvBNLayer)
│ │ ├── conv (nn.Conv2D)
│ │ ├── bn (nn.BatchNorm)
│ │ └── relu (nn.ReLU)
│ .
│ .
│ .
│ └── blocks12 (DepthwiseSeparable)
│ ├── depthwise_conv (ConvBNLayer)
│ │ ├── conv (nn.Conv2D)
│ │ ├── bn (nn.BatchNorm)
│ │ └── relu (nn.ReLU)
│ └── pointwise_conv (ConvBNLayer)
│ ├── conv (nn.Conv2D)
│ ├── bn (nn.BatchNorm)
│ └── relu (nn.ReLU)
│
├── avg_pool (nn.AdaptiveAvgPool2D)
│
├── flatten (nn.Flatten)
│
└── fc (nn.Linear)
因此,对于 MobileNetV1
而言:
- 网络层描述符
blocks[0].depthwise_conv.conv
,其指定了网络MobileNetV1
的blocks
层中的第1
个DepthwiseSeparable
对象中的depthwise_conv
中的conv
这一层; - 网络层描述符
blocks[5]
,其指定了网络MobileNetV1
的blocks
层中的第6
个DepthwiseSeparable
对象这一层; - 网络层描述符
flatten
,其指定了网络MobileNetV1
的flatten
这一层。
3. 方法说明
PaddleClas 提供的 backbone 网络均基于图像分类数据集训练得到,因此网络的尾部带有用于分类的全连接层,而在特定任务场景下,需要去掉分类的全连接层。在部分下游任务中,例如目标检测场景,需要获取到网络中间层的输出结果,也可能需要对网络的中间层进行修改,因此 TheseusLayer
提供了 3 个接口函数用于实现不同的修改功能。
3.1 网络截断(stop_after)
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.
"""
该方法可通过参数 stop_layer_name
指定网络中的特定子层,并将该层之后的所有层修改为映射层(Identity
),从而达到网络截断的目的。映射层(Identity
)的定义如下:
class Identity(nn.Layer):
def __init__(self):
super(Identity, self).__init__()
def forward(self, inputs):
return inputs
当该方法成功执行时,其返回值为 True
,否则为 False
。
以 MobileNetV1
网络为例,当 stop_layer_name
为 "blocks[0].depthwise_conv.conv"
,该方法:
- 将网络
MobileNetV1
的avg_pool
、flatten
和fc
置为Identity
; - 将
blocks
层的第 2 至 第 13 个子层置为Identity
; - 将
blocks
层第 1 个子层的pointwise_conv
置为Identity
; - 将
blocks
层第 1 个子层的depthwise_conv
的bn
和relu
置为Identity
;
具体效果可以参考下方代码案例进行尝试。
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)
3.2 返回网络中间层输出(update_res)
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.
"""
该方法可通过参数 return_patterns
指定一层或多层网络的中间子层,并在网络前向时,将指定层的输出结果与网络的最终结果一同返回。该方法的返回值为 dict
对象,元素为设置成功的层,其中,key 为设置成功的网络层描述符,value 为对应的网络层对象。
以 MobileNetV1
网络为例,当 return_patterns
为 ["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"]
,在网络前向推理时,网络的输出结果将包含以上 4 层的输出,具体效果可以参考下方代码案例进行尝试。
import numpy as np
import paddle
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)
# The result returned by update_res(): {'blocks[0]': ...}
output = net(pd_input)
print("The output's keys of processed net: ", output.keys())
# The output's keys of net: dict_keys(['output', 'blocks[0]', 'blocks[2]', 'blocks[4]', 'blocks[10]'])
除了通过调用方法 update_res()
的方式之外,也同样可以在实例化网络对象时,通过指定参数 return_patterns
实现相同效果:
net = paddleclas.MobileNetV1(pretrained=True, return_patterns=["blocks[0]", "blocks[2]", "blocks[4]", "blocks[10]"])
3.3 修改网络中间层(upgrade_sublayer)
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()'.
"""
该方法可通过参数 layer_name_pattern
指定一层或多层网络子层,并使用参数 handle_func
所指定的函数对指定的子层进行修改。该方法的返回值为 dict
,元素为修改的层,其中,key 为指定的网络层描述符,value 为 handle_func
针对该层的返回结果。
upgrade_sublayer
方法会根据 layer_name_pattern
查找对应的网络子层,并将查找到的子层和其 pattern
传入可调用对象 handle_func
,并使用 handle_func
的返回值替换该层。需要注意的是,形参 handle_func
须为可调用对象,且该对象应有 2 个形参,第 1 个形参为 nn.Layer
类型,第 2 个形参为 str
类型,该可调用对象返回值必须为 nn.Layer
类型对象。
以 MobileNetV1
网络为例,将网络最后的 2 个 block 中的深度可分离卷积(depthwise_conv)改为 5*5
大小的卷积核,同时将 padding 改为 2
,如下方代码所示:
from paddle import nn
import paddleclas
def rep_func(layer: nn.Layer, pattern: str):
new_layer = nn.Conv2D(
in_channels=layer._in_channels,
out_channels=layer._out_channels,
kernel_size=5,
padding=2
)
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)
# 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)}
print("\n\n========== the upgraded mobilenetv1 net arch ==========")
print(net)