iOS 11:机器学习人人有份

AllanHou· 2017-07-05
本文来自 InfoQ ,作者 AllanHou

WWDC 2017 使一件事情变得非常清楚,那就是:Apple 正在全力以赴地支持「设备上的机器学习」了。

他们希望 App 的开发者们能够尽可能的简单的加入他们的行列中。

Apple 去年发布了可以用于创建基本的卷积神经网的 Metal CNN 和 BNNS 框架。今年,Metal 得到了进一步扩展,增加了一个全新的计算机视觉框架,以及 Core ML:一个能够轻松地将机器学习集成到 App 中的工具包。

000.png

Core ML framework

在这片文章中,我将就 iOS 11 和 macOS 10.13 中这些新推出的机器学习的内容,分享我自己的一些想法和经验。

Core ML 

Core ML 在 WWDC 上获得了极大的关注度,原因很简单:大部分开发者希望能够在他们的 App 中使用这个框架。

Core ML 的 API 非常简单。你只能用它做这些事情:

  1. 加载一个训练好的模型

  2. 做出预测

  3. 收益!!!

这看起来好像很有限,但实际上你一般只会在 App 中加载模型和做出预测这两件事。

在 Core ML 之前,加载训练好的模型是非常困难的 —— 实际上,我写过一个框架来减轻这种痛苦。所以现在我对这一个简单的两步过程感到非常高兴。

模型被包含在了一个 .mlmodel 的文件中。这是一种新的开源文件格式,用于描述模型中的 layer、输入输出、标签,以及需要在数据上产生的任何预处理过程。它还包括了所有的学习参数(权重和偏置)。

使用模型所需的一切都在这一个文件里面了。

你只需要将 mlmodel 文件放入你的项目中,Xcode 将会自动生成一个 Swift 或 Objective-C 的包装类,使你能简单的使用这个模型。

举个例子,如果你把文件 ResNet50.mlmodel 添加到你的 Xcode 项目中,那么你就可以这么写来实例化这个模型:

let model = ResNet50()

然后做出预测:

let pixelBuffer: CVPixelBuffer = /* your image */if let prediction = try? model.prediction(image: pixelBuffer) {
  print(prediction.classLabel)
}

这差不多就是所有要写的东西了。你不需要编写任何代码来加载模型,或者将其输出转换成可以从 Swift 直接使用的内容 —— 这一切都将由 Core ML 和 Xcode 来处理。

注意: 要了解背后发生了什么,可以在 Project Navigator 里选择 mlmodel 文件,然后点击 Swift generated source 右边的箭头按钮,就能够查看生成的帮助代码了。

Core ML 将决定自己到底是在 CPU 上运行还是 GPU 上运行。这使得它能够充分的利用可以用的资源。Core ML 甚至可以将模型分割成仅在 GPU 上执行的部分(需要大量计算的任务)以及 CPU 上的其他部分(需要大量内存的任务)。

Core ML 使用 CPU 的能力对于我们开发者来说另一个很大的好处是:你可以从 iOS 模拟器运行它,从而运行那些对于 Metal 来说做不到,同时在单元测试中也不太好的任务。

Core ML 支持什么模型?

上面的 ResNet50 例子展示的是一个图像分类器,但是 Core ML 可以处理几种不同类型的模型,如:

  • 支持向量机 SVM

  • 诸如随机森林和提升树的决策树集成

  • 线性回归和 logistic 回归

  • 前馈神经网、卷积神经网、递归神经网

所有这些模型都可以用于回归问题和分类问题。此外,你的模型可以包含这些典型的机器学习预处理操作,例如独热编码(one-hot encoding)、特征缩放(feature scaling)、缺失值处理等等。

Apple 提供了很多已经训练好的模型可供下载,例如 Inception v3、ResNet50 和 VGG16 等,但你也可以使用 Core ML Tools 这个 Python 库来转换自己的模型。

目前,你可以转换使用 Keras、Caffe、scikit-learn、XGBoost 和 libSVM 训练的模型。转换工具只会支持具体指定的版本,比如 Keras 支持 1.2.2 但不支持 2.0。辛运的是,该工具是开源的,所以毫无疑问它将来会支持更多的训练工具包。

如果这些都不行,你还是可以随时编写自己的转换器。mlmodel 文件格式是开源且可以直接使用的(由 Apple 制定发布的一种 protobuf 格式)

  局限   

