iOS:使用 Core ML 进行机器学习

Core ML 是一个使各种机器学习和统计模型在 macOS 和 iOS 上原生支持的令人兴奋的新框架。它帮助开发者将已经成型的统计和机器学习模型整合到应用之中。这个模块是基于苹果公司的底层机器学习基本元件,有一些是在 WWDC2016中声明的。

Core ML在三个方面帮助开发者:

  1. Core ML 支持多种机器学习模型,从类神经网络模型到广义线性模型,Core ML 中都有。
  2. Core ML 使添加机器学习模型到开发者应用之更容易。这个目标是通过 coremltools实现的,Core ML Tools 是一个 专门帮助生成 Xcode 可用的 .mlmodel 文件的 Python 包。
  3. Core ML 可以自动为开发者的模型提供自定义编程接口来提供可供调用的 API。这项特性帮助开发者在 Xcode 中直接使用模型,就像模型是一个本地类型。

在阅读以上内容之后,我们也就可以得到一个关于 Core ML 声明周期的基本认识。开发者可以用 Python创建一个模型,生成一个 .mlmodel 文件,添加该模型到 Xcode 中并且通过 Core ML 在一个设备上使用该模型。现在咱们开始吧!

预测房价

Core ML 支持许多种类的模型。为了支持这些模型,Core ML 希望开发者在一定的前提条件下创建模型。这可以帮助开发者划定 coremltools 包需要生成 .mlmodel 文件的支持范围。

在这款应用中,我们会使用机器学习来创建一个线性回归模型来预测房价。机器学习是一个基于专门设计为普适的数据分析的 NumPy,SciPy 和 matplotlib 的 Python 包。我们预测房价的回归模型会基于两个预测变量。

  1. 犯罪率
  2. 房屋数量

顺便说一句:我们的目标不是创造一个非常精确的模型。而是通过创建一个比较合理的模型来展示在应用之中。建模过程是比较困难的,并且现在进行比较深入的模型选择和性能调整并不是一个特别明智的选择。

这次的模型我们将要使用以下模式:𝑦 = α + β₁𝑥₁ + β₂𝑥₂ + 𝑒

这里的𝑦是我们要预测的因变量:房价。x1是一个自变量来代表犯罪率。x2是一个自变量来代表房屋数目。e 是一个代表已知的数据集合中的记录中的模型预测值和真实值之间的误差的误差项。我们不会真正的去将这个参数建模,但是包含这个误差项是为了维护模型规格的完整性。

α, β₁, 和 β₂ 是系数(从技术上说,α 被称为拦截数)建模过程会估计这些系数来帮助我们 生成预测。顺便提一句,就是这些参数使我们的模型呈现线性。y 是组合了参数和变量的线性计算结果。

关于回归的基础知识

即使你不知道回归是什么,也没有问题。只需要知道它是统计和机器学习的基础模型。回归的主要目标是在数据中画一条线。为了这个目标,模型要尝试估计划线所必要的参数。
还记得这个方程式 y = mx + b? y 是Y 轴,m 是斜率,X 是 X 轴,b 是常量。回忆这些概念能够帮助你更好理解线性回归是在做什么。

区里来说,设想以下图形:

yAndX.png

这个表格展示了我们通过正态分布随机产生的两个变量 X 和 Y。每个有100个样本。X 是一个分布平均值为0 标准差为1:x ~ N(0, 1)。Y 是用 X 的平均值以1为标准差生成的:y ~ N(x, 1)。这样,每个 Y 都是由平均值为 X 的正态分布样本生成的。举例来说,第一个 Y 的值是由平均值为第一个 X 的值的正态分布样本得来的。

这样生成 Y 帮助在两个变量之间简历联系。
接下来,我们使用一个两个变量的线性回归来估计常量和斜率来定义 Y 和 X 之间的关系。上图中表格上画出的线表现除了最后模型的结果。这次,该模型非常契合数据,就像我们所期望的 Y 是一个 X 加上一些有来自正态分布样本的自由因子定义的函数。

