vis017_09.md 6.3 KB
Newer Older
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
1 2
# 模型检查的特征提取

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
3
> 原文:[`pytorch.org/vision/stable/feature_extraction.html`](https://pytorch.org/vision/stable/feature_extraction.html)
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
4 5 6 7 8 9 10 11 12

`torchvision.models.feature_extraction`包含特征提取工具,让我们可以访问模型对输入的中间转换。这对计算机视觉中的各种应用可能很有用。只是一些例子包括:

+   可视化特征图。

+   提取特征以计算图像描述符,用于面部识别、复制检测或图像检索等任务。

+   将选定的特征传递给下游子网络,以便根据特定任务进行端到端训练。例如,将特征的层次结构传递给具有目标检测头的特征金字塔网络。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
13
Torchvision 提供了`create_feature_extractor()`用于此目的。它大致按照以下步骤进行:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
14 15 16 17 18 19 20

1.  符号跟踪模型,以逐步获取其如何转换输入的图形表示。

1.  将用户选择的图节点设置为输出。

1.  删除所有冗余节点(输出节点之后的所有节点)。

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
21
1.  从生成的图形中生成 Python 代码,并将其与图形一起捆绑到 PyTorch 模块中。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
22

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
23
[torch.fx 文档](https://pytorch.org/docs/stable/fx.html)提供了对上述过程和符号跟踪内部工作的更一般和详细的解释。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
24 25 26

**关于节点名称**

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
27
为了指定应该作为提取特征的输出节点的节点,应该熟悉此处使用的节点命名约定(与`torch.fx`中使用的略有不同)。节点名称被指定为一个以`.`分隔的路径,从顶层模块向下遍历模块层次结构,直到叶操作或叶模块。例如,在 ResNet-50 中,`"layer4.2.relu"`代表`ResNet`模块第 4 层第 2 个块的 ReLU 的输出。以下是一些需要注意的细节:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
28

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
29
+   在为`create_feature_extractor()`指定节点名称时,您可以提供节点名称的缩写版本作为快捷方式。要查看其工作原理,请尝试创建一个 ResNet-50 模型,并使用`train_nodes, _ = get_graph_node_names(model) print(train_nodes)`打印节点名称,观察与`layer4`相关的最后一个节点是`"layer4.2.relu_2"`。可以将`"layer4.2.relu_2"`指定为返回节点,或者只指定`"layer4"`,因为按照惯例,这指的是`layer4`的最后一个节点(按执行顺序)。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
30

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
31
+   如果某个模块或操作重复多次,则节点名称会附加`_{int}`后缀以消除歧义。例如,也许加法(`+`)操作在同一个`forward`方法中使用了三次。那么会有`"path.to.module.add"``"path.to.module.add_1"``"path.to.module.add_2"`。计数器在直接父级的范围内维护。因此,在 ResNet-50 中有一个`"layer4.1.add"`和一个`"layer4.2.add"`。因为加法操作位于不同的块中,所以不需要后缀来消除歧义。
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
32 33 34

**一个示例**

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
35
这里有一个我们可能为 MaskRCNN 提取特征的示例:
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122

```py
import torch
from torchvision.models import resnet50
from torchvision.models.feature_extraction import get_graph_node_names
from torchvision.models.feature_extraction import create_feature_extractor
from torchvision.models.detection.mask_rcnn import MaskRCNN
from torchvision.models.detection.backbone_utils import LastLevelMaxPool
from torchvision.ops.feature_pyramid_network import FeaturePyramidNetwork

# To assist you in designing the feature extractor you may want to print out
# the available nodes for resnet50.
m = resnet50()
train_nodes, eval_nodes = get_graph_node_names(resnet50())

# The lists returned, are the names of all the graph nodes (in order of
# execution) for the input model traced in train mode and in eval mode
# respectively. You'll find that `train_nodes` and `eval_nodes` are the same
# for this example. But if the model contains control flow that's dependent
# on the training mode, they may be different.

# To specify the nodes you want to extract, you could select the final node
# that appears in each of the main layers:
return_nodes = {
    # node_name: user-specified key for output dict
    'layer1.2.relu_2': 'layer1',
    'layer2.3.relu_2': 'layer2',
    'layer3.5.relu_2': 'layer3',
    'layer4.2.relu_2': 'layer4',
}

# But `create_feature_extractor` can also accept truncated node specifications
# like "layer1", as it will just pick the last node that's a descendent of
# of the specification. (Tip: be careful with this, especially when a layer
# has multiple outputs. It's not always guaranteed that the last operation
# performed is the one that corresponds to the output you desire. You should
# consult the source code for the input model to confirm.)
return_nodes = {
    'layer1': 'layer1',
    'layer2': 'layer2',
    'layer3': 'layer3',
    'layer4': 'layer4',
}

# Now you can build the feature extractor. This returns a module whose forward
# method returns a dictionary like:
# {
#     'layer1': output of layer 1,
#     'layer2': output of layer 2,
#     'layer3': output of layer 3,
#     'layer4': output of layer 4,
# }
create_feature_extractor(m, return_nodes=return_nodes)

# Let's put all that together to wrap resnet50 with MaskRCNN

# MaskRCNN requires a backbone with an attached FPN
class Resnet50WithFPN(torch.nn.Module):
    def __init__(self):
        super(Resnet50WithFPN, self).__init__()
        # Get a resnet50 backbone
        m = resnet50()
        # Extract 4 main layers (note: MaskRCNN needs this particular name
        # mapping for return nodes)
        self.body = create_feature_extractor(
            m, return_nodes={f'layer{k}': str(v)
                             for v, k in enumerate([1, 2, 3, 4])})
        # Dry run to get number of channels for FPN
        inp = torch.randn(2, 3, 224, 224)
        with torch.no_grad():
            out = self.body(inp)
        in_channels_list = [o.shape[1] for o in out.values()]
        # Build FPN
        self.out_channels = 256
        self.fpn = FeaturePyramidNetwork(
            in_channels_list, out_channels=self.out_channels,
            extra_blocks=LastLevelMaxPool())

    def forward(self, x):
        x = self.body(x)
        x = self.fpn(x)
        return x

# Now we can build our model!
model = MaskRCNN(Resnet50WithFPN(), num_classes=91).eval() 
```

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
123
## API 参考
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
124

绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
125
| `create_feature_extractor`(model[, ...]) | 创建一个新的图模块,将给定模型的中间节点作为字典返回,用户可以指定键作为字符串,请求的输出作为值。 |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
126
| --- | --- |
绝不原创的飞龙's avatar
绝不原创的飞龙 已提交
127
| `get_graph_node_names`(model[, tracer_kwargs, ...]) | 开发工具,按执行顺序返回节点名称。 |