导语:在刚刚过去的WWDC上,苹果发布了Core ML这个机器学习框架。现在,开发者可以轻松的使用Core ML把机器学习功能集成到自己的应用里,让应用变得更加智能,给用户更牛逼的体验。

苹果在 iOS 5 里引入了 NSLinguisticTagger 来分析自然语言。iOS 8 出了 Metal,提供了对设备 GPU 的底层访问。去年,苹果在 Accelerate 框架添加了 Basic Neural Network Subroutines (BNNS),使开发者可以构建用于推理(不是训练)的神经网络。

今年,苹果给了我们 Core ML 和 Vision,让iOS开发者在人工智能上面更上一步台阶。

  • Core ML 让我们更容易在 App 中使用训练过的模型。
  • Vision 让我们轻松访问苹果的模型,用于面部检测、面部特征点、文字、矩形、条形码和物体。

你还可以在 Vision 模型中包装任意的图像分析 Core ML 模型。由于这两个框架是基于 Metal 构建的,它们能在设备上高效运行,所以不需要把用户的数据发送到服务器。

一、CORE ML是什么?

相信很多人都听说过机器学习,除了专业人士,应该很少有人去研究机器学习里面的具体实现,CORE ML的出现,大大降低了iOS开发人员进入这一领域的门槛,能以最低成本开发出更加智能的产品。

机器学习的一个重要环节就是利用海量的数据去训练特定的模型,然后在遇到新数据的时候能够准确预测出结果。比如,事先通过大量的物体特征训练一个模型,当把一个新物体输入该模型,模型能够准确预测出物体所属的物种和类别;学习大量围棋对局后,面对一个陌生的棋局,知道在哪下棋赢的概率更高。

在对机器进行训练的时候,训练完成后,会生成一个关于这个特定问题的数据模型,对模型输入关于这个特定问题的新数据,模型会返回一个预测结果。Core ML实际做的事情是使用事先训练好的模型(trained model),在Native利用MLNeuralNetworkEngine等相关模块进行预测,最终返回结果,这种在本地进行预测的方式可以不依赖网络,也可以降低处理时间。

应用和Core ML的交互流程大体如图所示:

从图上可以看出,真正智能的部分其实是这个事先训练好的模型(trained model),这个模型决定了最终判断的结果。苹果提供了一些转化好的Core ML格式的模型,也可以通过苹果提供的工具把自己在别的常用机器学习工具生成的模型转化为Core ML格式的模型,这个工具当前页只是支持一些常用的格式模型的转换,如果需要转换比较特定的格式模型,需要参考这个工具的代码,把你特定的模型数据转换为苹果规定的模型数据。

苹果的 Core ML 框架现在已经支持前馈神经网、卷积神经网、递归神经网、诸如随机森林和提升树的决策树集成、支持向量机、线性回归和 logistic 回归、特征工程和流水线模型。

二、CORE ML涉及到的相关技术

Core ML是机器学习的一个基础框架,Vision、GameplayKit都有利用Core ML做相应的处理。为了提升计算性能,苹果充分利用了硬件的特性,最大限度优化了Core ML的性能,减少内存占用和功耗。

1、Metal

Metal 是针对 iPhone 和 iPad 中 GPU 编程的高度优化的框架,Metal 与 OpenGL ES 相比最大的好处是显著降低了消耗。 OpenGL 在创建缓冲区和纹理的过程中都会复制一份以避免 GPU 在使用数据的时候发生异常,而这些复制操作是非常耗时的。 为了提升效率和性能Metal在安全和效率方面选择了后者,Metal 并不复制资源,使用Metal编程需要开发者自己来保证数据安全,开发者需要负责在 CPU 和 GPU 之间同步访问。使用 Metal 时仍然有些这方面的问题需要注意。

Metal 的另外一个好处是其预估 GPU 状态来避免多余的验证和编译。在 OpenGL 中,你需要依次设置 GPU 的状态,在每个绘制指令 (draw call) 之前需要验证新的状态。最坏的情况是 OpenGL 需要再次重新编译着色器 (shader) 以反映新的状态。 Metal 选择了另一种方法,在渲染引擎初始化过程中,一组状态被烘焙 (bake) 至预估渲染的路径 (pass) 中。多个不同资源可以共同使用该渲染路径对象,但其它的状态是恒定的。Metal 中一个渲染路径无需更进一步的验证,使 API 的消耗降到最低,从而大大增加每帧的绘制指令的数量。

2、神经网络

