2018.09.20

機械学習フレームワークTuri Createを使ってアクティビティを判別する

Pocket

こんにちは。F.S.です。
今回はAppleが提供している機械学習フレームワークTuri Createで、モーションセンサーのインプットをもとに身体的なアクティビティを判別するActivity classificationを試してみます。

Activity classification · GitBook より抜粋

ユーザーガイドにしたがって、iPhoneの加速度センサーとジャイロセンサーの値をもとに「歩いてる」とか「階段を上っている」などの動作を推定するプログラムを動かしてみます。

  • 開発環境
    • MacBook Air (13-inch, 2017)
    • macOS 10.13.4
    • Python 3.6.5(Anaconda 5.2)
    • Turi Create 5.0
    • Xcode 9.3.1
  • 実機テスト環境
    • iPhone 7
    • iOS 11.3

Turi Createの概要


Turiはもともと機械学習のプロダクトを手がけるシアトルのスタートアップで、Appleにより2016年に買収されました。Turi Createは彼らのプロダクトをベースにAppleがOSSとして公開したものです。
WWDC2018にてデモンストレーションが実施されています。
Turi Createの特徴は、アルゴリズムよりもタスクベース(レコメンドや物体検出など)のモジュールを使って手短に学習モデルを作成できるというところにあります。
また、Appleから提供されているだけあってCore MLフォーマットへのエクスポートを標準でサポートしており、iOS等で容易に利用することができます。
(Turi CreateそのものはCore ML専用という訳ではなく、Pythonが動くx86_64アーキテクチャの環境であれば利用可能です)

なお、Core MLは機械学習モデルをアプリで利用するためのライブラリで、iOS 11から導入されたものですね。

Activity Classification

冒頭にもあったように、Activity Classificationはモーションセンサーで取得したデータをもとにあらかじめ定義されたアクティビティ(止まってる、動いてるまたはジェスチャーなど)に振り分ける分類器です。内部的にはLSTMネットワークを用いて時系列データの依存関係でアクティビティを判別しているようです。

Activity classification · GitBook より抜粋

LSTMについてはこちらを参考ください

実装

こちらのAppleのガイドをもとに進めていきます。
APIの詳細が知りたい場合はこちらのドキュメントを参照ください。

モデル作成

基本、ガイド通りですので詳細は記載しませんが、大まかな流れは下記の通りです。

1. 加速度センサー、ジャイロセンサーのデータをSFrameにロードし、必要なデータに整形する

SFrameはTuri Createの表形式のデータフレームオブジェクトです。CSV、テキスト、JSONなどからデータを読み込むことができ、列指向のため列の追加削除が容易に行えます。
今回扱うデータは30人の被験者、合計61の計測(セッション)データで、1セッションの中にいくつかのアクティビティ(歩く、座る、階段を登るなど)がラベリングされています。データのアクティビティは12種類ありますが、ガイドではそのうち6種類のみをフィルタリングして使用しています。計測データは1セッションあたり1,500〜2,000フレーム、50fpsのため30〜40秒の活動記録となります。

本ガイドにおける最終的なSFrameの内容は次のようなものになります。

2. activity_classifierモジュールのユーティリティメソッドでSFrameのデータを学習用(train)、テスト用(test)に分ける

train, test = tc.activity_classifier.util.random_split_by_session(data, session_id='exp_id', fraction=0.8)


exp_idは一連のActivityを識別するIDです。
fraction=0.8により、SFrameのデータの80%をtrainデータ、残りをtestデータとして分割します。

3. activity_classifierモジュールの作成メソッドでtrainデータからActivityClassifierモデルを作成する

model = tc.activity_classifier.create(train, session_id='exp_id', target='activity', prediction_window=50)


1秒間隔で予測するために、元データが50fpsなのでprediction_window=50を指定します。

実行結果
The dataset has less than the minimum of 100 sessions required for train-validation split. Continuing without validation set

Pre-processing 589440 samples...

Using sequences of size 1000 for model creation.

Processed a total of 48 sessions.

Using CPU to create model
+----------------+----------------+----------------+----------------+
| Iteration      | Train Accuracy | Train Loss     | Elapsed Time   |
+----------------+----------------+----------------+----------------+
| 1              | 0.630          | 0.974          | 1.6            |
| 2              | 0.800          | 0.553          | 3.2            |
| 3              | 0.850          | 0.418          | 4.4            |
| 4              | 0.867          | 0.363          | 5.6            |
| 5              | 0.883          | 0.323          | 6.8            |
| 6              | 0.896          | 0.279          | 7.9            |
| 7              | 0.903          | 0.262          | 9.1            |
| 8              | 0.916          | 0.230          | 10.3           |
| 9              | 0.920          | 0.215          | 11.5           |
| 10             | 0.926          | 0.197          | 12.7           |
+----------------+----------------+----------------+----------------+
Training complete
Total Time Spent: 12.6999s