所以,为了避免被某种程度上的减少,线性回归都是关于划线。如果你想要了解更多关于划线知识,请看我们之前关于机器学习的文章。

数据:波士顿房屋数据集

此次的数据来自 Harrison 和 Rubinfeld 的一项已经公开的研究.该数据包含了506份作者收集的关于波士顿房价的各种影响因素的观察值。这包含了如下的相关预测:


     CRIM    ZN  INDUS  CHAS    NOX     RM   AGE     DIS  RAD    TAX \

0  0.00632  18.0   2.31   0.0  0.538  6.575  65.2  4.0900  1.0  296.0

1  0.02731   0.0   7.07   0.0  0.469  6.421  78.9  4.9671  2.0  242.0

2  0.02729   0.0   7.07   0.0  0.469  7.185  61.1  4.9671  2.0  242.0

3  0.03237   0.0   2.18   0.0  0.458  6.998  45.8  6.0622  3.0  222.0

4  0.06905   0.0   2.18   0.0  0.458  7.147  54.2  6.0622  3.0  222.0

   PTRATIO       B  LSTAT

0     15.3  396.90   4.98

1     17.8  396.90   9.14

2     17.8  392.83   4.03

3     18.7  394.63   2.94

4     18.7  396.90   5.33

因变量如下:


   MEDV

0  24.0

1  21.6

2  34.7

3  34.4

4  36.2

这个因变量展示出 $1,000 这一单元的房价的中间值。这些数据来自1970年左右,所以在房价后面加几个零也还是显得比较低。
充分了解数据类型之后,现在该写回归模型了。

Python 模型

我们使用 Python 2.7 来定义回归模型。这是 coremltools 需要的。以下是代码:


import coremltools

from sklearn import datasets, linear_model

import pandas as pd

from sklearn.externals import joblib

# Load data

boston = datasets.load_boston()

boston_df = pd.DataFrame(boston.data)

boston_df.columns = boston.feature_names

# Define model

X = boston_df.drop(boston_df.columns[[1,2,3,4,6,7,8,9,10,11,12]], axis=1)

Y = boston.target

lm = linear_model.LinearRegression()

lm.fit(X, Y)

# coefficients

lm.intercept_

lm.coef_

# Convert model to Core ML 

coreml_model = coremltools.converters.sklearn.convert(lm, input_features=["crime", "rooms"], output_feature_names="price")

# Save Core ML Model

coreml_model.save("BostonPricer.mlmodel")

我们不需要对 Python 非常精通才能理解以上代码。顶上的部分是导入必须的包。
接下来,我们导入波士顿房价数据,并且进行适度处理来使我们的模型更好地获取自变量和因变量。使用以上变量,我们可以创造出线性规划模型:lm = linear_model.LinearRegression().我们创建了模型之后,就可以使用数据进行适配,适配数据可以生产帮助我们预测房价的系数。.mlmodel 文件会允许我满使用系数来做出预测。
最后两行的 Python 代码,是转换线性模型到 .mlmodel 格式并且保存到文件中。
coremltools.converters.sklearn.convert(lm, [“crime”, “rooms”], “price”)完成装换操作。coremltools 提供一系列可供使用的转换器来转换模型到 .mlmodel 格式。详情参考.下载该网页文档来了解更多关于转换的消息。
运行python NAME_OF_FILE.py 来生成 BostonPricer.mlmodel。举例来说,我的文件名是“pricer_example.py”,所以 我们运行 python pricer_example.py。确保运行程序在 Python 脚本文件在相同目录中。我们的 .mlmodel 文件会创建在相同的位置。

一个小巧智能的 iOS 应用程序

我们的应用程序是一个简单的单视图应用程序。该应用使用了简答的用户图形接口来根据两个变量生成房价预测值。这个程序有一个有两个组件的UIPickerView,一个是犯罪率另一个是房屋个数。并且有一个标签来显示结果。

显示如下:

