1. 程式人生 > >從VGG到ResNet,你想要的MXNet預訓練模型輕鬆學

從VGG到ResNet,你想要的MXNet預訓練模型輕鬆學

本文介紹瞭如何利用 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() 函式。

原文地址:https://aws.amazon.com/cn/blogs/machine-learning/use-pre-trained-models-with-apache-mxnet/