如果你想在你的 App 上马上运行一个模型, Core ML 很不错。然而使用这样一个简单的 API 一定会有一些限制。

  • 仅支持有监督学习的模型,无监督学习和增强学习都是不行的。(不过有一个「通用」的神经网络类型支持,因此你可以使用它)

  • 设备上不能进行训练。你需要使用离线工具包来进行训练,然后将它们转换到 Core ML 格式。

  • 如果 Core ML 不支持某种类型的 layer,那么你就不能使用它。在这一点上,你不能使用自己的 kernel 来扩展 Core ML。在使用 TensorFlow 这样的工具来构建通用计算图模型时,mlmodel 文件格式可能就不那么灵活了。

  • Core ML 转换工具只支持特定版本的数量有限的训练工具。例如,如果你在 TensorFLow 中训练了一个模型,则无法使用此工具,你必须编写自己的转换脚本。正如我刚才提到的:如果你的 TensorFlow 模型具有一些 mlmodel 不支持的特性,那么你就不能在 Core ML 上使用你的模型。

  • 你不能查看中间层的输出,只能获得最后一层网络的预测值。

  • 我感觉下载模型更新会造成一些问题,如果你不想每次重新训练模型的时候都重写一个新版本的 App,那么 Core ML 不适合你。

  • Core ML 对外屏蔽了它是运行在 CPU 上还是 GPU 上的细节 —— 这很方便 —— 但你必须相信它对你的 App 能做出正确的事情。即便你真的需要,你也不能强迫 Core ML 运行在 GPU 上。

如果你能够忍受这些限制,那么 Core ML 对你来说就是正确的选择。

否则的话,如果你想要完全的控制权,那么你必须使用 Metal Performance Shader 或 Accelerate 框架 —— 甚至一起使用 —— 来驱动你的模型了!

当然,真正的黑魔法不是 Core ML,而是你的模型。如果你连模型都没有,Core ML 是没有用的。而设计和训练一个模型就是机器学习的难点所在……

一个快速示例程序 

我写了一个使用了 Core ML 的简单的示例项目,和往常一样,你可以在 GitHub 上找到源码。

2.png

The demo app in action

这个示例程序使用了 MobileNet 架构来分类图片中的猫。

最初这个模型是用 Caffe 训练得出的。我花了一点时间来搞清楚如何将它转换到一个 mlmodel 文件,但是一旦我有了这个转换好的模型,便很容易集成到 App 中了(转换脚本包含在 GitHub 中)。

虽然这个 App 不是很有趣 —— 它只输出了一张静态图片的前五个预测值 —— 但却展示了使用 Core ML 是多么的简单。几行代码就够了。

注意: 示例程序在模拟器上工作正常,但是设备上运行就会崩溃。继续阅读来看看为什么会发生这种情况 ;-)

当然,我想知道发生了什么事情。事实证明 mlmodel 实际上被编译进应用程序 bundle 的 mlmodelc 文件夹中了。这个文件夹里包含了一堆不同的文件,一些二进制文件,一些 JSON文件。所以你你可以看到 Core ML 是如何将 mlmodel 在实际部署到应用中之前进行转换的。

例如,MobileNet Caffe 模型使用了批量归一化(Batch Normalization)层,我验证了这些转换也存在于 mlmodel 文件中。但是在编译的 mlmodelc 中,这些批量归一化 layer 似乎就被移除了。这是个好消息:Core ML 优化了该模型。

尽管如此,它似乎可以更好的优化该模型的结构,因为 mlmodelc 仍然包含一些不必要的 scaling layer。

当然,我们还处在 iOS 11 beta 1 的版本,Core ML 可能还会改进。也就是说,在应用到 Core ML 之前,还是值得对模型进一步优化的 —— 例如,通过「folding」操作对 layer 进行批量归一化(Batch Normalization) —— 但这是你必须对你的特性模型进行测量和比较的东西。

还有其他一些你必须检查的:你的模型是否在 CPU 和 GPU 上运行相同。我提到 Core ML 将选择是否在 CPU 上运行模型(使用 Accelerate 框架)或 GPU(使用 Metal )。事实证明,这两个实现可能会有所不同 —— 所以你两个都需要测试!

例如,MobileNet 使用所谓的「depthwise」卷积层。原始模型在 Caffe 中进行训练,Caffe 通过使正常卷积的 groups 属性等于输出通道的数量来支持 depthwise 卷积。所得到的 MobileNet.mlmodel 文件也一样。这在 iOS 模拟器中工作正常,但它在设备上就会崩溃!

发生这一切的原因是:模拟器使用的是 Accelerate 框架,但是该设备上使用的却是 Metal Performance Shaders。由于 Metal 对数据进行编码方式的特殊性, MPSCNNConvolution 内核限制了:不能使 groups 数等于输出通道的数量。噢嚯!

