三、將其他模型文件轉化成Core ML模型文件(PB)

我們在工作或學習中,往往并不能夠直接得到Core ML模型。這就需要將其他模型轉化成Core ML。這篇文章將會介紹如何借助一個名為tf-coreml的開源項目,將一個手寫字識別的PB模型,轉化為Core ML的mlmodel模型。并將其部署到我們的iOS項目中。

本文依賴:

Python
tensorflow >= 1.5.0
coremltools >= 0.8
numpy >= 1.6.2
protobuf >= 3.1.0
six >= 1.10.0

注意:
1.閱讀本文,你最好親自動手使用TensorFlow生成并訓練過一個神經網絡模型,并使用模型做過預測。當然,這樣做的目的,是為了讓你自己對TensorFlow和神經網絡模型有一個大致的概念,因此你大可不必關心你的模型的準確率。

2.由于本文從網絡上獲取了一些Python代碼,他們有些是Python2,有些是Python3,因此本文將嚴格區(qū)分Python2和Python3的命令,包括pip2和pip3。不會出現類似:Python file.py 的命令。你需要根據自己的Python環(huán)境,自行決定如何調用這些Python腳本。

一、準備一個PB文件:

首先,我們需要準備一個PB格式的模型文件。如果你手上沒有PB文件,你可以使用我已經訓練好的模型文件:

首先,你需要Clone我的項目:

git clone git@github.com:yangchenlarkin/CoreML.git

在files目錄中找到pb_model.pb文件

cd CoreML/files/
ls pb_model.pb

二、安裝tf-coreml

首先你需要安裝好文章開頭描述的相關依賴項。然后安裝tf-coreml

pip3 install -U tfcoreml

三、觀察tfcoreml.convert函數

我們需要使用tfcoreml.convert函數來進行轉換,傳入PB文件路徑和一些參數,這個函數會幫助你生成一個.mlmodel文件存到你指定的路徑:

def convert(tf_model_path,
            mlmodel_path,
            output_feature_names,
            input_name_shape_dict=None,
            image_input_names=None,
            is_bgr=False,
            red_bias=0.0,
            green_bias=0.0,
            blue_bias=0.0,
            gray_bias=0.0,
            image_scale=1.0,
            class_labels=None,
            predicted_feature_name=None,
            predicted_probabilities_output='',
            add_custom_layers=False,  # type: bool
            custom_conversion_functions={},  # type: Dict[Text, Any]
            )

去掉我們不需要關注的字段,剩下的,我們一一解釋一下。然后我會給出一些手段,幫助你了解如何獲得這些字段的數據。

def convert(tf_model_path,
            mlmodel_path,
            output_feature_names,
            input_name_shape_dict=None,
            image_input_names=None,
            class_labels=None,
            )
  • tf_model_path:
    • PB文件路徑,即我們第一步中準備的文件
  • mlmodel_path:
    • 我們最終需要的Core ML文件的存儲路徑
  • output_feature_names:
    • 需要轉化的輸出層名字列表。
    • TensorFlow中,一個模型可能有好幾個輸出,每個輸出都會有一個名字,這里需要定義我們需要將哪些輸出顯示的暴露出來。我們在上一篇文章中曾經介紹到,我們將CoreML模型文件,在Xcode中打開后,可以看到他的輸出,這個輸出,就是在這里定義的。
  • input_name_shape_dict:
    • 輸入層的shape字典。
    • TensorFlow中,一個模型也會有好幾個輸入,每個輸入都會有一個名字,輸入的shape可能不盡相同,因此需要在這里進行描述。
    • 對于圖片類型的輸入而言,一般都會是形如[?, sizeW, sizeH, n]的shape。解釋一下:第一個'?'是因為模型支持批量傳入數據集,而數據集的大小(有幾條數據,或者說一批傳入的圖片數)是可變的,因此這里是一個問號。sizeW和sizeH表示圖片寬高;n表示圖片通道數,比如說,1表示灰度圖,每個像素只有灰度,3表示彩色,可能是RGB,也可能是其他,4表示彩色加一個透明度通道。這個需要和構建這個神經網絡模型的工程師溝通一下。
    • 另外,由于Core ML只用于單個樣本的預測,不會批量傳樣本到模型,因此在轉化模型的時候,這里需要把'?'修改成1;
  • image_input_names:
    • 用于描述哪些輸入是圖片格式的,這個列表應該是input_name_shape_dict所有key的子集,
    • 如果這里不加描述,那么生成的Core ML模型將會認為這些輸入只是簡單的多維數組。加了這個描述之后,Core ML會將其描述為圖片,這樣,你才可以在Xcode中,以CVPixelBufferRef對象作為輸入。
  • class_labels:
    我們這里是一個圖片分類模型,這里可以設置一個字符串數組,用于對應輸出層的每一個節(jié)點,本文中因為是手寫字體識別,所以這里恰好應該是:
[str(x) for x in range(10)]

四、獲取信息

由上一小節(jié)我們可以知道,我們主要需要獲取的信息有:

input_name_shape_dict = None
image_input_names = None
output_feature_names = None
class_labels = None

1.利用tf-coreml內置的工具打印出PB文件的信息

首先我們將tf-coreml項目clone下來

git clone git@github.com:tf-coreml/tf-coreml.git

在utils文件加下找到一個名叫inspect_pb.py的文件:

cd tf-coreml/utils/
ls inspect_pb.py

這個腳本,需要兩個參數:

參數1:PB模型文件路徑
參數2:模型信息輸出的文件路徑

python3 inspect_pb.py <model.pb> <pb_info.txt>

