機械学習モデルとモバイルDBを統合するCouchbase Lite 予測クエリ 紹介

# はじめに

この記事では、モバイル端末用の組込NoSQLデータベースである、Couchbase Lite 2.7にてGAとなった「予測クエリ Predictive Query」について、概要を記します。

様々なサービスやソフトウェアが、その新機能として「AI」との関連性を喧伝する中、機能の名称のみからその実体を推し量ることは時に難しく、そんな場合人は多大な期待を抱いてしまうものです。

ここで紹介する機能が具体的にどういうものであるのかを、登場した背景などとの関係を踏まえて、整理していきたいと思います。

## エッジAI

機械学習、深層学習の成果が実用化される時、多くの場合、そのサービスは、クラウドとのやりとりで実現されてきたといって良いでしょう。例えば、スマートスピーカは、音声データがクラウドに送られることで成り立っています。一方、iPhoneのFace IDでの顔認識モデルは、端末上で実現されています。このような端末上での「AI」機能は、エッジAI（※）と言われています（対する前者は、クラウドAIと言われます）。

※「エッジ」という単語についても、前提抜きに（一般用語として）使うのは不親切かもしれません。IoTの隆盛の中、「エッジ」は、　IoTデバイスの同義としても使われていますが、「エッジ・コンピューティング」という、「クラウド・コンピューティング」への対比となる用法もあり、この意味では、必ずしも「エッジ」は、「モバイル」（を含むとしても）そのものではなく、クラウド/中央データセンターに対して、様々なエッジデバイス上での、あるいは拠点（ローカルネットワーク、例えば、工場にあるサーバ）におけるコンピューティングという含意も持ちます。

端末の認証という目的の場合は、オフライン環境での利用という実際的な必要性がありますが、エッジAIの進歩において語られることが多いのは、セキュリティやプライバシーの観点だといえます。その意味で、Federated Learningのように、モデルの「学習」のためのテクノロジーも重要である一方、学習済みのモデルを使った「推論」を、エッジにて完結することは、より直接的な目的・意図と言えます。

Face IDについては、Apple社独自のハードウェア/ソフトウェアにより実現されていますが、このようなニーズや背景を踏まえ、エッジAIを実現するための、オープンソースのソフトウェアライブラリが登場しています。

Couchbase Liteの「予測クエリ Predictive Query」も、こうした「エッジ（主にモバイル）」での、「学習済みモデルを使った推論」のためのものであり、「オープンソースソフトウェアライブラリ」と本質的な関係があります。

# Couchbase Mobile

Couchbase Mobileは、組込NoSQLデータベースであるCouchbase Liteと、Couchbase LiteとCouchbase Serverとの双方向の同期を実現するSync Gatewayからなる全体のパッケージを指していますが、予測クエリ自体は、Couchbase Liteの機能として完結していますので、ここではCouchbase Liteにフォーカスします。

## Couchbase Lite 

Couchbae Liteは、組込NoSQLデータベースとして、以下のプログラミング環境で利用可能です。

- Android/Java 
- Objective-C
- Swift
- C#.Net
- JavaScript

また、Couchbase Mobile 2.7以降は、Android以外のJAVAでも（初期化関数以外は、Andoroidと同じAPIを用いて）利用できるようになっています。この辺りは、モバイル端末以外の、Javaの利用できるエッジ・デバイスを射程に入れたアップデートと言えるかと思います。「予測クエリ」のGAとともに、サポートが開始されているというのも、興味深いところです。

Couchbase Liteは、ドキュメント指向のNoSQLデータベースであり、データをJSONとして格納します。

# 予測クエリ Predictive Query

## 参照情報

予測クエリに関する情報は多くありませんが、以下のものが存在しています。