深度学习(Deep Learning,简称DL)当前是相当火热,任何一个方向和应用,都希望能够运用到深度学习,戴上智能的皇冠,不只是互联网、人工智能,生活中的各大领域都能反映出深度学习引领的巨大变革。要学习深度学习,首先需要先弄清楚人工神经网络(Artificial Neural Networks,简称ANN),人工神经网络的设计灵感完全来源于生物学上的神经元的信息传递机制,神经网络已经发展成为一类多学科交叉的学科领域,它也随着深度学习取得的进展受到重视和推崇。

卷积神经网络(Convolutional Neural Networks,简称 CNNs 或者 ConvNets)是人工神经网络的一种,已成为当前语音分析和图像识别领域的研究热点。它的权值共享网络结构使之更类似于生物神经网络,降低了网络模型的复杂度,减少了权值的数量。优点在网络的输入是多维图像时表现的更为明显,使图像可以直接作为网络的输入,避免了传统识别算法中复杂的特征提取和数据重建过程。CNNs是深层神经网络领域的主力。它们已经学会对图像进行分类,对图像的识别准确率已经超过了人类。

3、Metal Performance Shaders

Metal Performance Shader是apple推出的一套通过Metal来在iOS上实现深度学习的工具,它主要封装了MPSImage来存储数据管理内存,实现了Convolution、Pooling、Fullconnetcion、ReLU等常用的卷积神经网络中的Layer。

如果自己的学习模型Core ML不支持,或者想要完全控制各个Layer的输入和输出,则必须使用MPS来完成,在服务端训练好的模型参数需要进行转换才能被MPS使用。一般的CNN网络包含可训练参数的Layer基本上只有Convolution、Fullconnetcion、Normalization这三种layer。也就是说只要把这三种层的参数拿出来转化为MPS需要的格式就可以给MPS使用了。

三、Core ML+Vision的应用场景

Core ML提供的是一套底层的算法库,Core ML 框架当前已经支持神经网络、树组合、支持向量机、广义线性模型、特征工程和流水线模型等算法模型的运算, 理论上,只要我们是基于上述这些算法架构训练出来的模型,Core ML都是可以支持的

你可能已经从它的名字中猜到了,Vision 可以让你执行计算机视觉任务。在以前你可能会使用OpenCV,但现在 iOS 有自己的 API 了。Vision库提供了很多图像处理方面的功能,可以完成人脸识别、特征检测、条码识别、文字识别、并对图像和视频中的场景进行分类等多个领域,苹果对这些大数据量的运行也是进行了很深入的优化的,性能比较好。

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

  1. 在给定的图像中寻找人脸。
  2. 寻找面部的详细特征,比如眼睛和嘴巴的位置,头部的形状等等。
  3. 追踪视频中移动的对象、确定地平线的角度。
  4. 转换两个图像,使其内容对齐、检测包含文本的图像中的区域。
  5. 检测和识别条形码。

可以使用 Vision 驱动 Core ML,在使用Core ML进行机器学习的时候,可以先用Vision框架进行一些数据的预处理。例如,你可以使用 Vision 来检测人脸的位置和大小,将视频帧裁剪到该区域,然后在这部分的面部图像上运行神经网络。

利用Core ML 进行机器学习的时候,输入的图像数据要求是模型规定的格式和大小,一般我们获取到的数据大部分都是不满足这个要求的,如果使用 Vision 框架来负责调整图像大小、图像色度等,我们就很容易把图像数据转换为模型要求的格式。

利用苹果提供的这些能力,并结合我们自己的产品,应该可以创造出很多有意思的产品功能,想象空间很大,比如:

  1. 利用训练好的模型,把低分辨率的图片转化为高分辨率图片,能够节省很大的流量,同时在用户体验上也能得到很大的提升
  2. 用于人脸检测,通过人脸聚焦出之前跟这个好友的一些互动操作
  3. 通过学习用户在app上的操作路径,预测用户的行为,比如通过预测用户的习惯,定时给用户发feed?

总之,有很多场景可以应用。

四、利用Core ML在图像识别方面实践

需要 Xcode 9 Beta1 或更新的版本、以及 iOS 11环境,可以下载Demo

项目中允许用户从照片库中选择一张图片,分别选择物体分类识别和矩形区域数字识别。

1、直接利用ML进行图像分类识别

a、将 Core ML 模型集成到你的 App

以Inceptionv3模型为例,可以从苹果的“机器学习”页面下载。目前苹果提供的这几个模型都用于在图片中检测物体——树、动物、人等等。如果你有一个训练过的模型,并且是使用受支持的机器学习工具训练的,例如 Caffe、Keras 或 scikit-learn,Converting Trained Models to Core ML 介绍了如何将其转换为 Core ML 格式。

下载 Inceptionv3.mlmodel 后,把它从 Finder 拖到项目导航器里:

