LOADING

React NativeアプリでiOSのCore MLを使ってみた

こんにちは、ウォズ株式会社の有吉です。

弊社ではiOSアプリのプロトタイプ開発をする時は、ネイティブ (Swift) かReact Nativeでアプリを開発しています。

ネイティブで開発しているiOSアプリからCore MLを使う記事は世の中に沢山ありますが、今回はReact Nativeで開発しているアプリからCore MLを利用する方法について調べてみました。

※WWDC2019関連の新ネタは本題ではありませんが、少しだけ触れています

利用した技術

概要だけ、おさらいします。

React Native

JavaScript / React を使ってiOSアプリやAndroidアプリが開発できるフレームワークです。先日発表されたSwift UIの比較対象に挙げられがちですが、Android アプリも開発できるということや、JavaScript (WEB開発) の資産を持ち込んでアプリ開発が出来るという点が異なります。

Core ML

iOSアプリに機械学習モデルを組み込むための仕組みです。Core MLを利用すると、iOS端末内で機械学習モデルを利用することができます。クラウド (サーバーサイド) ではなく端末側での処理となるため、処理時間・通信量、プライバシー保護、サーバー費用・負荷分散の面で有利です。後述のCreate MLで作成したモデルだけではなく、Keras などのライブラリで作成したモデルを変換して利用することもできます。

Create ML

Core MLで利用するための機械学習モデルを、ほぼドラッグ&ドロップだけで作成できる仕組みです。画像分類モデル、テキスト分類モデルなどが作成可能です。今後は、物体検出、音声分類、推薦、行動分類のテンプレートも追加されるようです。転移学習を活用しており、データセットの量が少なくても良い結果を出し、モデルサイズを削減できると謳われています。

 

今回作ったアプリ

画像を分類するiOSアプリです。

画像を入力すると、機械学習モデルによる予測が行われ、予測結果としてidentifier (分類されたクラス) と confidence (予測の確からしさ) が出力されます。

今回作ったモデルでは、”coffee”, “tea” “beer” の3種類の飲み物のうちいずれかに写真が分類されます。

ミルクティーに見えるかもしれない飲み物が、上手くコーヒーに分類されていますね!

モデルを組み込んだReact Native アプリのコードをGithub に置いたので、詳細はそちらでご確認ください。

※とりあえず組み込むために作ったモデルのため、分類精度については無視してください。タピオカも対象外です。

 

実装の仕方

大まかには3ステップあります。

  • Create ML で画像分類モデルを作る
  • ネイティブ側でモデルを利用するコードを書き、ネイティブモジュールを作る
  • React Naitve 側からネイティブ側のコードを呼び出す

順番に見ていきます。

Create ML で画像分類モデルを作る

Xcode を起動して、Blank のPlayground を作成してコードを記述します。

import CreateMLUI
let builder = MLImageClassifierBuilder()
builder.showInLiveView()

Playground を実行するとGUIが表示されます。

データセットとして、複数の画像ファイルをトレーニング用とテスト用に分けておきます。

ディレクトリ構成はこのようにしました。


dataset/ ├── test │   ├── beer │   ├── coffee │   └── tea └── train ├── beer ├── coffee └── tea

後は train ディレクトリをGUIにドラッグ&ドロップして、モデルを作成します。

test ディレクトリ を与えると、モデルの精度が検証されます。

最後に、作成したモデルを書き出します。今回は ImageClassifier.mlmodel というファイル名にしました。

ネイティブ側でモデルを利用するコードを書き、ネイティブモジュールを作る

Core ML が関わる箇所についてはXcodeで作業をする必要があります。

React Native でexpo を使う場合はdetach かeject をしないとXcode プロジェクトが触れないようなので、今回は $ react-native init YourProject コマンドでアプリを作成しました。

 

Xcode プロジェクトにファイルを追加します。

  • ImageClassifier.mlmodel (さっき作成したもの)
  • CoreMLImageClassifierSample-Bridging-Header.h
  • FoodClassifier.swift
  • FoodClassifierBridge.m

 

後はコードを書いていきます。