我向 Apple 提交了一个 bug,但是我想说的是:模型能在模拟器上运行正常并不意味着它在设备上运行正常。一定要测试!

有多快?

我没有办法测试 Core ML 的速度,因为我的全新 10.5 寸 iPad Pro 下个星期才能到(呵呵)。

我感兴趣的是我自己写的 Forge 库和 Core ML (考虑到我们都是一个早期的测试版)之间运行 MobileNets 之间的性能差异。

敬请关注!当我有数据可以分享时,我会更新这一节内容。

Vision 

下一个要讨论的事情就是全新的 Vision 框架。

你可能已经从它的名字中猜到了,Vision 可以让你执行计算机视觉任务。在以前你可能会使用 OpenCV,但现在 iOS 有自己的 API 了

3.jpg

Happy people with square faces

Vision 可以执行的任务有以下几种:

  • 在图像中寻找人脸。然后对每个脸给出一个矩形框。

  • 寻找面部的详细特征,比如眼睛和嘴巴的位置,头部的形状等等。

  • 寻找矩形形状的图像,比如路标。

  • 追踪视频中移动的对象。

  • 确定地平线的角度。

  • 转换两个图像,使其内容对齐。这对于拼接照片非常有用。

  • 检测包含文本的图像中的区域。

  • 检测和识别条形码。

Core Image 和 AVFoundation 已经可以实现其中的一些任务,但现在他们都集成在一个具有一致性 API 的框架内了。

如果你的应用程序需要执行这些计算机视觉任务之一,再也不用跑去自己实现或使用别人的库了 - 只需使用 Vision 框架。你还可以将其与 Core Image 框架相结合,以获得更多的图像处理能力。

更好的是:你可以使用 Vision 驱动 Core ML,这允许你使用这些计算机视觉技术作为神经网络的预处理步骤。例如,你可以使用 Vision 来检测人脸的位置和大小,将视频帧裁剪到该区域,然后在这部分的面部图像上运行神经网络。

事实上,任何时候当你结合图像或者视频使用 Core ML 时,使用 Vision 都是合理的。原始的 Core ML 需要你确保输入图像是模型所期望的格式。如果使用 Vision 框架来负责调整图像大小等,这会为你节省不少力气。

使用 Vision 来驱动 Core ML 的代码长这个样子:

// Core ML 的机器学习模型
let modelCoreML = ResNet50()
// 将 Core ML 链接到 Vision
let visionModel = try? VNCoreMLModel(for: modelCoreML.model)
let classificationRequest = VNCoreMLRequest(model: visionModel) {
  request, error iniflet observations = request.results as? [VNClassificationObservation] {
    /* 进行预测 */
  }
}
let handler = VNImageRequestHandler(cgImage: yourImage)
try? handler.perform([classificationRequest])

请注意,VNImageRequestHandler 接受一个请求对象数组,允许你将多个计算机视觉任务链接在一起,如下所示:

try? handler.perform([faceDetectionRequest, classificationRequest])

Vision 使计算机视觉变得非常容易使用。 但对我们机器学习人员很酷的事情是,你可以将这些计算机视觉任务的输出输入到你的 Core ML 模型中。 结合 Core Image 的力量,批量图像处理就跟玩儿一样

Metal Performance Shaders 

我最后一个想要讨论的话题就是 Metal —— Apple 的 GPU 编程 API。

我今年为客户提供的很多工作涉及到使用 Metal Performance Shaders (MPS) 来构建神经网络,并对其进行优化,从而获得最佳性能。但是 iOS 10 只提供了几个用于创建神经网络的基本 kernel。通常需要编写自定义的 kernel 来弥补这个缺陷。

所以我很开心使用 iOS 11,可用的 kernel 已经增长了许多,更好的是:我们现在有一个用于构建图的 API 了!

3.png

Metal Performance Shaders 

注意: 为什么要使用 MPS 而不是 Core ML?好问题!最大的原因是当 Core ML 不支持你想要做的事情时,或者当你想要完全的控制权并获得最大运行速度时。

MPS 中对于机器学习来说的最大的变化是:

递归神经网络。你现在可以创建 RNN,LSTM,GRU 和 MGU 层了。这些工作在 MPSImage 对象的序列上,但也适用于 MPSMatrix 对象的序列。这很有趣,因为所有其他 MPS layer 仅处理图像 —— 但显然,当你使用文本或其他非图像数据时,这不是很方便。

更多数据类型。以前的权重应该是 32 位浮点数,但现在可以是 16 位浮点数(半精度),8 位整数,甚至是 2 进制数。卷积和 fully-connected 的 layer 可以用 2 进制权重和 2 进制化输入来完成。

