IOS机械学习初探

背景


机械学习是目前比较热门的技术,它的英文名称叫做Machine Learning简称ML。

早在2017年WWDC上,Apple公司就宣布其提供的框架Core ML已经被每个主流ML平台支持,并可以将现有的训练好的模型转换成Core ML支持的模型。

Turi Create在2017年WWDC之后被收购,其简化了定制机器学习模型的开发,降低了开发门槛。开发者无需成为机器学习专家,就可以进行机械训练,导出Core ML支持的模型。但是它使用的语言是Python。

2018年WWDC上,apple宣布了新的ML开发工具Create ML,这次使用的是自己的语言swift并集成到Xcode中。虽然目前只完成了Turi Create的部分功能,但后续会不断扩展优化。

本文就是介绍使用Create MLCore ML框架,体验一次机械学习从无到有的过程。

开发环境


想要使用Create ML需要满足2个条件:

  • 一台Mac,运行macOS 10.14 Mojave beta
  • Xcode 10.x beta

写这篇文章的时候 Mojave 版本是10.14 beta 8,Xcode 10 beta 6。下载链接:https://developer.apple.com/download/

创建一个自己训练的模型


准备机械学习的数据样本

我想做一个自动识别各种动物的神经模块,因此,我准备了一些动物的图片,作为数据样本。因为时间原因,从百度上下80张动物图片,分别为兔子、猫、狗、老虎,每个种类各20张。这里对图片有些要求,尽量清晰,并且最好只有一个动物。

数据样本越多,训练的效果越好,不过相对的训练的时间也越长。

图片分类.png

准备机械学习效果验证的数据样本

有了学习数据样本后,还需要准备一个验证样本,来验证机械学习训练结果的准确性,目录结构和之前一样,每个动物我准备了10个图片。

测试数据.png

开始机械训练


用xcode10创建机械学习库

打开xcode10,创建一个macOS版的playground。

macOS_playground.png

然后输入以下代码:

import CreateMLUI

let builder = MLImageClassifierBuilder()
builder.showInLiveView()

点击运行并打开辅助界面就可以看到Create ML的训练界面。

机械训练界面.png

导入样本数据进行训练

将之前准备的数据样本,pets文件夹拖入右侧的虚线框就可以自动进行训练。

pets文件夹里面有很多字文件夹,每个文件夹成为数据集,里面是同一类图片数据,字文件夹的名称就是这一类的输出结果。字文件夹名字不能用中文,会在后面集成自己APP的时候输出乱码,应该有其他方法解决。

训练结果.png

上面这张图片是我80个测试数据样本的训练结果,第一个表格是有3个表格头,分别是Images ProcessedElapsed TimePercent Complete 代表的是图片总个数、花费时间和完成进度。在我的电脑配置下,80个图片大约用了14秒。

每次训练完成,系统会在样本数据中随机抽取10%的数据样本进行准确度验证,第二个表格是验证结果。我的样本数据得到了100%的验证结果。

从上面这个训练过程可以看出,我们并不需要进行代码编写机械学习的算法,这些Create ML都帮我们实现了,并且提供了一套优秀的提取图片特征算法,我们只需要提供样本数据就能得到训练后的模型,因此的确不需要专业的机械学习知识。

进行准确度验证

经过以上步骤,我们能够得到一个初步的神经模型,但是我们还需要测试其是否准确,是否达到我们的要求,因此还需要进行验证。将之前准备好的pets-test文件夹(目录结构和pets一致)拖入右上角虚线框就可以进行验证。

验证结果.png

可以看到,这次训练的模型结果有一张图片出错,我传进去是猫的图片,它是被成了兔子。准确率只有98%。根据这个结果,我们可以选择是否使用这个模型。

提高模型准确性

提供准确性有很多方法,加大数据样本数量,优化样本数据等等。Create ML提供一些优化方法可以在训练之前设置,如下图:

增加样本.png

我们可以给图片样本增加参数,如翻转flipping, 旋转rotating, 斜切shearing或改变曝光exposure等,打上勾就会增加效果并进行排列组合增加样本数量。

官方提供的解释.png

我们还可以增加iterations的值提高准确性,这个应该和算法有关。不过这里需要注意的是,勾选越多就意味着样本数据越多,训练的时间也就越长。