React Native に関わっていると、Objective-C のコードを読むことになりがちですが、自分がコードを書く時はSwift を使いたいです。CoreMLImageClassifierSample-Bridging-Header.h ファイルを追加しています。

// CoreMLImageClassifierSample-Bridging-Header.h
#import <React/RCTBridgeModule.h>
#import "ImageClassifier.h"

FoodClassifierBridge.m によって、 predict メソッドをReact Native 側に公開します。

// FoodClassifierBridge.m
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(FoodClassifier, NSObject)

RCT_EXTERN_METHOD(predict:(NSString *)source findEventsWithResolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)

@end

predict の中身はFoodClassifier.swift に記述します。

// FoodClassifier.swift
import Foundation
import Vision
import CoreML

@objc(FoodClassifier)
class FoodClassifier: NSObject {
  @objc static func requiresMainQueueSetup() -> Bool {
    return false // 警告が出るので、今回は明示的にfalse
  }
  
  @objc(predict:findEventsWithResolver:rejecter:)
  func predict(source: String, resolve: @escaping RCTPromiseResolveBlock, reject: RCTPromiseRejectBlock) -> Void {
    guard let model = try? VNCoreMLModel(for: ImageClassifier()!.model!) else { fatalError() }
    let request = VNCoreMLRequest(model: model) { request, error in
      guard let results = request.results as? [VNClassificationObservation] else { fatalError() }
      if let classification = results.first {
        resolve([
          "identifier": classification.identifier,
          "confidence": classification.confidence
          ])
        return
      } else { fatalError() }
    }
    
    let imageURL = URL(string: source)
    guard let ciImage = CIImage(contentsOf: imageURL!) else { fatalError() }
    
    let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
    guard (try? handler.perform([request])) != nil else { fatalError() }
  }
}

<code>VNCoreMLModel</code> でモデルを読み込み、VNImageRequestHandler で画像データを扱ってリクエストを実行するという内容です。VNCoreMLRequest に予測結果を扱う処理を記述しています。import している Vision はiOSの画像解析フレームワークで、VN~ はそのクラスです。

予測した結果は resolve を使ってReact Native 側に返します。

 

これでネイティブ側の実装が出来ました。あとはReact Native のJavaScript で、ネイティブモジュールを呼び出すだけです。

 

React Naitve 側からネイティブ側のコードを呼び出す

ネイティブモジュールが使えるようにします。

import { NativeModules, ... }  from "react-native";

const foodClassifier = NativeModules.FoodClassifier;

予測処理を実行するコードです。 predict を呼ぶだけです。

predict = imageUri => {
  foodClassifier.predict(imageUri).then(result => {
    this.setState({
      identifier: result.identifier,
      confidence: result.confidence,
    });
  });
};

result に予測結果が格納されているので、後はUIで表示するなり何とでもできます。

所感

React NativeアプリでCore MLを使って画像分類を行う実装をすることができました。

思ったより楽に組み込みが出来たな、という印象です。ネイティブブリッジに関するコードはほとんど書かずに済みました。。

今回実装したのは単純なアプリですが、(WWDC2019のタイミングで公式配布モデルに追加されたYOLOのような) 画像のリアルタイム検出を行うモデルを動かしたい場合は、ネイティブ側で行いたい処理が多くなりそうな予感がします。その際はReact Nativeではなくネイティブ側でUIコンポーネントを実装する必要があるかもしれません。また機会があれば検証しようと思います。

まとめ

今回はiOSのCore MLの機能を利用する、React Nativeのサンプルアプリを実装してみました。

Core MLを使いたい場面というのはネイティブでiOSアプリを開発する時にしか出てこなそうな気もしますが、もし「React Nativeのアプリを既に運用していて、どうしてもCore MLを使って検証したい」ということがあった場合にも、なんとかなりそうということが分かりました。

WWDC2019のタイミングではCore ML 3が発表されました。今後はCore MLやCreate MLを使って出来ることも増えていきそうで、楽しみです。

 

さいごに

ウォズ株式会社では、React Nativeでのアプリ開発やプロトタイプ制作の案件をお受けしています。開発したいアプリや検証をしたいプロジェクトがありましたらご相談ください。

参考