Core ML框架详细解析(二) —— 获取模型并集成到APP中

版本记录

版本号 时间
V1.0 2017.10.28

前言

目前世界上科技界的所有大佬一致认为人工智能是下一代科技革命,苹果作为科技界的巨头,当然也会紧跟新的科技革命的步伐,其中ios API 就新出了一个框架Core ML。ML是Machine Learning的缩写,也就是机器学习,这正是现在很火的一个技术,它也是人工智能最核心的内容。感兴趣的可以看我写的下面几篇。
1. Core ML框架详细解析(一) —— Core ML基本概览

获取模型

Core ML支持各种机器学习模型,包括神经网络,树组合,支持向量机和广义线性模型。 Core ML需要Core ML模型格式(具有.mlmodel文件扩展名的模型)。

苹果提供了几种流行的,开源models,它们已经是Core ML模式格式。 您可以下载这些模型并开始在您的应用程序中使用它们。 另外,各研究组和大学也发表了他们的模型和训练数据,这些数据可能并不是Core ML模型格式。 要使用这些模型,您需要转换它们,如Converting Trained Models to Core ML中所述。


将模型集成到APP中

下面我们就看一下将Core ML模型集成到您的APP中,传递给模型输入数据,并产生模型的预测数据。

1. 示例

下面我们看一个示例,此示例APP使用经过训练的模型MarsHabitatPricer.mlmodel来预测火星上的栖息地价格。

先看一下项目文档结构。

下面我们就简单的看一下代码。

1. Feature.swift
import UIKit

/**
     Represents the different features used by this model. Each feature
     (# of solar panels, # of greenhouses, or size) is an input value to the
     model. So each needs an appropriate `UIPicker` as well.
*/
enum Feature: Int {
    case solarPanels = 0, greenhouses, size
}
2. ViewController.swift
import UIKit
import CoreML

class ViewController: UIViewController {
    // MARK: - Properties
    
    let model = MarsHabitatPricer()
    
    /// Data source for the picker.
    let pickerDataSource = PickerDataSource()
    
    /// Formatter for the output.
    let priceFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.maximumFractionDigits = 0
        formatter.usesGroupingSeparator = true
        formatter.locale = Locale(identifier: "en_US")
        return formatter
    }()
    
    // MARK: - Outlets

    /// Label that will be updated with the predicted price.
    @IBOutlet weak var priceLabel: UILabel!

    /**
         The UI that users will use to select the number of solar panels,
         number of greenhouses, and acreage of the habitat.
    */
    @IBOutlet weak var pickerView: UIPickerView! {
        didSet {
            pickerView.delegate = self
            pickerView.dataSource = pickerDataSource

            let features: [Feature] = [.solarPanels, .greenhouses, .size]
            for feature in features {
                pickerView.selectRow(2, inComponent: feature.rawValue, animated: false)
            }
        }
    }
    
    // MARK: - View Life Cycle
    
    /// Updated the predicted price, when created.
    override func viewDidLoad() {
        super.viewDidLoad()
        updatePredictedPrice()
    }
    
    /**
         The main logic for the app, performing the integration with Core ML.
         First gather the values for input to the model. Then have the model generate
         a prediction with those inputs. Finally, present the predicted value to
         the user.
    */
    func updatePredictedPrice() {
        func selectedRow(for feature: Feature) -> Int {
            return pickerView.selectedRow(inComponent: feature.rawValue)
        }

        let solarPanels = pickerDataSource.value(for: selectedRow(for: .solarPanels), feature: .solarPanels)
        let greenhouses = pickerDataSource.value(for: selectedRow(for: .greenhouses), feature: .greenhouses)
        let size = pickerDataSource.value(for: selectedRow(for: .size), feature: .size)

        guard let marsHabitatPricerOutput = try? model.prediction(solarPanels: solarPanels, greenhouses: greenhouses, size: size) else {
            fatalError("Unexpected runtime error.")
        }

        let price = marsHabitatPricerOutput.price
        priceLabel.text = priceFormatter.string(for: price)
    }
}
3. ViewController+PickerViewDelegate.swift
import UIKit

extension ViewController: UIPickerViewDelegate {
    // MARK: - UIPickerViewDelegate
    
    /// When values are changed, update the predicted price.
    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        updatePredictedPrice()
    }
    
    /// Accessor for picker values.
    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        guard let feature = Feature(rawValue: component) else {
            fatalError("Invalid component \(component) found to represent a \(Feature.self). This should not happen based on the configuration set in the storyboard.")
        }

        return pickerDataSource.title(for: row, feature: feature)
    }
}
4. PickerViewDataSource.swift
import UIKit

/**
     The common data source for the three features and their picker values. Decouples
     the user interface and the feature specific values.
*/
class PickerDataSource: NSObject, UIPickerViewDelegate, UIPickerViewDataSource {
    // MARK: - Properties
    
    private let solarPanelsDataSource = SolarPanelDataSource()
    private let greenhousesDataSource = GreenhousesDataSource()
    private let sizeDataSource = SizeDataSource()
    
    // MARK: - Helpers
    
    /// Find the title for the given feature.
    func title(for row: Int, feature: Feature) -> String? {
        switch feature {
        case .solarPanels:  return solarPanelsDataSource.title(for: row)
        case .greenhouses:  return greenhousesDataSource.title(for: row)
        case .size:         return sizeDataSource.title(for: row)
        }
    }
    