b、Xcode会把模型编译成三个类:Inceptionv3Input、Inceptionv3Output、Inceptionv3,同时生成一个Inceptionv3.mlmodelc文件目录,这里面就是需要用到已经训练好的模型数据.

只需要添加几行代码,即可实现一个简单的物体分类智能识别器。大大降低了人工智能的门槛

- (NSString*)predictionWithResnet50:(CVPixelBufferRef )buffer
{
    NSError *modelLoadError = nil;
    NSURL *modelUrl = [NSURL URLWithString:[[NSBundle mainBundle] pathForResource:@"Resnet50" ofType:@"mlmodelc"]];
    Resnet50* resnet50 = [[Resnet50 alloc] initWithContentsOfURL:modelUrl error:&modelLoadError];
    
    NSError *predictionError = nil;
    Resnet50Output *resnet50Output = [resnet50 predictionFromImage:buffer error:&predictionError];
    if (predictionError) {
        return predictionError.description;
    } else {
        // resnet50Output.classLabelProbs sort
        return [NSString stringWithFormat:@"识别结果:%@,匹配率:%.2f",resnet50Output.classLabel, [[resnet50Output.classLabelProbs valueForKey:resnet50Output.classLabel]floatValue]];
    }
}

2、利用Vision识别矩形框中的数字

上面的方式,是直接利用Core ML操作模型来预测结果的,除了这种方式,我们还可以在 Vision 模型中包装任意的图像分析 Core ML 模型。模型添加跟上面的方法一致,我们只需要通过vision把相关请求进行封装,

- (void)predictMINISTClassifier:(UIImage* )uiImage {
    CIImage *ciImage = [CIImage imageWithCGImage:uiImage.CGImage];
    CGImagePropertyOrientation orientation = [self cgImagePropertyOrientation:uiImage];
    self.inputImage = [ciImage imageByApplyingOrientation:orientation];

    VNDetectRectanglesRequest* rectanglesRequest = [[VNDetectRectanglesRequest alloc]initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
        [self handleRectangles:request error:error];
    }];

    VNImageRequestHandler *handler = [[VNImageRequestHandler alloc] initWithCGImage:uiImage.CGImage orientation:orientation options:nil];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSError* error = nil;
        [handler performRequests:@[rectanglesRequest] error:&error];
    });
}

- (void)handleRectangles:(VNRequest*)request error:(NSError*)error {
    VNRectangleObservation *detectedRectangle = request.results.firstObject;
    CGSize imageSize = self.inputImage.extent.size;
    CGRect boundingBox = [self scaledCGRect:detectedRectangle.boundingBox toSize:imageSize];
    if (!CGRectContainsRect(self.inputImage.extent, boundingBox)) {
        NSLog(@"invalid detected rectangle");
        return;
    }
    CGPoint topLeft = [self scaledCGPoint:detectedRectangle.topLeft toSize:imageSize];
    CGPoint topRight = [self scaledCGPoint:detectedRectangle.topRight toSize:imageSize];
    CGPoint bottomLeft =[self scaledCGPoint:detectedRectangle.bottomLeft toSize:imageSize];
    CGPoint bottomRight = [self scaledCGPoint:detectedRectangle.bottomRight toSize:imageSize];
    CIImage *cropImage = [self.inputImage imageByCroppingToRect:boundingBox];
    NSDictionary *param = [NSDictionary dictionaryWithObjectsAndKeys:[CIVector vectorWithCGPoint:topLeft],@"inputTopLeft",[CIVector vectorWithCGPoint:topRight],@"inputTopRight",[CIVector vectorWithCGPoint:bottomLeft],@"inputBottomLeft",[CIVector vectorWithCGPoint:bottomRight],@"inputBottomRight", nil];
    CIImage* filterImage = [cropImage imageByApplyingFilter:@"CIPerspectiveCorrection" withInputParameters:param];
    filterImage = [filterImage imageByApplyingFilter:@"CIColorControls" withInputParameters:[NSDictionary dictionaryWithObjectsAndKeys:@(0),kCIInputSaturationKey,@(32),kCIInputContrastKey, nil]];
    filterImage = [filterImage imageByApplyingFilter:@"CIColorInvert" withInputParameters:nil];    UIImage *correctedImage = [UIImage imageWithCIImage:filterImage];
    dispatch_async(dispatch_get_main_queue(), ^{
        self.imageView.image = correctedImage;
    });
    VNImageRequestHandler *vnImageRequestHandler = [[VNImageRequestHandler alloc] initWithCIImage:filterImage options:nil];

    MNISTClassifier *model = [MNISTClassifier new];
    VNCoreMLModel *vnCoreModel = [VNCoreMLModel modelForMLModel:model.model error:nil];
    VNCoreMLRequest *classificationRequest = [[VNCoreMLRequest alloc] initWithModel:vnCoreModel completionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
        VNClassificationObservation *best = request.results.firstObject;
        
        NSString* result  = [NSString stringWithFormat:@"识别结果:%@,匹配率:%.2f",best.identifier,best.confidence];
        dispatch_async(dispatch_get_main_queue(), ^{
            self.resultLabel.text = result;
        });
    }];
    NSError *imageError = nil;
    [vnImageRequestHandler performRequests:@[classificationRequest] error:&imageError];
}

