将训练好的 Keras 图像分类模型转换为 coreml 并集成到 iOS11

Posted

技术标签:

【中文标题】将训练好的 Keras 图像分类模型转换为 coreml 并集成到 iOS11【英文标题】:Convert trained Keras image classification model to coreml and integrate in iOS11 【发布时间】:2017-12-15 14:03:30 【问题描述】:

使用https://blog.keras.io/building-powerful-image-classification-models-using-very-little-data.html 上的教程,我训练了一个 Keras 模型来识别猫和狗之间的区别。

'''
Directory structure:

data/
    train/
        dogs/
            dog001.jpg
            dog002.jpg
            ...
        cats/
            cat001.jpg
            cat002.jpg
            ...
    validation/
        dogs/
            dog001.jpg
            dog002.jpg
            ...
        cats/
            cat001.jpg
            cat002.jpg
            ...
'''    
from keras.preprocessing.image import ImageDataGenerator
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras import backend as K
from PIL import Image
import numpy as np

# dimensions of our images.
img_width, img_height = 150, 150

train_data_dir = 'data/train'
validation_data_dir = 'data/validation'
nb_train_samples = 2000
nb_validation_samples = 800
epochs = 50
batch_size = 16

if K.image_data_format() == 'channels_first':
    input_shape = (3, img_width, img_height)
else:
    input_shape = (img_width, img_height, 3)