下面是优化过的模型结果,正确率有一定提高。

正确率提高.png

最后一步就是导出数据模型,我们下面将在工程中使用它。

保存数据模型.png

在工程中使用训练好的模型


新建一个工程

导出的模型是一个以.mlmodel结尾的文件,我的模型大小不太大,大概53KB左右。新建一个singleView工程,然后直接将该模型拖进来就可以直接使用。

新建工程.png

.mlmodel文件会自动根据项目的语言转换成OC或者swift代码,点击中间紫色图标右边的箭头就可以进入查看对应的代码。

api代码.png

从上图我们可以看到,主要有3个类animal,animalInput,animalOutput,分别是主类,输入类和输出类。

animalOutput是输出结果类,里面有个属性的名字叫做classLabel就是相似度最高的结果,另外一个属性classLabelProbs就是每个结果的可能性占比,占比越高可能性越大。

animalInput是输入类,其支持一个初始化方法- (instancetype)initWithImage:(CVPixelBufferRef)image;,可以看出需要把图片转换成CVPixelBufferRef格式然后才能使用。

图片转化

前面我们已经说到不能直接使用UIImage格式,而需要转换成CVPixelBufferRef格式下面是转换代码:

- (CVPixelBufferRef)pixelBufferFromCGImage:(CGImageRef)image{
    
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
                         [NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
                         nil];

CVPixelBufferRef pxbuffer = NULL;

CGFloat frameWidth = CGImageGetWidth(image);
CGFloat frameHeight = CGImageGetHeight(image);

CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault,
                                      frameWidth,
                                      frameHeight,
                                      kCVPixelFormatType_32ARGB,
                                      (__bridge CFDictionaryRef) options,
                                      &pxbuffer);

NSParameterAssert(status == kCVReturnSuccess && pxbuffer != NULL);

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

CGColorSpaceRef rgbColorSpace = CGColorSpaceCreateDeviceRGB();

CGContextRef context = CGBitmapContextCreate(pxdata,
                                             frameWidth,
                                             frameHeight,
                                             8,
                                             CVPixelBufferGetBytesPerRow(pxbuffer),
                                             rgbColorSpace,
                                             (CGBitmapInfo)kCGImageAlphaNoneSkipFirst);
NSParameterAssert(context);
CGContextConcatCTM(context, CGAffineTransformIdentity);
CGContextDrawImage(context, CGRectMake(0,
                                       0,
                                       frameWidth,
                                       frameHeight),
                   image);
CGColorSpaceRelease(rgbColorSpace);
CGContextRelease(context);

CVPixelBufferUnlockBaseAddress(pxbuffer, 0);

return pxbuffer;
}

这里还有一点需要主注意,从Model Evaluation Parameters的表格中可以看出,输入的图片规格需要时299 X 299,因此还需要将UIImage先转换成299 X 299大小再进行转换。

下面是转换代码:

- (UIImage *)scaleToSize:(CGSize)size image:(UIImage *)image {
    UIGraphicsBeginImageContext(size);
    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage* scaledImage =  UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return scaledImage;
}

模型的使用

在使用前别忘了加入头文件#import "animal.h"

我这里是使用从相册中选取一张图片,然后判断该图片是什么类型的动物,代码如下:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
//获取图片
    UIImage *image = info[UIImagePickerControllerOriginalImage];
    [self dismissViewControllerAnimated:YES completion:nil];
    self.photoView.image = image;
    UIImage * newSizeImage = [self scaleToSize:CGSizeMake(299, 299) image:image];

    CVPixelBufferRef imageRef = [self pixelBufferFromCGImage:newSizeImage.CGImage];

    NSError * error = nil;
    animalOutput * outPut = [_netModel predictionFromImage:imageRef error:&error];
    if (error == nil) {
        NSNumber * num = [outPut.classLabelProbs objectForKey:outPut.classLabel];
        NSString * resultStr = [NSString stringWithFormat:@"%@ : %.2lf",outPut.classLabel,[num doubleValue]];

        self.resultLabel.text = resultStr;
    }
    else {
        self.resultLabel.text = error.description;
    }

}

最终结果

最终结果.PNG
最终结果2.PNG
最终结果3.PNG

推荐阅读更多精彩内容