3、运行效果

五、一些思考

1、模型是否可以通过下载的方式

从苹果提供的几个模型来看,他们占用的空间都是几十兆上下,在实际应用中,这基本是不现实的,安装包增加几十兆,基本是不可想象的。经过分析,Xcode对添加进去的模型做了两件事:创建对应的类、添加模型数据文件,这个工作我们自己也能完成

a、首先我们自己生成所需要的类,参考项目中的GoogLeNetPlaces.h GoogLeNetPlaces.m两个文件

b、把需要的模型文件夹GoogLeNetPlaces.mlmodelc作为资源添加到工程,实际中可以通过下载获取

c、在生成GoogLeNetPlaces实例的时候,把模型文件的路径传入

- (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError * _Nullable * _Nullable)error {
    self = [super init];
    if (!self) { return nil; }
    _model = [MLModel modelWithContentsOfURL:url error:error];
    if (_model == nil) { return nil; }
    return self;
}

MLModel的创建方式是通过接口

+ (nullable instancetype)modelWithContentsOfURL:(NSURL *)url
                                          error:(NSError **)error;

完成的,我我们只需要把模型数据的路径指定即可。通过这种方式我们完全不需要添加Places205-GoogLeNet模型到工程中,即可用它完成物体预测,用相同的方法,其他模型也可以用这种方式完成。

2、如果需要修改线上APP的模型数据,这种需求能完成么?

这也是能够做到的,

a、如果只是更改模型的参数和数据,接口没有改变(即这些类文件没有改变),这种情况,完全可以通过更改模型数据,达到修改外网模型的需求

b、如果模型接口有改变,或者是想换一个模型进行预测,这种情况,如果借用OCS的能力,也是能够做到的,把生成的类代码转换为OCS脚本下发,接口和模型文件,都通过下载新的插件,即可完成修改模型参数甚至替换其他模型进行数据预测的需求

3、线程是否安全?

现在从文档上看,没有明确说是否线程安全,自己实验采样100个线程并行运行,没有发现异常情况,具体还需要等正式版发布后,再看看是否线程安全

六、遇到的一些问题

  1. 现在看模型的预测准确率还比较低,很多种情况都识别不了,但愿正式版出来后会提升准确率
  2. Xcode9 beta版不支持添加资源目录,如果想再工程中添加资源目录,必须先在Xcode8打开工程,添加进去之后,再用Xcode9 beta打开,这个应该是Xcode9 beata版本的bug,正式版应该能够修复
  3. xcode9 beta版之后,导致xcode8的模拟器都不能够用了
  4. 设备上不能进行训练。你需要使用离线工具包来进行训练,然后将它们转换到 Core ML 格式
  5. 如果 Core ML 并不是支持所有的layer。在这一点上,你不能使用自己的 kernel 来扩展 Core ML。在使用 TensorFlow 这样的工具来构建通用计算图模型时,mlmodel 文件格式可能就不那么灵活了。
  6. Core ML 转换工具只支持特定版本的数量有限的训练工具。例如,如果你在 TensorFLow 中训练了一个模型,则无法使用此工具,你必须编写自己的转换脚本。
  7. 不能查看Core ML中间层的结果输出,只能获得最后一层网络的预测值,在使用模型进行预测的时候出现问题,这时候不好定位是模型的问题还是框架的问题。
  8. 如果你想要完全能够掌控机器学习的的各个layer输出以及决定是否运行在CPU还是GPU,那么你必须使用 Metal Performance Shader 或 Accelerate 框架来实现完成你的模型的运行。

总之,Core ML当前版本还有很多问题,期待正式版本发布能够解决这些问题

参考文献:

  • Metal
  • Metal开发文档
  • Vision开发文档
  • Metal Performance Shaders
  • How do Convolutional Neural Networks work
  • 深度学习 — 反向传播(BP)理论推导
  • 神经网络基础

如果您觉得我们的内容还不错,就请转发到朋友圈,和小伙伴一起分享吧~

文章来源于腾讯云开发者社区,点击查看原文