singleView.png

我们不会使用 storyboard 来构建视图。如果对该项目好奇可以在这里找到。

添加BostonPricer.mlmodel 到 Xcode 项目中。

将 .mlmodel 文件 拖拽进项目中,这样可以方便查看模型文件。你可以通过点击BostonPricer.mlmodel 来观察模型文件。

这样添加文件和会自动添加生成名为:BostonPricer, BostonPricerInput and BostonPricerOutput的类。

  • BostonPricer 是用来创建模型实例的。它提供了一套接口来产生预测值。
  • BostonPricerInput 是一个可以创建一个输入数据源来传给模型实例 BostonPricer 的类。模型会使用这些信息来生成预测。我们可以这样工作,但是却没有必要。BostonPricer 提供了一个方法来从符合你的输入变量的数据类型中生成预测值。
  • BostonPricerOutput 是一个可以规范基于一定输入的模型的类。我们可以使用这些通过预测模型产品来生成各种类型的结果。

某些类定义的实现细节对我们是隐藏的,但是当我们对BostonPricer使用‘command-click’。一个弹出视图会出现并且会给出“Jump to Definition”的选项。点击“Jump to Definition”,这样Xcode 会跳到BostonPricer 的定义实现文件中。另一个定义实现类也在这个文件中。如下:


@objc class BostonPricer:NSObject {

    var model: MLModel

    init(contentsOf url: URL) throws {

        self.model = try MLModel(contentsOf: url)

    }

    convenience override init() {

        let bundle = Bundle(for: BostonPricer.self)

        let assetPath = bundle.url(forResource: "BostonPricer", withExtension:"mlmodelc")

        try! self.init(contentsOf: assetPath!)

    }

    func prediction(input: BostonPricerInput) throws -> BostonPricerOutput {

        let outFeatures = try model.prediction(from: input)

        let result = BostonPricerOutput(price: outFeatures.featureValue(for: "price")!.doubleValue)

        return result

    }

    func prediction(crime: Double, rooms: Double) throws -> BostonPricerOutput {

        let input_ = BostonPricerInput(crime: crime, rooms: rooms)

        return try self.prediction(input: input_)

    }

}

BostonPricer 是一个提供 MLModel 接口的 NSObject 的子类。它提供了两个方法,每个都是可以从某些输入值预测因变量。第一个方法是:BostonPricerInput prediction(input:)这是 CoreML 为我们提供的另一个方法。正如上面所说的,我们不会在本文中使用这种类型。
第二种方法使用了两个自变量的值:prediction(crime:rooms:)。我们会使用第二种方法来生成预测值。让我们看看怎么工作的:

使用 BostonPricer

我们使用 BostonPricer 利用犯罪率和房屋中的房子个数来生成预测房价的值。我们需要创建一个实例来在工程代码中使用该模型。我们的例子中添加了模型作为属性:
UIViewController: let model = BostonPricer()。
(接下来的代码片段都在我们的ViewController.swift 中)
当 Picker 设置好以后,我们选择一些随机的行来标识一些输入值从而计算预测值。

@IBOutlet var picker: UIPickerView! {

    didSet {

        picker.selectRow(4, inComponent: Predictor.crime.rawValue, animated: false)

        picker.selectRow(3, inComponent: Predictor.rooms.rawValue, animated: false)

    }

}

为了在程序运行开始时就显示预测值,我们要调用generatePrediction()方法在viewDidLoad()。

override func viewDidLoad() {

    super.viewDidLoad()

    generatePrediction()

}

设置好以后,我们可以在UIPickerView更新选择值之后生成预测值。其中的两个中心方法如下:


func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

    generatePrediction()

}

