资源 | 从VGG到ResNet,你想要的MXNet预训练模型轻松学

时间:2022-05-31
本文章向大家介绍资源 | 从VGG到ResNet,你想要的MXNet预训练模型轻松学,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

选自AWS Blog

作者:Julien Simon

机器之心编译

参与:Pedro、路

本文介绍了如何利用 Apache MXNet 预训练出的多个模型。每个模型在特定图像上的表现略有不同,训练多个模型旨在找出更适合特定任务的模型。

在这篇博文中,你将会了解如何使用 Apache MXNet 预训练出的多个模型。为什么要尝试多个模型呢?为什么不直接选择准确率最高的呢?稍后我们会在文章中看到,尽管这些模型是在相同的数据集上训练的,并且都针对最大准确率进行了优化,但它们在特定图像上的表现略有不同。此外,(不同模型)预测速度也不同,而对很多应用来说速度是一个重要的影响因素。尝试这些模型,或许能找到一个合适的模型解决手头上的难题。首先,我们先从 Apache MXNet 模型库中下载三个图像分类模型。(模型库地址:http://mxnet.io/model_zoo/)

三个模型分别是:

  • VGG-16,获得 2014 年 ImageNet 大规模视觉识别挑战赛分类项目冠军。
  • Inception v3,GoogleNet 的进化版,获得 2014 年比赛的目标检测项目冠军。
  • ResNet-152,获得 2015 年比赛的多个项目的冠军。

我们需要为每一个模型下载两个文件:

  • 包含神经网络 JSON 定义的符号文件:层、连接、激活函数等。
  • 网络在训练阶段学习到的存储了所有连接权重、偏置项和 AKA 参数的权重文件。
# MacOS users can easily install 'wget' with Homebrew: 'brew install wget'
!wget http://data.dmlc.ml/models/imagenet/vgg/vgg16-symbol.json -O vgg16-symbol.json
!wget http://data.dmlc.ml/models/imagenet/vgg/vgg16-0000.params -O vgg16-0000.params
!wget http://data.dmlc.ml/models/imagenet/inception-bn/Inception-BN-symbol.json -O Inception-BN-symbol.json
!wget http://data.dmlc.ml/models/imagenet/inception-bn/Inception-BN-0126.params -O Inception-BN-0000.params
!wget http://data.dmlc.ml/models/imagenet/resnet/152-layers/resnet-152-symbol.json -O resnet-152-symbol.json
!wget http://data.dmlc.ml/models/imagenet/resnet/152-layers/resnet-152-0000.params -O resnet-152-0000.params
!wget http://data.dmlc.ml/models/imagenet/synset.txt -O synset.txt

让我们来看看 VGG-16 符号文件的第一行。可以看到输入层的定义('data'),第一个卷积层的权重和偏置项。卷积操作和线性修正单元激活函数分别用(『conv1_1』)和(『relu1_1』)定义。

!head -48 vgg16-symbol.json

三个模型都使用 ImageNet 训练集进行预训练。这个训练集包含超过 120 万张物体和动物的图像,这些图像被分成了 1000 个类别。我们可以在 synset.txt 文件中查看这些类别。

!head -10 synset.txt
import mxnet as mx
import numpy as np
import cv2, sys, time   # You can easily install OpenCV with 'pip install cv2'
from collections import namedtuple
from IPython.core.display import Image, display

print("MXNet version: %s"  % mx.__version__)

现在加载一个模型。

首先,我们需要从文件中加载权重和模型描述。MXNet 将此称为检查点。在每个训练 epoch 之后保存权重是个好习惯。一旦训练完成,我们可以查看训练日志,然后选择最佳 epoch 的权重,最优 epoch 即具有最高验证准确度的 epoch。一般来说它不会是最后一个 epoch。在模型加载完成之后,我们得到一个 Symbol 对象和权重、AKA 模型参数。之后我们创建一个新 Module 并为其分配 Symbol 作为输入。我们可以选择运行模型的环境:默认情况下使用 CPU 环境。这么做有两个原因:

  • 第一,即使你的电脑没有 GPU,你也能测试 notebook(https://s3.amazonaws.com/aws-ml-blog/artifacts/pre-trained-apache-mxnet-models/Pre-trained%2Bmodels.ipynb)。
  • 第二,我们接下来只预测单个图像,因此对性能没有特殊要求。对于那些希望通过预测大量图像以获得最佳吞吐量的应用产品,GPU 肯定是最优选择。

然后,我们将 Symbol 作为输入数据。我们称之为 data,为了与它在网络输入层时的名字保持一致(JSON 文件的前几行提过)。最后,我们将 data 的形态定义成 1 x 3 x 224 x 224。224 x 224 是图像分辨率:模型就是使用这个分辨率的图像来训练的。3 是通道数量:红色、绿色和蓝色(按此顺序)。1 是批量大小:一次预测一个图像。

def loadModel(modelname, gpu=False):
        sym, arg_params, aux_params = mx.model.load_checkpoint(modelname, 0)
        arg_params['prob_label'] = mx.nd.array([0])
        arg_params['softmax_label'] = mx.nd.array([0])
        if gpu:
            mod = mx.mod.Module(symbol=sym, context=mx.gpu(0))
        else:
            mod = mx.mod.Module(symbol=sym)
        mod.bind(for_training=False, data_shapes=[('data', (1,3,224,224))])
        mod.set_params(arg_params, aux_params)
        return mod

我们还需要加载存储在 synset.txt 文件中的 1000 个类别。预测时会使用这些类别描述。

def loadCategories():
        synsetfile = open('synset.txt', 'r')
        synsets = []
        for l in synsetfile:
                synsets.append(l.rstrip())
        return synsets

synsets = loadCategories()
print(synsets[:10])

现在我们编写一个从文件中加载图像的函数。别忘了,模型需要一个四维的 NDArray,其中包含 224 x 224 图像的红色、绿色以及蓝色通道。我们将利用 openCV 库及输入图像来构建 NDArray。

下面是具体步骤:

  • 读取图像:此步会返回一个形如(图像高度,图像宽度,3)的 numpy 数组。它包含三个 BGR 顺序的通道(蓝色、绿色、红色)。
  • 将图像转换为 RGB 顺序(红色、绿色、蓝色)。
  • 将图像大小改为 224 x 224。
  • 将数组从(图像高度,图像宽度,3)转换为(3,图像高度,图像宽度)。
  • 加上第四个维度,然后构建 NDArray。
def prepareNDArray(filename):
        img = cv2.imread(filename)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        img = cv2.resize(img, (224, 224,))
        img = np.swapaxes(img, 0, 2)
        img = np.swapaxes(img, 1, 2)
        img = img[np.newaxis, :]
        array = mx.nd.array(img)
        print(array.shape)
        return array

现在我们把重点放在预测上。我们的参数是单个图像、模型、类别列表以及我们想要优先返回的类别数量。

记住,Module 对象必须批量地向模型输入数据。通常的做法是使用数据迭代器。此处,我们想要预测单个图像,所以虽然我们可以使用数据迭代器,但它可能会越界。所以,我们创建一个叫 Batch 的元组,它将作为伪迭代器,在其 data 属性被引用时返回输入 NDArray。

在图像馈送至模型后,模型输出一个包含 1000 种可能性的 NDArray,对应 1000 个类别。NDArray 只有一行因为批大小为 1。

我们使用 squeeze() 将其转换为数组。之后使用 argsort() 创建第二个数组用于存放这些降序排列的概率索引。最后我们返回前 n 个类别及其描述。

def predict(filename, model, categories, n):
        array = prepareNDArray(filename)
        Batch = namedtuple('Batch', ['data'])
        t1 = time.time()
        model.forward(Batch([array]))
        prob = model.get_outputs()[0].asnumpy()
        t2 = time.time()
        print("Predicted in %.2f microseconds" % (t2-t1))
        prob = np.squeeze(prob)
        sortedprobindex = np.argsort(prob)[::-1]

        topn = []
        for i in sortedprobindex[0:n]:
                topn.append((prob[i], categories[i]))
        return topn

现在是时候将所有的模块组装起来了。加载这三个模型:

gpu = False
vgg16 = loadModel("vgg16", gpu)
resnet152 = loadModel("resnet-152", gpu)
inceptionv3 = loadModel("Inception-BN", gpu)
categories = loadCategories()

在进行图像分类之前,我们来仔细查看一下之前从 .params 文件中加载得到的 VGG-16 模型参数。首先,我们输出所有层的名字。

params = vgg16.get_params()

layers = []
for layer in params[0].keys():
    layers.append(layer)

layers.sort()    
print(layers)

对每一层而言,有两个部分值得我们关注:权重和偏置项。数一数这些权重你就会发现一共有 16 个层:13 个卷积层以及 3 个全连接层。现在你知道为什么这个模型叫 VGG-16 了。现在输出剩下的全连接层的权重:

print(params[0]['fc8_weight'])

你注意到这个矩阵的形状了吗?它是 1000×4096 的。这个层包含了 1000 个神经元:每一个神经元会存储图像属于某个特定分类的概率。每个神经元也和前一层(『fc7』)所有的神经元(4096 个)全部连接。

现在开始使用这些模型来对我们自己的图像进行分类:

!wget http://jsimon-public.s3.amazonaws.com/violin.jpg -O violin.jpg
image = "violin.jpg"

display(Image(filename=image))

topn = 5
print("*** VGG16")
print(predict(image,vgg16,categories,topn))
print("*** ResNet-152")
print(predict(image,resnet152,categories,topn))
print("*** Inception v3")
print(predict(image,inceptionv3,categories,topn))

再用 GPU 环境试试:

gpu = True
vgg16 = loadModel("vgg16", gpu)
resnet152 = loadModel("resnet-152", gpu)
inceptionv3 = loadModel("Inception-BN", gpu)

print("*** VGG16")
print(predict(image,vgg16,categories,topn))
print("*** ResNet-152")
print(predict(image,resnet152,categories,topn))
print("*** Inception v3")
print(predict(image,inceptionv3,categories,topn))

注意:如果遇到了关于 GPU 支持的错误,有可能是你的机器没有配置 GPU,或者你使用的 MXNet 版本尚未提供 GPU 支持(USE_CUDA=1)。

构建提供 GPU 支持的 MXNet 教程可参考:https://mxnet.incubator.apache.org/get_started/build_from_source.html;你也可以安装预制版本:https://mxnet.incubator.apache.org/install/index.html。

GPU 版本和 CPU 版本的性能差异非常明显,在 15 倍到 20 倍之间。如果我们同时预测多个图像,由于 GPU 架构的大规模并行性,二者差距会更大。

现在是时候用你自己的图像试试了。只需将它们复制到此 notebook(https://s3.amazonaws.com/aws-ml-blog/artifacts/pre-trained-apache-mxnet-models/Pre-trained%2Bmodels.ipynb)所处的文件夹即可,更新上述模块的文件名,然后再次运行 predict() 函数。