用深度学习模型提取特征

用途

有时候需要从图片(或文本)中提取出数值型特征,供各种模型使用。深度学习模型不仅可以用于分类回归,还能用于提取特征。通常使用训练好的模型,输入图片,输出为提取到的特征向量。

加入特征之后,结果往往不尽如人意,大致有以下原因:

  • 深度学习模型一般有 N 层结构,不能确定求取哪一层输出更合适。 深度学习模型很抽象——几十层的卷积、池化、信息被分散在网络参数之中。提取自然语言的特征时,常常提取词向量层的输出作为特征,有时也取最后一层用于描述句意;图像处理时往往提取最后一层输出向量;在图像目标识别问题中,常提取后两层子网络的输出作为组合向量。如何选择提取位置,取决于对模型的理解,后文将对图像处理层进行详细说明。

  • 针对不同问题训练出的模型,输出的特征也不同。 通常下载的 ResNet,VGG,BERT 预训练模型,虽然通用性高,但解决具体问题的能力比较弱。比如在自然语言处理中,用 GPT-2 或者 BERT 训练的模型只面对普通文章,如果从中提取特征用于判断辱骂,有些脏字可能有效,但是更多的“多义词”会被它的普通含义淹没。用自己的数据 fine-tune 后往往更有针对性,而 fine-tune 的目标也需仔细斟酌,否则可能起到反作用。比如希望用 ResNet 识别不同的衣服,就需要考虑到衣服的形状、质地、颜色等等因素,如果用衣服类型(大衣、裤子)的分类器去 fine-tune 模型,新模型可能对形状比较敏感,而对材质、颜色的识别效果反而变差。

原理

图像模型 ResNet-50 规模适中,效果也很好,因此被广泛使用。下面将介绍该模型各层输出的含义,以及用它提取图片特征的方法。

ResNet 原理详见论文:https://arxiv.org/pdf/1512.03385.pdf。常用的 ResNet 网络参数如下,

可以看到,它包含四层 Bottlenect(子网络),越往后,获取的特征越抽象。

以单图为例,输入模型的图像结构为 [1, 3, 224, 224],图片大小为 224x224(大小根据具体图片而定),有红、绿、蓝 3 个通道。

第一步,经过一个 7x7 卷积层,步长为 2,它的输出是:(1,64,112,112),可视为 64 通道的 112x112 大小的图片,处理后效果如下图所示。

该层共生成 64 张图片,由于步长是 2,大小变为 112x112,每一种特征提取方法对应一组参数,这些参数对每 7x7 个像素进行同样处理,最终生成一个新的像素。换言之,就是构造了 64 种特征提取方法,分别提取了颜色,形状,边缘等特征,也可以看到由于处理以卷积为基础,图像位置关系得以保留。

在输入一张图片时,一个 224x224 的图通过这一层,提取了 64x112x112=802816 维特征,该层一般称为第一组卷积层 conv1,由于该层次太过底层,维度过大,很少使用该层特征。

经过第一层之后,又经过归一化,激活函数,以及步长为 2 的池化,输出大小为 [1, 64, 56, 56],如下图所示:

然后依次传入四个 Bottlenext 子网络(原理同上),分别称为 conv2, conv3, conv4, conv5(也有名为 layer1,layer2,layer3,layer4),输出的大小也逐层递减,最终减致 2048x7x7=100352,长宽分别是原始参数的 1/32。Mask-RCNN 中就可获取 R-50 的第 4 和第 5 次作为特征。四层输出如下:

第一维是图像的张数,第二维是通道数,后两维分别是图片的宽高,从任一通道取出数据,都可以直接绘制该通道的图像。数据流至最后一个子网络 conv5 后输出是 2048 个 7x7 大小的图片。有时候将 7x7 的图片做最大池化或平均池化,最终得到一个值,可理解成从该通道提取的一个特征值。到这一步,特征已经和像素位置无关了。

如果希望提取到的特征不是 2048 维,则可以在后面再加一个输入为 2048,输出为目标维度的全连接层。从上述分析中可以大概了解模型的规模,以及运算量。

另外一个常见的问题是:图像处理对图像的大小有没有要求?是不是所有大小的图片都可以直接代入模型?上例中使用 224x224 大小的图片,经过多层池化,最终变成了 7x7 大小。当然也可以代入更大图片(图片越大占用资源越多),比如 448x224 的图片,最终输出为 14x7。一般在训练时,往往经过 crop 裁剪和 resize 缩放的步骤,把图片变为统一大小。输入模型时往往以 batch 为单位,同一 batch 中的数据大小必须一致,否则只能单张处理。

示例

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
39
40
41
下例为从指定的层提取ResNet50的特征。

import torch
from torch import nn
import torchvision.models as models
import torchvision.transforms as transforms
import cv2

class FeatureExtractor(nn.Module): # 提取特征工具
def __init__(self, submodule, extracted_layers):
super(FeatureExtractor, self).__init__()
self.submodule = submodule
self.extracted_layers = extracted_layers

def forward(self, x):
outputs = []
for name, module in self.submodule._modules.items():
if name is "fc":
x = x.view(x.size(0), -1)
x = module(x)
if name in self.extracted_layers:
outputs.append(x)
return outputs

model = models.resnet50(pretrained=True) # 加载resnet50工具
model = model.cuda()
model.eval()

img=cv2.imread('test.jpg') # 加载图片
img=cv2.resize(img,(224,224));
img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
img=transform(img).cuda()
img=img.unsqueeze(0)

model2 = FeatureExtractor(model, ['layer3']) # 指定提取 layer3 层特征
with torch.no_grad():
out=model2(img)
print(len(out), out[0].shape)