其中 model.pb就是第一步中獲得的pb模型,pb_info.txt,你可以按照自己的喜好隨意指定,比如:

~/Desktop/pb_info.txt

執(zhí)行完畢后讓我們打開這個文件,我們看到很多內容,你可以和創(chuàng)建這個神經網絡的工程師溝通,了解一下他的輸入和輸入到底是哪個。在本文中,輸入和輸出分別是:


輸入
輸出

這里輸入和輸出的最后一行就是name和shape,值得注意的是:

  • 首先,我們不要前面的import/
  • 第二,如上文中所描述的,這里我們需要把輸入的'?'改成1
    因此,我們可以得到參數:
input_name_shape_dict={'conv2d_1_input:0': [1, 28, 28, 1]},
image_input_names='conv2d_1_input:0',
output_feature_names=['dense_2/Softmax:0'],
class_labels=[str(x) for x in range(10)],

意思是分別是:

  • 輸入'conv2d_1_input:0'的shape是[1, 28, 28, 1]
  • 輸入'conv2d_1_input:0'是一個圖片
  • 輸出是'dense_2/Softmax:0'
  • 輸出的標簽是['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

五、編寫代碼

以上準備工作都做完之后,代碼其實很簡單,創(chuàng)建一個名為convert.py的python腳本:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# file name: convert.py

import tfcoreml
import sys


def main():
    if len(sys.argv) == 1:
        print('please input the file path of a pb model')
        return

    tfcoreml.convert(tf_model_path=sys.argv[1],
                     mlmodel_path='mlmodel.mlmodel',
                     output_feature_names=['dense_2/Softmax:0'],
                     input_name_shape_dict={'conv2d_1_input:0': [1, 28, 28, 1]},
                     image_input_names='conv2d_1_input:0',
                     class_labels=[str(x) for x in range(10)])


if __name__ == '__main__':
    main()

然后直接執(zhí)行就OK了:

python3 convert.py <model.pb>

你會在同級目錄下,得到一個mlmodel.mlmodel的Core ML文件。

有時候,執(zhí)行可能會報錯:

NotImplementedError: Unsupported Ops of type: Shape,Pack

不用理會,再跑一次就可以了。

六、部署到iOS

你可以參考《二、使用Core ML加載.mlmodel模型文件》來完成模型的部署,你可以直接從我的GitHub獲取一個已經構建好的項目半成品:

git clone git@github.com:yangchenlarkin/CoreML.git

運行項目,點擊首頁的“手寫數字識別”,可以進入到識別界面。該ViewController位于項目中的DigitRecognition文件夾。打開DRViewController.m,你需要完成最后的方法:

- (NSString *)predict:(UIImage *)image {
    return @"TODO";
}

而如下兩個方法已經為你寫好了:


#pragma mark - predict

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {
    //...
}
- (UIImage*)scaleImage:(UIImage *)image size:(CGFloat)size {
    //...
}

七、分析與coding

在動手之前,我們看一下這個方法:


#pragma mark - predict

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {
    //...
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
                                          frameWidth,
                                          frameHeight,
                                          kCVPixelFormatType_OneComponent8,
                                          (__bridge CFDictionaryRef) options,
                                          &pxbuffer);
    //...
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();
    CGContextRef context = CGBitmapContextCreate(pxdata,
                                                 frameWidth,
                                                 frameHeight,
                                                 8,
                                                 CVPixelBufferGetBytesPerRow(pxbuffer),
                                                 colorSpace,
                                                 (CGBitmapInfo)kCGImageAlphaNone);
    //...
}

對比一下《二、使用Core ML加載.mlmodel模型文件》中的同名方法:


#pragma mark - predict

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image {
    //...
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
                                          frameWidth,
                                          frameHeight,
                                          kCVPixelFormatType_32ARGB,
                                          (__bridge CFDictionaryRef) options,
                                          &pxbuffer);
    //...
    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata,
                                                 frameWidth,
                                                 frameHeight,
                                                 8,
                                                 CVPixelBufferGetBytesPerRow(pxbuffer),
                                                 rgbColorSpace,
                                                (CGBitmapInfo)kCGImageAlphaNoneSkipFirst);
    //...
}

我們可以發(fā)現,我們在兩端代碼中使用的圖片格式是不同的,原因是,《二、使用Core ML加載.mlmodel模型文件》中我們使用的是彩色圖片,模型輸入的shape是[1, size, size, 3],而這里我們使用的是灰度圖,模型輸入的shape是[1, size, size, 1]。這是值得注意的一個地方。

下面直接給出實現代碼:


- (NSString *)predict:(UIImage *)image {
    UIImage *scaleImage = [self scaleImage:image size:28];
    CVPixelBufferRef buffer = [self pixelBufferFromCGImage:scaleImage.CGImage];
    mlmodel *m = [[mlmodel alloc] init];
    NSError *error = nil;
    mlmodelOutput *o = [m predictionFromConv2d_1_input__0:buffer error:&error];
    if (error) {
        NSLog(@"%@", error);
        return nil;
    }
    return o.classLabel;
}

運行之后,點擊首頁的“手寫數字識別”,在黑色區(qū)域內書寫數字,就可以在下方看到分類結果了。

為了縮短本文中腳本的運行時間,這個模型使用的層數較少,同時也沒有使用非常大量的訓練集,所以并不能很好的識別手寫數字,你大概需要寫的規(guī)范一點、把數字寫滿黑色區(qū)域的中間位置才能得到比較好的結果。

七、成果

最后,還是來看幾張成果圖吧:


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容