• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

MLCore加载在线模型

武飞扬头像
HatsuneMikuFansYSQ
帮助2

前言回顾

上次已经解释了如何从本地加载mlmodel模型,这次,我们着重来介绍一下如何加载在线的mlmodel模型。

思路

模型文件解析

我们以官网的MNISTClassifier.mlmodel模型为例。首先,我们打开MNISTClassifier.h头文件看下:

......	
/// Model Prediction Input Type
API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
@interface MNISTClassifierInput : NSObject<MLFeatureProvider>
@property (readwrite, nonatomic) CVPixelBufferRef image;
......
@end

/// Model Prediction Output Type
API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
@interface MNISTClassifierOutput : NSObject<MLFeatureProvider>
/// Probability of each digit as dictionary of 64-bit integers to doubles
@property (readwrite, nonatomic, strong) NSDictionary<NSNumber *, NSNumber *> * labelProbabilities;

/// Most likely digit as integer value
@property (readwrite, nonatomic) int64_t classLabel;
......
@end

/// Class for model loading and prediction
API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)) __attribute__((visibility("hidden")))
@interface MNISTClassifier : NSObject
......
@end
学新通

可以发现,无论是Input和Output都必须继承来自MLFeatureProvider协议,我们在看下MLFeatureProvider协议的相关函数:

//
//  MLFeatureProvider.h
//  CoreML
//
//  Copyright © 2017 Apple Inc. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreML/MLFeatureValue.h>

NS_ASSUME_NONNULL_BEGIN

/*
 * Protocol for accessing a feature value for a feature name
 */
API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0))
@protocol MLFeatureProvider

@property (readonly, nonatomic) NSSet<NSString *> *featureNames;

/// Returns nil if the provided featureName is not in the set of featureNames
- (nullable MLFeatureValue *)featureValueForName:(NSString *)featureName;

@end

NS_ASSUME_NONNULL_END
学新通

看到这个协议,我们只需要理解featureNames变量和@select(featureValueForName:)即可。
这里,我们回顾一下一个模型的输入和输出需要具备什么功能:

  1. 输入可能有多个通道输入,并且每个通道都有一个唯一表示的名字。
  2. 输出可能有多个通道输出,并且每个通道都有一个唯一表示的名字。
    这里,就可以很好的解释以上两个东西的含义了。
    featureNames,代表这一层所包含所有输入(输出)所包含的唯一标识符,在这里,input的唯一标识符有一个,名字为"image",输出的唯一标识符有两个,名字为"classLabel"和"labelProbabilities"。
    @select(featureValueForName:)的意思为,如果查询到某个变量,需要返回该变量对应的数据。好比输入的image,当传入"image"的时候,需要返回对应的图像数据。
    到此,我们已经明白了MLModel的运行流程。那么,只需要仿照mlmodel相关资料,就可以模拟其在手机上的运行了。

模型加载

上面已经讲完了输入和输出,这时候,我们还没搞清楚模型到底从哪里来?
我们看下MLModel类:

@interface MLModel : NSObject
/// Construct a model with a default MLModelConfiguration object
  (nullable instancetype)modelWithContentsOfURL:(NSURL *)url
                                          error:(NSError **)error;
/// Construct a model given the location of its on-disk representation. Returns nil on error.
  (nullable instancetype)modelWithContentsOfURL:(NSURL *)url
                                  configuration:(MLModelConfiguration *)configuration
                                          error:(NSError * _Nullable __autoreleasing *)error API_AVAILABLE(macos(10.14), ios(12.0), watchos(5.0), tvos(12.0));
@end

我们可以找到,有两个函数可以提供加载一个mlmodel模型,并且返回一个MLModel类。而从MLModel的函数可以得出,这个类,就是我们需要的推理类。到此,我们得到了可以通过用url的方式来加载一个模型的函数,但是我们都知道,iOS在编译的时候,会把mlmodel编译为mlmodelc,那我们实际要怎么完成这一步呢?继续查阅相关代码,我们找到了相关的函数:

/*!
 * MLModel (MLModelCompilation)
 *
 * Class with utilties for performing .mlmodel compilation
 */
@interface MLModel (MLModelCompilation)