model = Sequential()
model.add(Conv2D(32, (3, 3), input_shape=input_shape))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(32, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Conv2D(64, (3, 3)))
model.add(Activation('relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))

model.add(Flatten())
model.add(Dense(64))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(1))
model.add(Activation('sigmoid'))

model.compile(loss='binary_crossentropy',
              optimizer='rmsprop',
              metrics=['accuracy'])

# this is the augmentation configuration we will use for training
train_datagen = ImageDataGenerator(
    rescale=1. / 255,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True)

# this is the augmentation configuration we will use for testing:
# only rescaling
test_datagen = ImageDataGenerator(rescale=1. / 255)

train_generator = train_datagen.flow_from_directory(
    train_data_dir,
    target_size=(img_width, img_height),
    batch_size=batch_size,
    class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
    validation_data_dir,
    target_size=(img_width, img_height),
    batch_size=batch_size,
    class_mode='binary')

model.fit_generator(
    train_generator,
    steps_per_epoch=nb_train_samples // batch_size,
    epochs=epochs,
    validation_data=validation_generator,
    validation_steps=nb_validation_samples // batch_size)

model.save('first_try.h5')

使用 coremltools documentation 作为指导,我尝试将我的模型转换为 coreml 格式:

import coremltools
import h5py
coreml_model = coremltools.converters.keras.convert('first_try.h5',input_names='image',output_names='class',image_input_names = 'image',class_labels = ['cat', 'dog'], is_bgr=True)
coreml_model.save('cats_dogs.mlmodel')

当我将模型导入 XCode 并使用以下代码运行它时(适用于 Apple 网站上的 resnet50 和 inceptionv3 模型),代码 sn-p guard let prediction = try? model.prediction(image: pixelBuffer!) else print("error!") return 打印“错误!”并且永远无法到达textView.text = "I think this is a \(String(describing: prediction.classLabel))." 代码 sn-p。

    import UIKit
    import Vision
    import CoreML

    class ViewController: UIViewController, UINavigationControllerDelegate 

        //MARK: - Properties


        @IBOutlet weak var imageView: UIImageView!
        @IBOutlet weak var textView: UITextView!

        let imagePicker = UIImagePickerController()

        //MARK: - ViewController

        override func viewDidLoad() 
            super .viewDidLoad()
            self.imagePicker.delegate = self
        

        @IBAction func openImagePicker(_ sender: Any) 
            imagePicker.allowsEditing = false
            imagePicker.sourceType = .photoLibrary
            present(imagePicker, animated: true, completion: nil)
        

        @IBAction func camera(_ sender: Any) 

            if !UIImagePickerController.isSourceTypeAvailable(.camera) 
                return
            

            let cameraPicker = UIImagePickerController()
            cameraPicker.delegate = self
            cameraPicker.sourceType = .camera
            cameraPicker.allowsEditing = false

            present(cameraPicker, animated: true)
        

    

    extension ViewController: UIImagePickerControllerDelegate 
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) 
            dismiss(animated: true, completion: nil)
        

        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) 
            picker.dismiss(animated: true)
            textView.text = "Analyzing Image..."
            guard let image = info["UIImagePickerControllerOriginalImage"] as? UIImage else 
                return
            

            UIGraphicsBeginImageContextWithOptions(CGSize(width: 150, height: 150), true, 2.0)
            image.draw(in: CGRect(x: 0, y: 0, width: 150, height: 150))
            let newImage = UIGraphicsGetImageFromCurrentImageContext()!
            UIGraphicsEndImageContext()

            let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
            var pixelBuffer : CVPixelBuffer?
            let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(newImage.size.width), Int(newImage.size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
            guard (status == kCVReturnSuccess) else 
                return
            

            CVPixelBufferLockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
            let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer!)

            let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
            let context = CGContext(data: pixelData, width: Int(newImage.size.width), height: Int(newImage.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) //3

            context?.translateBy(x: 0, y: newImage.size.height)
            context?.scaleBy(x: 1.0, y: -1.0)

            UIGraphicsPushContext(context!)
            newImage.draw(in: CGRect(x: 0, y: 0, width: newImage.size.width, height: newImage.size.height))
            UIGraphicsPopContext()
            CVPixelBufferUnlockBaseAddress(pixelBuffer!, CVPixelBufferLockFlags(rawValue: 0))
            imageView.image = newImage

guard let prediction = try? model.prediction(image: pixelBuffer!) else 
        print("error!")
        return
    
    textView.text = "I think this is a \(String(describing: prediction.classLabel))."
        
    

XCode Error Implementing Custom Keras-CoreML model

我已经广泛搜索了 *** 来解决这个问题。非常感谢您帮助解决此问题!

================================================ ============================== 编辑#1:

使用“print(try!model.prediction(image:pixelBuffer!) as Any)”我打印了以下错误:

2017-07-13 15:33:49.034967-0400cats_dogs[60441:1198094] 致命错误:“尝试!”表达式意外引发错误:错误域 = com.apple.CoreML 代码 = 0“层 '输出' 的尺寸与类标签的数量不同。” UserInfo=NSLocalizedDescription=“输出”层的维度与类标签的数量不同。:

不确定“层'输出'的尺寸与类标签的数量不同”是什么意思。

================================================ ==============================

编辑#2:

这是我用来将模型转换为 .mlmodel 格式的代码

import coremltools   
import h5py   
output_labels = ['cat','dog']   
coreml_model = coremltools.converters.keras.convert('first_try.h5',input_names='image',image_input_names = 'image',class_labels = output_labels, is_bgr=False)   
coreml_model.author = ''   
coreml_model.short_description = 'Model to classify images as either cats or dogs'   
coreml_model.input_description['image'] = 'Image of a cat or dog'   
print coreml_model   
coreml_model.save('cats_dogs.mlmodel') 

这是终端输出:

0 : conv2d_1_input, <keras.engine.topology.InputLayer object at 0x1194c6c50>
1 : conv2d_1, <keras.layers.convolutional.Conv2D object at 0x1194c6c90>
2 : activation_1, <keras.layers.core.Activation object at 0x119515b90>
3 : max_pooling2d_1, <keras.layers.pooling.MaxPooling2D object at 0x119501e50>
4 : conv2d_2, <keras.layers.convolutional.Conv2D object at 0x119520cd0>
5 : activation_2, <keras.layers.core.Activation object at 0x1194e8150>
6 : max_pooling2d_2, <keras.layers.pooling.MaxPooling2D object at 0x11955cc50>
7 : conv2d_3, <keras.layers.convolutional.Conv2D object at 0x11955ce50>
8 : activation_3, <keras.layers.core.Activation object at 0x11954d9d0>
9 : max_pooling2d_3, <keras.layers.pooling.MaxPooling2D object at 0x119594cd0>
10 : flatten_1, <keras.layers.core.Flatten object at 0x1195a08d0>
11 : dense_1, <keras.layers.core.Dense object at 0x119579f10>
12 : activation_4, <keras.layers.core.Activation object at 0x1195c94d0>
13 : dense_2, <keras.layers.core.Dense object at 0x1195ea450>
14 : activation_5, <keras.layers.core.Activation object at 0x119614b10>

input 
  name: "image"
  shortDescription: "Image of a cat or dog"
  type 
    imageType 
      width: 150
      height: 150
      colorSpace: RGB
    
  

output 
  name: "output1"
  type 
    dictionaryType 
      stringKeyType 
      
    
  

output 
  name: "classLabel"
  type 
    stringType 
    
  

predictedFeatureName: "classLabel"
predictedProbabilitiesName: "output1"
metadata 
  shortDescription: "Model to classify images as either cats or dogs"
  author: ""

【问题讨论】:

【参考方案1】:

不确定“层'输出'的尺寸与类标签的数量不同”是什么意思。

这意味着您的模型的最后一层与您的类标签的维度不同(我假设是维度 2)。我建议删除此参数:

class_labels = output_labels

从您的模型转换中,看看它是否解决了问题

【讨论】:

以上是关于将训练好的 Keras 图像分类模型转换为 coreml 并集成到 iOS11的主要内容,如果未能解决你的问题,请参考以下文章

将 UIImage 转换为 Keras 模型的 MLMultiArray

错误 "IndexError: 如何在Keras中使用训练好的模型预测输入图像?

.h5 keras 模型到 coreml 的分类转换在 IOS 中不起作用

在 Keras 中训练多类图像分类器

R语言基于Keras的小数据集深度学习图像分类

如何将训练有素的 Tensorflow 模型转换为 Keras?