iOS高级开发GitHub 中文社区ios

iOS中的人工智能(Core ML)

2017-09-15  本文已影响1249人  ZhengYaWei

前言

大概在一个多月前笔者参加了一场线上“IT技术人成长经验交流会”,实际上这场大会的参加者主要是以iOS技术人员为主。小猿搜题技术负责人唐巧大神也参加了这场大会,给在场所有人做了一场分享。印象最为深刻的一个大神李嘉璇(《TensorFlow技术解析于实战》作者),当时做了一个让在场几乎所有人都懵逼的技术分享,技术分享的主题是和TensorFlow相关的人工智能。参加交流会的多为iOS开发者,接触人工智能的少之又少,所以在场的90%以上的人听得一脸懵也是正常情况。所以今天笔者想简单的总结下所谓的人工智能以及苹果最近推出的Core ML框架。附上上次技术交流会的几张图,巧神、李嘉璇以及我露面的镜头😀。
唐巧大神 李嘉璇大神 露面的我

什么是人工智能?

人工智能总体介绍

人工智能简称AI。提及这个词汇的时候,通常大数据、机器学习、神经网络等词汇也会与之一块出来。接下来笔者带领大家一一认识这几个词汇。
所谓的人工智能,实际就是机器依靠数据的内在逻辑自己定义方法,通过机器模拟人类大脑思考过程,进而定义方法。机器学习实际就是实现人工智能的一种方法,而方法的定义是以大数据为依靠。
试想一位儿童心理学家在做一些心理学实验。这个实验大概可以分为三步:

人类学习和机器学习对比

但为什么人工智能智能会比人类学习更智能?因为数据和人相比人脑,更准确更快。人类在思考问题的过程中,会因为某些极端条件、前后因果、以及一些细节问题没考虑进去等而导致一些问题,然而机器学习可以依据大量的数据为食物,不断的填充自己的肚子,从而可以考虑到很多极端情况以及一些细节问题等。除此之外,计算机的运行速度是人类大脑无法相比的。吴军博士在《智能时代》一书中对大数据的优势进行了以下总结:“在无法确定因果关系时,数据为我们提供了解决问题的新方法,数据中所包含的信息可以帮助我们消除不确定性,而数据之间的相关性在某种程度上可以取代原来的因果关系,帮助我们得到想要的答案,这便是大数据的核心。”
再简单说下神经网络。可以简单理解成神经元是神经网络的成员,每个神经元都有自己的功能。如在花和草之间,前一个神经元识别出花,后一个神经元在花中识别出玫瑰花。前一个神经元的识别结果再传递个后一个神经元。前者是后者的输出,这就是神经分层的大致比喻。围棋AlphaGo理论上就是一个大型的神经网络,在围棋比赛中,它能预测到接下来的若干种结果,这种预测就是基于神经分层的原理。

机器学习

机器学习就是通过对经验、数据进行分析,来改进现有的计算机算法,优化现有的程序性能。简单说就是:

数据->算法->模型; 需要判断的数据->模型->给出预测

机器学习有三个要素:

Core ML基本介绍

Core ML支持 iOS、MacOS、tvOS和 watchOS。由4部分组成。

Core ML构造

Core ML应用步骤分析

1、拿到模型

最简单的获取方式是在苹果官网下载,具体是在Model模块中,该模块下有Places205-GoogLeNet、ResNet50、Inception V3、 VGG6四个模型。当然也可以自己训练模型。另外苹果也提供了转换器(Core ML Tools),该转换器是基于Python实现的,可用它把训练出来的模型转为适配Core ML的模型。在文章的最后我会介绍如何使用Core ML Tools进行模型转换。

模型转换适配Core ML框架的过程
2、模型导入到项目

将模型导入到项目中。然后点击会出现下图所示状态。大小(Size)是 App 性能的一个重要指标,输入(Input)输出(Output)决定了如何使用这个模型。下图的输入是一张图片,输出有两个值,一个是最有可能的图片物体结果,为 String 类型;另一个是所有可能的物体类型以及对应的可能性,为 String 对应 Dobule 的 Dictionary 类型。点击下图的Resret50字样可以跳转到生成的代码链接中。


模型信息
3、生成高级代码并编程

这一步骤请具体看下面示例的代码。

Core ML实战(基于ResNet50模型照片识别)

这个示例中我选择苹果官网提供的图像识别ResNet50作为模型。照片的选择主要是通过UIImagePickerController这个类实现。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    self.imagePickerController = [[UIImagePickerController alloc] init];
    self.imagePickerController.delegate = self;
    self.imagePickerController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
    self.imagePickerController.allowsEditing = YES;
    self.imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
    self.imagePickerController.mediaTypes = @[(NSString *)kUTTypeImage];
    self.imagePickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
    [self.navigationController presentViewController:self.imagePickerController
                                            animated:YES
                                          completion:nil];
}