/*!
 * Compile a .mlmodel for this device
 *
 * @param modelURL URL file path to .mlmodel file you wish to compile
 * @param error Any errors are surfaced here
 *
 * @returns a URL to the compiled .mlmodelc directory if successful
 * The model is compiled to a temporary location on disk
 * You must move the compiled model to a permenant location if you wish to keep it
 *
 * @discussion the returned model can be loaded using:
 * @code
 * [MLModel modelWithContentsOfURL:error:]
 * @endcode
 *
 */
  (nullable NSURL *)compileModelAtURL:(NSURL *)modelURL
                                error:(NSError **)error
                                API_AVAILABLE(macos(10.13), ios(11.0), tvos(11.0)) __WATCHOS_UNAVAILABLE;

@end
学新通

到此,我们已经可以尝试加载一个本地路径的mlmodel转化为MLModel类了。我们封装一下:

- (void)loadModelUrl:(NSString*)path modelName:(modelName) {
    NSError* error = nil;
    NSURL* fileUrl = [NSURL fileURLWithPath:path];
    if (fileUrl == nil) {
        return;
    }
    
    // 编译mlmodel
    NSURL* tempModelUrl = nil;
    if (@available(iOS 11.0, *)) {
        tempModelUrl = [MLModel compileModelAtURL:fileUrl error:&error];
    }
    
    if (error || tempModelUrl == nil) {
        NSLog(@"load model error with url:%p error:%p", error, tempModelUrl);
        return;
    }

    // 将临时目录拷贝到目标目录
    NSString* directorPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
    NSString* modelPath = [directorPath stringByAppendingPathComponent:modelName];
    
    NSFileManager* fileManager = [NSFileManager defaultManager];
    // 删除原始文件
    if ([fileManager fileExistsAtPath:modelPath]) {
        [fileManager removeItemAtPath:modelPath error:&error];
        if (error) {
            NSLog(@"remove file error with(%s)", [modelPath UTF8String]);
        }
    }
    
    // 更新文件
    [fileManager moveItemAtURL:tempModelUrl toURL:[NSURL fileURLWithPath:modelPath] error:&error];
    if (error) {
        NSLog(@"copy temp(%s) to modelPath(%s) error", [tempModelUrl.absoluteURL.path UTF8String], [modelPath UTF8String]);
        return;
    }
    
    // modelPath即为本地mlmodelc路径,我们可以使用它来加载模型
    _modelInstance = [MLModel modelWithContentsOfURL:modelPath error:&error];
    // todo some thing
}
学新通

编写模型的输入

@interface MNISTClassifierInput : NSObject<MLFeatureProvider>
@property (readwrite, nonatomic) CVPixelBufferRef image;
@end

@implementation MNISTClassifierInput
@synthesize featureNames = _featureNames;
- (void)setFeatureNames(NSSet<NSString*>*)featureNames {
    _featureNames = featureNames;
}

- (NSSet<NSString*>*)featureNames {
    return _featureNames;
}

- (nullable MLFeatureValue *)featureValueForName:(nonnull NSString *)featureName {
    for (NSString* name in _featureNames) {
        if ([name isEqualToString:featureName]) {
            // todo same thing
        }
    }
    return nil;
}
@end
学新通

这是一个通用的模版,几乎所有的MLModel的输入输出都可以这么编写,只需要在@select(featureValueForName:)实现相关的数据返回,满足模型的输入和输出即可。但对于输出,我们无需去编写这样的一个类,只需要将返回值标记为id即可,类似

// prediction
id<MLFeatureProvider> output = [_enhance predictionFromFeatures:input error:error];
// 获取第一个feature的输出,实际上也可以获取你需要的输出,只需要通过字符串即可映射出来。
NSArray* featureNamesWithOutput = output.featureNames.allObjects;
NSString* featureName = featureNamesWithOutput[0];
MLFeatureValue* value = [output featureValueForName:featureName];
if (value == nil) {
    return NO;
}

到此,整个流程已经串通。

网络下载mlmodel

NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
[data writeToFile:filePath atomically:YES];

总结

我们来梳理下,总结下,对于一个在线模型,我们需要怎么使用MLCore进行端上增强呢?

  1. 下载资源,可参考"网络下载mlmodel"
  2. 将模型从mlmodel编译为mlmodelc,可以参考"模型加载"
  3. 编写输入类/接受输出类,用于MLModel的模型输入,可参考"编写模型的输入"

参考文献

https://www.jianshu.com/p/c86d14c87ead


这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhifkjkg
系列文章
更多 icon
同类精品
更多 icon
继续加载