更多的层。到目前为止,我们不得不采用普通的常规卷积、最大池化和平均池化,但是在 iOS 11 MPS 中,你可以进行扩张卷积(Dilated Convolution)、子像素卷积(Subpixel Convolution)、转置卷积(Transposed Convolution)、上采样(Upsampling)和重采样(Resampling)、L2 范数池化(L2-norm pooling)、扩张最大池化(dilated max pooling),还有一些新的激活函数。 MPS 还没有所有的 Keras 或 Caffe layer 类型,但差距正在缩小...

更方便。使用 MPSImages 总是有点奇怪,因为 Metal 每次以 4 个通道的片段组织数据(因为图像由 MTLTexture 对象支持)。但是现在,MPSImage 有用于读取和写入数据的方法,这些数据不会让你感到困惑。

MPSCNNConvolutionDescriptor 还有一个新方法,可以让你在 layer 上设置批量归一化参数。这意味着你不再需要将批量归一化到卷积层中,而 MPS 会为你处理这些事情。非常方便!

性能改进。现有的内核变得更快。这总是好消息。

图 API。这是我最关心的消息。手动创建所有 layer 和(临时)图像总是令人讨厌的。现在你可以描述一个图,就像你在Keras 中一样。 MPS 将自动计算出图像需要多大,如何处理填充,如何设置 MPS 内核的 offset 等等。甚至可以通过融合不同的 layer 来优化整个图。

看起来所有的 MPS 内核都可以使用 NSSecureCoding 进行序列化,这意味着你可以将图保存到文件中,然后将其还原。并且使用这个图来推断现在只是一个单一的方法调用。它不像 Core ML 那么简单,但使用 MPS 绝对比以前好用得多。

有一件事情我目前还不太清楚,那就是我不知道你是否可以编写自己的 kernel 并在这个图中使用。在我客户的工作中,我发现通常需要使用 Metel Shading 语言编写的自定义着色器来进行预处理步骤。据我所知,似乎没有一个「MPSNNCustomKernelNode」类。这还要再多研究一下!

结论:用于机器学习的 Metal Performance Shaders 已经在 iOS 11 中变得更加强大,但是大多数开发人员应该转而使用 Core ML(对于那些使用MPS的来说)。

注意:新的图 API 使我的 Forge 库基本上过时了,除非你希望在 App 中继续支持 iOS 10。我将尽快将示例应用移植到新的图 API 上,然后将写一个更详细的博客文章。

杂项 

还有一些其他的更新:

Accelerate 框架: 似乎 Accelerate 框架中的 BNNS 并没有获得太多功能上的更新。它终于有了 Softmax 层,但 MPS 却没有新的 layer 类型。也许无关紧要:使用 CPU 进行深层神经网络可能不是一个好主意。也就是说,我喜欢 Accelerate,它有很多好玩的东西。而今年,它确实获得了对稀疏矩阵的更多支持,很棒。

自然语言处理: Core ML不仅仅只能处理图像,它还可以处理大量不同类型的数据,包括文本。 使用的 API NSLinguisticTagger 类已经存在了一段时间,但是与 iOS 11 相比变得更加有效了。NSLinguisticTagger 现在已经能进行语言鉴别,词法分析,词性标注,词干提取和命名实体识别。

我没有什么 NLP 的经验,所以我没办法比较它与其他 NLP 框架的区别,但NSLinguisticTagger 看起来相当强大。 如果要将 NLP 添加到 App 中,此 API 似乎是一个好的起点。

都是好消息吗?  

Apple 向我们开发者提供所有的这些新工具都非常的好,但是大多数 Apple API 都有一些很重要的问题:

  1. 闭源

  2. 有局限

  3. 只有在新 OS 发布时候才会更新

这三个东西加在一起意味着苹果的 API 总会落后于其他工具。如果 Keras 增加了一个很炫酷的新的 layer 类型,那么在 Apple 更新其框架和操作系统之前,你都没办法将它和 Core ML 一起使用了。

如果某些 API 得到的计算结果并不是你想要的,你没办法简单的进去看看到底是 Core ML 的问题还是模型的问题,再去修复它 —— 你必须绕开 Core ML 来解决这个问题(并不总是可能的);要么就只能等到下一个 OS 发布了(需要你所有的用户进行升级)。

当然我不希望 Apple 放弃他们的秘密武器,但是就像其他大多数机器学习工具开源一样,为什么不让 Core ML 也开源呢? 

我知道这对于 Apple 来说不可能马上发生,但当你决定在 App 中使用机器学习时,要记住上面的这些内容。