在UIImagePickerControllerDelegate的代理方法下,实现了如下代码。

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
    NSString *mediaType=[info objectForKey:UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]){
        CGSize thesize = CGSizeMake(224, 224);
        UIImage *theimage = [self image:info[UIImagePickerControllerEditedImage] scaleToSize:thesize];
        self.imageView.image = theimage;
        
        CVPixelBufferRef imageRef = [self pixelBufferFromCGImage:theimage.CGImage];
        Resnet50 *resnet50Model = [[Resnet50 alloc] init];
        NSError *error = nil;
        Resnet50Output *output = [resnet50Model predictionFromImage:imageRef
                                                              error:&error];
        if (error == nil) {
            self.photoNameLabel.text = output.classLabel;
        } else {
            NSLog(@"Error is %@", error.localizedDescription);
        }
    }
        
    UIImagePickerController *imagePickerVC = picker;
    [imagePickerVC dismissViewControllerAnimated:YES completion:^{
        
    }];
}

在上面Core ML应用步骤分析中,导入模型那一步骤我们知道,ResNet50这个模型需要输入的是一个 224 * 224 的Image 图片模型;输出则是预测归类标签等信息。上面方法中的 imageRef以及output.classLabel都是基于此模型定义的。

如果不是很理解,可以点击模型,然后再点击Model Class -> 模型名称,可以查看模型生成的代码。下面一段代码便是此模型生成的代码。

//
// Resnet50.h
// 
// This file was automatically generated and should not be edited.
// 

#import <Foundation/Foundation.h>
#import <CoreML/CoreML.h>
#include <stdint.h>

NS_ASSUME_NONNULL_BEGIN

/// Model Prediction Input Type
@interface Resnet50Input : NSObject<MLFeatureProvider>
/// Input image of scene to be classified as BGR image buffer, 224 pixels wide by 224 pixels high
@property (readwrite, nonatomic) CVPixelBufferRef image;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithImage:(CVPixelBufferRef)image;
@end

/// Model Prediction Output Type
@interface Resnet50Output : NSObject<MLFeatureProvider>
/// Probability of each category as dictionary of strings to doubles
@property (readwrite, nonatomic) NSDictionary<NSString *, NSNumber *> * classLabelProbs;
/// Most likely image category as string value
@property (readwrite, nonatomic) NSString * classLabel;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithClassLabelProbs:(NSDictionary<NSString *, NSNumber *> *)classLabelProbs classLabel:(NSString *)classLabel;
@end

/// Class for model loading and prediction
@interface Resnet50 : NSObject
@property (readonly, nonatomic, nullable) MLModel * model;
- (nullable instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError * _Nullable * _Nullable)error;
/// Make a prediction using the standard interface
/// @param input an instance of Resnet50Input to predict from
/// @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
/// @return the prediction as Resnet50Output
- (nullable Resnet50Output *)predictionFromFeatures:(Resnet50Input *)input error:(NSError * _Nullable * _Nullable)error;
/// Make a prediction using the convenience interface
/// @param image Input image of scene to be classified as BGR image buffer, 224 pixels wide by 224 pixels high:
/// @param error If an error occurs, upon return contains an NSError object that describes the problem. If you are not interested in possible errors, pass in NULL.
/// @return the prediction as Resnet50Output
- (nullable Resnet50Output *)predictionFromImage:(CVPixelBufferRef)image error:(NSError * _Nullable * _Nullable)error;
@end
NS_ASSUME_NONNULL_END

这样就基本完成了主要代码的编写。不过代码的实现中还有这样两个方法:

- (CVPixelBufferRef) pixelBufferFromCGImage: (CGImageRef) image {
    NSDictionary *options = @{
                              (NSString*)kCVPixelBufferCGImageCompatibilityKey : @YES,
                              (NSString*)kCVPixelBufferCGBitmapContextCompatibilityKey : @YES,
                              };

    CVPixelBufferRef pxbuffer = NULL;
    CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, CGImageGetWidth(image),
                                          CGImageGetHeight(image), kCVPixelFormatType_32ARGB, (__bridge CFDictionaryRef) options,
                                          &pxbuffer);
    if (status!=kCVReturnSuccess) {
        NSLog(@"Operation failed");
    }
    NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);

    CVPixelBufferLockBaseAddress(pxbuffer, 0);
    void *pxdata = CVPixelBufferGetBaseAddress(pxbuffer);

    CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(pxdata, CGImageGetWidth(image),
                                                 CGImageGetHeight(image), 8, 4*CGImageGetWidth(image), rgbColorSpace,
                                                 kCGImageAlphaNoneSkipFirst);
    NSParameterAssert(context);

    CGContextConcatCTM(context, CGAffineTransformMakeRotation(0));
    CGAffineTransform flipVertical = CGAffineTransformMake( 1, 0, 0, -1, 0, CGImageGetHeight(image) );
    CGContextConcatCTM(context, flipVertical);
    CGAffineTransform flipHorizontal = CGAffineTransformMake( -1.0, 0.0, 0.0, 1.0, CGImageGetWidth(image), 0.0 );
    CGContextConcatCTM(context, flipHorizontal);

    CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image),
                                           CGImageGetHeight(image)), image);
    CGColorSpaceRelease(rgbColorSpace);
    CGContextRelease(context);

    CVPixelBufferUnlockBaseAddress(pxbuffer, 0);
    return pxbuffer;
}