    /// For the given feature, find the value for the given row.
    func value(for row: Int, feature: Feature) -> Double {
        let value: Double?
        
        switch feature {
        case .solarPanels:      value = solarPanelsDataSource.value(for: row)
        case .greenhouses:      value = greenhousesDataSource.value(for: row)
        case .size:             value = sizeDataSource.value(for: row)
        }
        
        return value!
    }
    
    // MARK: - UIPickerViewDataSource
    
    /// Hardcoded 3 items in the picker.
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 3
    }
    
    /// Find the count of each column of the picker.
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        switch Feature(rawValue: component)! {
        case .solarPanels:  return solarPanelsDataSource.values.count
        case .greenhouses:  return greenhousesDataSource.values.count
        case .size:         return sizeDataSource.values.count
        }
    }
}
5. SolarPanelDataSource.swift
import Foundation

struct SolarPanelDataSource {
    /// Possible values for solar panels in the habitat
    let values = [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5]
    
    func title(for index: Int) -> String? {
        guard index < values.count else { return nil }
        return String(values[index])
    }
    
    func value(for index: Int) -> Double? {
        guard index < values.count else { return nil }
        return Double(values[index])
    }
}
6. GreenhousesDataSource.swift
import Foundation

struct GreenhousesDataSource {
    /// Possible values for greenhouses in the habitat
    let values = [1, 2, 3, 4, 5]
    
    func title(for index: Int) -> String? {
        guard index < values.count else { return nil }
        return String(values[index])
    }
    
    func value(for index: Int) -> Double? {
        guard index < values.count else { return nil }
        return Double(values[index])
    }
}
7. SizeDataSource.swift
import Foundation

struct SizeDataSource {
    /// Helper formatter to represent large nubmers in the picker
    private static let numberFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.locale = .current
        formatter.numberStyle = .decimal
        formatter.usesGroupingSeparator = true
        return formatter
    }()

    /// Possible values for size of the habitat.
    let values = [
        750,
        1000,
        1500,
        2000,
        3000,
        4000,
        5000,
        10_000
    ]
    
    func title(for index: Int) -> String? {
        guard index < values.count else { return nil }
        return SizeDataSource.numberFormatter.string(from: NSNumber(value: values[index]))
    }
    
    func value(for index: Int) -> Double? {
        guard index < values.count else { return nil }
        return Double(values[index])
    }
}

具体效果如下所示。

2. Adding a Model to Your Xcode Project - 将模型加到您的Xcode Project中

通过将模型拖放到项目导航器中,将模型添加到Xcode项目。

您可以通过打开Xcode中的模型来查看有关模型的信息,包括模型类型及其预期输入和输出。 模型的投入是太阳能电池板和温室的数量,以及栖息地的面积(英亩)。 模型的产出是栖息地的预测价格。如下图所示。

3. Creating the Model in Code - 在代码中创建模型

Xcode还使用有关模型输入和输出的信息来自动生成模型的自定义编程接口,用于与代码中的模型进行交互。 对于MarsHabitatPricer.mlmodel,Xcode生成用于表示模型(MarsHabitatPricer),模型输入(MarsHabitatPricerInput)和模型输出(MarsHabitatPricerOutput)的接口。

使用生成的MarsHabitatPricer类的初始化器来创建模型:

// Listing 1

let model = MarsHabitatPricer()

4. Getting Input Values to Pass to the Model - 将输入值传递给模型

此示例app使用UIPickerView从用户获取模型的输入值:

// Listing 2

func selectedRow(for feature: Feature) -> Int {
    return pickerView.selectedRow(inComponent: feature.rawValue)
}

let solarPanels = pickerDataSource.value(for: selectedRow(for: .solarPanels), feature: .solarPanels)
let greenhouses = pickerDataSource.value(for: selectedRow(for: .greenhouses), feature: .greenhouses)
let size = pickerDataSource.value(for: selectedRow(for: .size), feature: .size)

5. Using the Model to Make Predictions - 使用模型做预测

MarsHabitatPricer类有一个生成的prediction(solarPanels:greenhouses:size :)方法,用于从模型的输入值预测价格 - 在这种情况下,太阳能电池板的数量,温室的数量和栖息地的大小( 英亩)。 该方法的结果是MarsHabitatPricerOutput实例marsHabitatPricerOutput

// Listing 3

guard let marsHabitatPricerOutput = try? model.prediction(solarPanels: solarPanels, greenhouses: greenhouses, size: size) else {
    fatalError("Unexpected runtime error.")
}

获取marsHabitatPricerOutputprice属性,得到一个预测的价格并显示在app UI中。

// Listing 4

let price = marsHabitatPricerOutput.price
priceLabel.text = priceFormatter.string(for: price)

注意:生成预测(solarPanels:greenhouses:size :)的方法可能会发生错误。 在使用Core ML时遇到的最常见的错误类型发生在您传递给该方法的输入数据类型与模型所期望的输入类型不匹配时,例如,格式错误的图像。 在此示例应用程序中,输入的类型为Double。 任何类型的不匹配在编译时被捕获,如果发生错误,示例应用程序会引发致命错误。

6. Building and Running a Core ML App - 构建并运行一个Core ML App

Xcode将Core ML模型编译经过优化以在设备上运行的资源。 这个经过优化的模型包含在您的应用程序包中,并且是在应用程序在设备上运行时进行预测的。

后记

未完,待续~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容