fileprivate func generatePrediction() {

    let selectedCrimeRow = picker.selectedRow(inComponent: Predictor.crime.rawValue)

    guard let crime = crimeDataSource.value(for: selectedCrimeRow) else {

        return

    }

    let selectedRoomRow = picker.selectedRow(inComponent: Predictor.rooms.rawValue)

    guard let rooms = roomsDataSource.value(for: selectedRoomRow) else {

        return

    }

    guard let modelOutput = try? model.prediction(crime: crime, rooms: rooms) else {

        fatalError("Something went wrong with generating the model output.")

    }

    // Estimated price is in $1k increments (Data is from 1970s...)

    priceLabel.text = priceFormatter.string(for: modelOutput.price)

}

代码中的第一个方法是:pickerView(_:didSelectRow:inComponent:),该方法定义在UIPickerViewDelegate 协议中。我们使用这个方法来确定新的值。

第二个方法是generatePrection(),其中包含了从我们的 .mlmodel 文件生成预测值的所有业务逻辑。将这些逻辑抽象到新的方法中使得在UIViewController的viewDidLoad()方法调用是使用预测值跟新标签上的值更容易。就像上面看到的,这样我们可以在viewDidLoad()调用generatePrediction()。
generatePrediction()根据选择器的状态来决定现在组件中现在选择的值。我们用这些信息来获取crimeDataSource和roomsDataSource中相关行的值。例如,犯罪率关联的行数3 是0.03。类似的,房间数在第三行相关的值是3。

我们传值到模型中来生成预测值。代码没有执行的使用try? model.prediction(crime: crime, rooms: rooms)。

这里简单说一下使用 try?转换到可选类型来避免错误。我们可以对错误处理的更好,但是这不是本篇文章的重点。这也不是我们示例程序中的大问题。当我们给prediction(crime:rooms:)方法传值时,编译器会捕捉到任何不匹配数据。这个方法理论上是可以抛弃的,举例来说,当我们需要一张图片,但是图片被一种无法识别的格式传递过来。

从现在的目的来看,从我们的输入值来生成预测值是十分必要的。
我们生产预测值之后,要跟新标签显示的值。


pricer_estimator.gif

也许你会想不通为何预测的价格会是负值。问题在于超出范围的模型数据匹配。问题的关键是我们的模型估计的常量是负数。这表示我们的模型是不足的,并没有包含所有自变量参数来预测房价。如果我们想要优化模型,我们必须深入挖掘数据并且辨别出是怎么发生的。

有趣的是,犯罪率并没有对房价产生很大的影响。这一点可能是真实的发现,也可能是我们模型中的一些缺陷。如果这是一个真的应用程序,它可能会需要包含更多的调查。然而,屋子中的房间数对房价产生了很大的影响,这就像我们所想的。

最后说一句

这篇文章说明了 iOS 应用程序中集成 CoreML 的过程。使用 CoreML,我们可以使用模型的又是来在应用程序中直接使用我们创建的模型。CoreML 使创建机器学习和统计模型更加方便。

尽管如此,还是要做一个警告。统计和机器学习并不是简单的应用程序接口。他们是整块的领域就像艺术和科学。开发选择一个有益的模型需要更多的实践和学习。我们的应用程序在这一点上充分显示:很明显的是我们的模型还有很多缺陷来预测房价。

CoreML 可以是一个包含设计和开发人员的应用程序团队和数据科学界比较最好的合作方式。数据学家会研究成吨的模型来确保传递出正确的结果。应用程序团队可以集中精力来优化将模型集成到应用程序中的体验。

也许有些问题是更容易定义的,并且匹配模型也已经是充分测试的。这样可以更加容易的集成到应用程序中,而我们不用自己对模型进行优化。举例来说就包含了很多分类的问题:a)面部识别, b)图像识别 , c)手写文字识别 等等。

需要特别注意的地方就是要确定我们所选用的模型要比较契合我们开发的项目实例。这样在测试时可以确认模型的值是否就是我们所期望的那样。然而这并不说明 CoreML 可以使所有的模型变得即插即用,但是它确实让开发者更容易的进行应用程序的开发。

了解更多关于为何要提前为应用程序适配 iOS 11,或者下载图书来进一步了解这些改变是如何影响你的生意的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容