关于特征提取

深度学习模型提取特征

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

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

通常下载的ResNet,VGG,BERT预训练模型,虽然通用性高,但解决具体问题的能力比较弱。 用自己的数据fine-tune后往往更有针对性,而fine-tune的目标也需仔细斟酌,否则可能起到反作用。比如希望用ResNet识别不同的衣服,就需要考虑到衣服的形状、质地、颜色等等因素,如果用衣服类型(大衣、裤子)的分类器去fine-tune模型,新模型可能对形状比较敏感,而对材质、颜色的识别效果反而变差。

原理

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

ResNet原理详见论文:https://arxiv.org/pdf/1512.03385.pdf。 常用的ResNet网络参数如下,可以看到,包含四层Bottlenetct(子网络),越往后,获取的特征越抽象.

输入模型的图像结构为[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中的数据大小必须一致,否则只能单张处理。

下例为从指定的层提取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)

使用resnet

import torch
import torch.nn as nn
from torch.autograd import Variable
from torchvision import models, transforms
from PIL import Image
import numpy as np
import os, glob
 
data_dir = './test'   # train
features_dir = './Resnet_features_test'  # Resnet_features_train
 
 
//这里自己修改网络
class net(nn.Module):
    def __init__(self):
        super(net, self).__init__()
        self.net = models.resnet50(pretrained=True)
 
    def forward(self, input):
        output = self.net.conv1(input)
        output = self.net.bn1(output)
        output = self.net.relu(output)
        output = self.net.maxpool(output)
        output = self.net.layer1(output)
        output = self.net.layer2(output)
        output = self.net.layer3(output)
        output = self.net.layer4(output)
        output = self.net.avgpool(output)
        return output
 
 
model = net()
//加载cuda
model = model.cuda()
 
 
def extractor(img_path, saved_path, net, use_gpu):
    transform = transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor()]
    )
 
    img = Image.open(img_path)
    img = transform(img)
    print(img.shape)
 
    x = Variable(torch.unsqueeze(img, dim=0).float(), requires_grad=False)
    print(x.shape)
 
    if use_gpu:
        x = x.cuda()
        net = net.cuda()
    y = net(x).cpu()
    y = torch.squeeze(y)
    y = y.data.numpy()
    print(y.shape)
    np.savetxt(saved_path, y, delimiter=',')
 
 
if __name__ == '__main__':
    extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
 
    files_list = []
    x = os.walk(data_dir)
    for path,d,filelist in x:
        for filename in filelist:
            file_glob = os.path.join(path, filename)
            files_list.extend(glob.glob(file_glob))
 
    print(files_list)
 
    use_gpu = torch.cuda.is_available()
 
    for x_path in files_list:
        print("x_path" + x_path)
        file_name = x_path.split('/')[-1]
        fx_path = os.path.join(features_dir, file_name + '.txt')
        print(fx_path)
        extractor(x_path, fx_path, model, use_gpu)

使用vgg

import torch
import torch.nn as nn
from torch.autograd import Variable
from torchvision import models, transforms
from PIL import Image
import numpy as np
import os, glob
 
data_dir = './test'  # train
features_dir = './Vgg_features_test'  # Vgg_features_train
 
 
class Encoder(nn.Module):
    def __init__(self):
        super(Encoder, self).__init__()
        VGG = models.vgg16(pretrained=True)
        self.feature = VGG.features
        self.classifier = nn.Sequential(*list(VGG.classifier.children())[:-3])
        pretrained_dict = VGG.state_dict()
        model_dict = self.classifier.state_dict()
        pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict}
        model_dict.update(pretrained_dict)
        self.classifier.load_state_dict(model_dict)
 
    def forward(self, x):
        output = self.feature(x)
        output = output.view(output.size(0), -1)
        output = self.classifier(output)
        return output
 
 
model = Encoder()
model = model.cuda()
 
 
def extractor(img_path, saved_path, net, use_gpu):
    transform = transforms.Compose([
        transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor()]
    )
 
    img = Image.open(img_path)
    img = transform(img)
    print(img.shape)
 
    x = Variable(torch.unsqueeze(img, dim=0).float(), requires_grad=False)
    print(x.shape)
 
    if use_gpu:
        x = x.cuda()
        net = net.cuda()
    y = net(x).cpu()
    y = torch.squeeze(y)
    y = y.data.numpy()
    print(y.shape)
    np.savetxt(saved_path, y, delimiter=',')
 
 
if __name__ == '__main__':
    extensions = ['jpg', 'jpeg', 'JPG', 'JPEG']
 
    files_list = []
    x = os.walk(data_dir)
    for path, d, filelist in x:
        for filename in filelist:
            file_glob = os.path.join(path, filename)
            files_list.extend(glob.glob(file_glob))
 
    print(files_list)
 
    use_gpu = torch.cuda.is_available()
 
    for x_path in files_list:
        print("x_path" + x_path)
        file_name = x_path.split('/')[-1]
        fx_path = os.path.join(features_dir, file_name + '.txt')
        print(fx_path)
        extractor(x_path, fx_path, model, use_gpu)

使用三个模型的特征提取的方法

updatedupdated2021-03-012021-03-01