### ドキュメンテーション
[Predictive Query
](https://docs.couchbase.com/couchbase-lite/current/java/learn/java-query-predictive.html)

### Blog
[Machine Learning Predictions in Mobile Apps with Couchbase Lite Predictive Query](https://blog.couchbase.com/machine-learning-predictions-couchbase-lite-predictive-query/)

## 予測クエリによって実現できること

「予測クエリ Predictive Query」を使うことで、Couchbase Liteデータベースに格納されたデータと、機械学習モデルとを容易に組み合わせることができるようになります。

つまりこれは、何らかの予測モデル（AIの実装）そのものを提供するものではなく、Couchbase Liteの上記のプログラミング環境で利用可能な、（SwiftのCoreMLや、AndroidのNNAPIのような）エッジAIライブラリーと組み合わせて利用するものです。

この機能により、Couchbase Liteデータベースのデータ(Blobのイメージデータや、テキスト、数値)へ機械学習モデルによる推論を適用する際に、データ個々に対してコードを記述する必要がなくなります。言い換えれば、「データベースに対して、モデルをアタッチすることで、データセットに対して推論結果を付与する」というような統合的な表現が可能になります。



# 実装解説

ここからは、Couchbase Labsで公開されている、サンプルプログラムを使って、具体的な実装を解説します。

https://github.com/couchbaselabs/couchbase-lite-predictive-query-examples

このサンプルでは、Couchbase LiteにBlobとして格納されている画像データに対して、分類器(Classifier)モデルを適用し、画像の分類を行っています。（なお、現在、サンプルプログラムとしてSwiftによる実装のみ、公開されています。）

解説の粒度としては、これまでの機能紹介を補完するために、全体の概観を示すに留めています。
（開発者観点から、より詳細・具体的に理解するためには、是非サンプルプログラムを実行する等して、補っていただければ幸いです）

## 実装の流れ

実装の流れは、概略以下のようなものです。

- `PredictiveModel`インターフェイスを実装する
- `Database.prediction.registerModel`メソッドを使って、実装したクラスを登録する
- インデックスを作成する。この際、以下の２通りの方法がある。
 - バリューインデックス: ドキュメントが追加された時に、予測メソッドを呼び出す。
 - 予測インデックス: 予測結果のキャッシュを作成する。
- 予測クエリを実行する

## モデル実装

シングルトンとして、CoreMLを使った分類モデルを定義。

```Swift
import Foundation
import UIKit
import CouchbaseLiteSwift
import CoreML
let kPredictionCategory = "label"
let kPredictionProbability = "labelProbability"

class ItemClassifierCoreMLPredictiveModel {
    class var name: String {
        return "ItemClassifier"
    }

    enum outFeatureNames:String {
        case kPredictionCategory = "label"
        case kPredictionProbability = "labelProbability"
    }
    
    var inputFeatureNames:[String] = ["image"] // Just get this from the OpenFace model description
    
    static let SharedInstance:ItemClassifierCoreMLPredictiveModel = {
        let instance = ItemClassifierCoreMLPredictiveModel()
        return instance
        
    }()
    
    var model:MLModel {
        return ItemClassifier.init().model
        
    }
    
    // Don't allow instantiation . Enforce singleton
    private init() {
        
    }
    
}
```

インスタンス作成箇所。

```Swift

    fileprivate var coreMLPredictiveModel = {
        return CoreMLPredictiveModel(mlModel: ItemClassifierCoreMLPredictiveModel.SharedInstance.model)
    }()
```

## モデル登録


予測モデルを登録する関数。

```Swift
    func registerPredictionModel() {
        print(#function)
        // add transformation function
        
        // This input transformation is not really required. This is just for testing purposes- I forced the
        // input to be different than what is expected 
        coreMLPredictiveModel.inputTransformer = { (input) in
            let val = input.value(forKey: input.keys[0])
             let transformed = [ItemClassifierCoreMLPredictiveModel.SharedInstance.inputFeatureNames[0]:val]
            return MutableDictionaryObject(data: transformed)
            
        }
        coreMLPredictiveModel.outputTransformer = { (output) in
            guard let result = output else {
                return nil
            }
            
            let label = result.string(forKey: kPredictionCategory)!
            let prob = result.dictionary(forKey: kPredictionProbability)?.value(forKey: label)
            
            let modifiedResult = [ kPredictionCategory: label, kPredictionProbability: prob]
            return MutableDictionaryObject(data: modifiedResult)
        
        }
        
        Database.prediction.registerModel(coreMLPredictiveModel, withName: ItemClassifierCoreMLPredictiveModel.name)
    }
```

最後の以下の部分で、データベースに予測モデルを名前（`ItemClassifierCoreMLPredictiveModel.name`=`ItemClassifier`）をつけて登録している。

```Swift
Database.prediction.registerModel(coreMLPredictiveModel, withName: ItemClassifierCoreMLPredictiveModel.name)`
```

## 予測インデックス登録

```Swift
    func createPredictiveIndexOnProperties(_ properties:[String])->Bool  {
        print(#function)

    // For searches on type property
        guard let db = db else {
            print ("Database is not initialized")
            return false
        }
        let model = ItemClassifierCoreMLPredictiveModel.name
        guard let propName = properties.first else {
            return false
        }
      
        let inputFeatureName = ItemClassifierCoreMLPredictiveModel.SharedInstance.inputFeatureNames[0]
        let input = Expression.value([propName: Expression.property(propName)])
        
        let index = IndexBuilder.predictiveIndex(model: model, input: input)
        do {
            try db.createIndex(index, withName: "\(propName)Index")
            return true
        }
        catch {
            print (error)
            return false
        }
    }
```

## 予測実行

```Swift
func useRegisteredModelToFindMatchingItemsInDBToInputImage(_ imageData:Data, imageName:String, propertyName:String)->(userRecords:[UserRecord]?,status:Bool) {
            print(#function)
            guard let db = db else {
                fatalError("Database not initialized")
                return (nil,false)
            }
    
            let modelName = ItemClassifierCoreMLPredictiveModel.name
            let inputFeatureName = ItemClassifierCoreMLPredictiveModel.SharedInstance.inputFeatureNames[0]
        
            // DO prediction on input param
            let inputPhotoParam = Expression.dictionary([propertyName : Expression.parameter(imageName)])
            let inputImagePrediction = Function.prediction(model: modelName, input: inputPhotoParam)
        
            let categoryKeyPath = ItemClassifierCoreMLPredictiveModel.outFeatureNames.kPredictionCategory.rawValue
            let probabilityKeyPath = ItemClassifierCoreMLPredictiveModel.outFeatureNames.kPredictionProbability.rawValue
        
        
            // Find matching category as long as the match has a probability of > 0.7
    
               let query = QueryBuilder
                .select(SelectResult.all(),SelectResult.expression(inputImagePrediction).as("PredictionResult"))
                .from(DataSource.database(db))
                .where(inputImagePrediction.property(categoryKeyPath)
                            .equalTo(Expression.property(UserRecordDocumentKeys.tag.rawValue))
                        .and(inputImagePrediction.property(probabilityKeyPath))
                            .greaterThanOrEqualTo(Expression.double(0.7)))
       
            // Set input parameters for query
            let params = Parameters()
        params.setBlob(Blob.init(contentType: "image/jpg", data: imageData), forName: imageName)
            query.parameters = params
    
            // Execute query
          //  print(try? query.explain())
            var results:[UserRecord] = [UserRecord]()
    
            do {
                //   print ("results are ...")
                for result in try query.execute() {
                     //  print("Distance  is :\(result.toDictionary())")
                    let resultVal = result.dictionary(forKey: "items")
                    print(result.toDictionary())
                    let matchDetails = result.dictionary(forKey: "PredictionResult")
                    let matchProbability = matchDetails?.value(forKey: probabilityKeyPath)
                    //print("Found \(resultVal?.count) matches")
                    if let name = resultVal?.string(forKey:  UserRecordDocumentKeys.tag.rawValue), let image = resultVal?.blob(forKey:  UserRecordDocumentKeys.photo.rawValue)?.content {
                        let tagAndMatch = "\(name) : \(matchProbability!)"
                        let user = UserRecord.init(tag: tagAndMatch, photo: image, extended: nil)
                        // print("\(name):user")
                        results.append(user)
                    }
                }
    
            }
            catch {
                print("Error in query execution \(error)")
                return (nil,false)
            }
            return (results,true)
    }
```


## 最後に

先に、予測クエリで実現できることとして、「データベースに対して、モデルをアタッチすることで、データセットに対して推論結果を付与する」という統合的な表現が可能、と書きました。一方、鳥瞰的に考えてみると（さらに抽象化を進めると）、ここでアタッチするものが予測モデル＝推論関数であることは、技術的には本質的ではなく、何らかの任意の関数であっても良いはず、ということに思いが至ります。

そうしたことを踏まえる時、逆に照射されるところとして、単に抽象的な技術的実装からの発想ではなく、エッジAIという具体的なニーズ（今後のモバイル・エッジ端末の機能として大きな部分を占めることになるであろうもの）こそが、このような機能を導き出した、ということが言えるのではないかと思います。

## 参考情報

 エッジでの推論にフォーカスしたEdge AIに関する網羅的な解説として、以下の記事があります。
[今日から始める Edge AI](https://qiita.com/cvusk/items/c89db530548e3e105ecb)

Federated Learning等、エッジでの学習についての情報源として、Udacityの提供する下記のフリーコースがあります。
[Secure and Private AI](https://www.udacity.com/course/secure-and-private-ai--ud185)