4. ActivityClassifierモデルの評価メソッドでtestデータを用いてモデルを評価する

metrics = model.evaluate(test)
print(metrics['accuracy'])
精度は次の結果が得られました。
0.890511178490998

Core MLモデルへのエクスポート

モデルのエクスポートメソッドでCore MLフォーマットに書き出します。
model.export_coreml('MyActivityClassifier.mlmodel')
作成したモデルをXcodeで開くと、次のようなCore MLモデルになっているのがわかります。


アルゴリズムを気にせず、とはいえLSTMのhidden state(ワーキングメモリ)、cell state(長期記憶領域)がモデルのインターフェースに含まれています。LSTMとはなんぞやくらいの知識は持っていたいですね。実際はhiddenOut, cellOutを次の予測のhiddenIn, cellInに渡してあげるだけなんですけど。

iOSアプリの実装

作成したCore MLのモデルを使い、iOSアプリで利用してみます。ここからXcodeでの実装になります。
ガイドの実装は解説を簡単にするために加速度センサーしか取得していませんが、それだとまともな結果が得られなかったので、モデルの入力値である加速度+ジャイロのデータが使えるように下記のように一部のコードを変更します。
50fpsで加速度センサー、ジャイロセンサーの値を参照し、1秒(50フレーム)ごとに予測結果をデバッグコンソールに出力するだけのプログラムです。
    override func viewDidLoad() {
        super.viewDidLoad()

        motionManager.accelerometerUpdateInterval = TimeInterval(ModelConstants.sensorsUpdateInterval)
        motionManager.gyroUpdateInterval = TimeInterval(ModelConstants.sensorsUpdateInterval)
        
        // モーションデータをプッシュ型ではなく、プル型で加速度・ジャイロを同時に処理するためにタイマーを使う
        motionManager.startAccelerometerUpdates()
        motionManager.startGyroUpdates()
        timer = Timer.scheduledTimer(timeInterval: ModelConstants. sensorsUpdateInterval, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
    }

    // ガイドのaddAccelSampleToDataArrayに相当する部分
    @objc func update() {
        guard let accelerometerData = motionManager.accelerometerData else {return}
        guard let gyroData = motionManager.gyroData else {return}
        guard let dataArray = predictionWindowDataArray else { return }
        dataArray[[0 , currentIndexInPredictionWindow ,0] as [NSNumber]] = accelerometerData.acceleration.x as NSNumber
        dataArray[[0 , currentIndexInPredictionWindow ,1] as [NSNumber]] = accelerometerData.acceleration.y as NSNumber
        dataArray[[0 , currentIndexInPredictionWindow ,2] as [NSNumber]] = accelerometerData.acceleration.z as NSNumber
        dataArray[[0 , currentIndexInPredictionWindow ,3] as [NSNumber]] = gyroData.rotationRate.x as NSNumber
        dataArray[[0 , currentIndexInPredictionWindow ,4] as [NSNumber]] = gyroData.rotationRate.y as NSNumber
        dataArray[[0 , currentIndexInPredictionWindow ,5] as [NSNumber]] = gyroData.rotationRate.z as NSNumber
        
        currentIndexInPredictionWindow += 1
        
        if (currentIndexInPredictionWindow == ModelConstants.predictionWindowSize) {
            let predictedActivity = performModelPrediction() ?? "N/A"
            
            print(predictedActivity)
            
            currentIndexInPredictionWindow = 0
        }
    }

結果

iPhoneを一定のリズムで上下させてみた際のコンソール出力結果です。
N/A
laying
laying
laying
laying
laying
laying
laying
laying
laying
laying
laying
climbing_upstairs
climbing_upstairs
climbing_downstairs
climbing_downstairs
climbing_downstairs
climbing_downstairs
climbing_downstairs
climbing_downstairs
climbing_downstairs
climbing_upstairs
climbing_upstairs
climbing_upstairs
climbing_upstairs
climbing_upstairs
climbing_downstairs
climbing_downstairs
climbing_downstairs
climbing_downstairs
climbing_upstairs
climbing_upstairs
laying
laying
laying
最初は動きがない状態(laying)から階段を登る動作に移っていると判別されています。
意識的に上方向に向かうように上下させたときにupstairs、逆に下方向でdownstairsとなるところが、それらしさを感じました。

まとめ

Turi CreateのActivity Classificationをガイドに沿って実装し、Core MLモデルを実機で動かしてみました。アルゴリズムやパラメータを選択することなく、やりたいことに適合するモジュールを使って学習モデルを作成するというTuri Createの特徴を理解することができました。
モデルの作成は簡潔に実現できましたが、データセットができるまでのデータ整形作業がコードの大部分を占めているというのは機械学習の宿命ですかね。

それでは、また。

次世代システム研究室では、アプリケーション開発や設計を行うリードエンジニアを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。