- (UIImage*)image:(UIImage *)image scaleToSize:(CGSize)size{

    UIGraphicsBeginImageContext(size);

    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];

    UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return scaledImage;
}

对于第一个方法,CVPixelBufferRef这种图像格式的处理与UIImage, CGImageRef的处理需小心,容易造成内存泄漏。对于第二个方法,是因为模型的Input Image是有宽和高限制的,因此输入时,需要转换为224 * 224大小才能够正确识别。

这个Demo到此就完成了,如果此时拍一张你敲代码的键盘,就能识别出图片中显示的是键盘。
这里顺便在说一下文字感情分析类实现过程,有兴趣的可以自行研究下。文字感情分析总共分为四个过程:1、用自然语义处理API统计输入文字的单词频率。2、将单词频率输入到 Core ML 的模型中。3、Core ML 模型根据单词频率判断内容为正面或负面情绪。4、根据情绪内容更新 UI。其实说白了基本上和图片识别的实现过程实现一致。

Core ML Tool 模型转化工具介绍

之前说过了,模型可以在苹果官网下载,可以自己训练模型,也可以借助Core ML Tool将Caffee,Keras,LIBSVM,scikit-learn,xgboot等开源机器学习框架训练出的模型转换为 Core ML 对应的模型。Core ML Tool模型转换工具是基于Python实现的,我们可以定制转换器以及转换模型的参数。

#######1、安转Core ML Tool 模型转换工具
如果电脑没有安装Python,请先执行:
brew install python
然后直接输入以下命令:
pip install -U coremltools

2、转换训练好的模型

假如模型是用 caffe 训练的,即现在有一个 .caffemodel 文件,以下步骤可以将其转化为苹果支持的 .mlmodel:

import coremltools

// 利用 core ml 中对应的 caffee 转化器处理 .caffemodel 模型
coreml_model = coremltools.converters.caffe.convert('XXX.caffemodel')

// 将转化好的模型存储为 .mlmodel 文件
coreml_model.save('XXX.mlmodel')

确定转化的模型是否正常(检测模型能否识别一张狗的图片),可以直接运行如下命令。如果能正确输出结果,预测结果应含有 dog,并且预测的正确可能性比较高,则说明模型转换没问题。

XXX.mlmodel.predict('data': myTestData)
3、定制化转化的模型

定制转化模型的参数,我们一般用 label.txt 文件来定义,直接传入转化中即可。

// 自定义模型的接口参数
labels = 'labels.txt'

// 将 labels 设为转换的模型参数
coreml_model = coremltools.converters.caffe.convert('XXX.caffemodel', class_labels='labels')

定制转化的输入数据 data 为 image 类型:

coreml_model = coremltools.converters.caffe.convert('XXX.caffemodel', class_labels='labels', image_input_name = 'data')

指定转换模型的描述型参数(metadata),其他参数的设置类似:

// 指定作者信息
coreml_model.author = 'Apple Papa'

// 指定许可证
coreml_model.license = 'MIT'

// 指定输入('data')描述
coreml_model.input_description['data'] = 'An image of flower'

结语

整片文章中我们认识了什么人工智能、机器学习,知道了Core ML框架结构以及应用步骤,并实现了一个简单的照片识别实例,最后还介绍了Core ML模型转换工具。但是这一切的一切只是简单的入门,同上次参加经验交流会《TensorFlow技术解析于实战》作者李嘉璇所讲的那些技术相比,连九牛一毛都不算,毕竟人家讲的都是一些很高深的底层实现以及高数中那些复杂计算公式。可能有些人会认为人工智能只是一个噱头,实际人工智能已经渗入到我们生活的很多方面,希望这篇文章能引起更多iOS开发者对人工智能领域的关注。

上一篇下一篇

猜你喜欢

热点阅读