AppleプラットフォームでカスタムTensorFlowLiteモデルを使用する

アプリでカスタムTensorFlowLiteモデルを使用している場合は、FirebaseMLを使用してモデルをデプロイできます。 Firebaseを使用してモデルをデプロイすることで、アプリの初期ダウンロードサイズを縮小し、アプリの新しいバージョンをリリースせずにアプリのMLモデルを更新できます。また、リモート構成とA / Bテストを使用すると、さまざまなモデルをさまざまなユーザーセットに動的に提供できます。

前提条件

  • MLModelDownloaderライブラリは、Swiftでのみ使用できます。
  • TensorFlow Liteは、iOS9以降を使用するデバイスでのみ実行されます。

TensorFlowLiteモデル

TensorFlow Liteモデルは、モバイルデバイスで実行するように最適化されたMLモデルです。 TensorFlow Liteモデルを取得するには:

あなたが始める前に

TensorFlowLiteをFirebaseで使用するには、CocoaPodsを使用する必要があります。TensorFlowLiteは現在、SwiftPackageManagerでのインストールをサポートしていないためです。 MLModelDownloaderのインストール方法については、 MLModelDownloaderインストールガイドを参照してください。

インストールしたら、FirebaseとTensorFlowLiteをインポートして使用します。

迅速

import FirebaseMLModelDownloader
import TensorFlowLite

1.モデルをデプロイします

FirebaseコンソールまたはFirebaseAdminPythonおよびNode.jsSDKを使用して、カスタムTensorFlowモデルをデプロイします。カスタムモデルの展開と管理を参照してください。

Firebaseプロジェクトにカスタムモデルを追加した後、指定した名前を使用してアプリでモデルを参照できます。 getModel()を呼び出すことで、いつでも新しいTensorFlow Liteモデルをデプロイし、新しいモデルをユーザーのデバイスにダウンロードできます(以下を参照)。

2.モデルをデバイスにダウンロードし、TensorFlowLiteインタープリターを初期化します

アプリでTensorFlowLiteモデルを使用するには、まずFirebaseMLSDKを使用してモデルの最新バージョンをデバイスにダウンロードします。

モデルのダウンロードを開始するには、モデルダウンローダーのgetModel()メソッドを呼び出し、アップロード時にモデルに割り当てた名前、常に最新のモデルをダウンロードするかどうか、およびダウンロードを許可する条件を指定します。

次の3つのダウンロード動作から選択できます。

ダウンロードタイプ説明
localModelデバイスからローカルモデルを取得します。使用可能なローカルモデルがない場合、これはlatestModelのように動作します。モデルの更新を確認することに興味がない場合は、このダウンロードタイプを使用してください。たとえば、Remote Configを使用してモデル名を取得していて、常に新しい名前でモデルをアップロードしている(推奨)。
localModelUpdateInBackgroundデバイスからローカルモデルを取得し、バックグラウンドでモデルの更新を開始します。使用可能なローカルモデルがない場合、これはlatestModelのように動作します。
latestModel最新モデルを入手してください。ローカルモデルが最新バージョンの場合は、ローカルモデルを返します。それ以外の場合は、最新モデルをダウンロードしてください。この動作は、最新バージョンがダウンロードされるまでブロックされます(推奨されません)。この動作は、最新バージョンが明示的に必要な場合にのみ使用してください。

モデルがダウンロードされたことを確認するまで、モデル関連の機能(UIの一部をグレーアウトまたは非表示にするなど)を無効にする必要があります。

迅速

let conditions = ModelDownloadConditions(allowsCellularAccess: false)
ModelDownloader.modelDownloader()
    .getModel(name: "your_model",
              downloadType: .localModelUpdateInBackground,
              conditions: conditions) { result in
        switch (result) {
        case .success(let customModel):
            do {
                // Download complete. Depending on your app, you could enable the ML
                // feature, or switch from the local model to the remote model, etc.

                // The CustomModel object contains the local path of the model file,
                // which you can use to instantiate a TensorFlow Lite interpreter.
                let interpreter = try Interpreter(modelPath: customModel.path)
            } catch {
                // Error. Bad model file?
            }
        case .failure(let error):
            // Download was unsuccessful. Don't enable ML features.
            print(error)
        }
}

多くのアプリは初期化コードでダウンロードタスクを開始しますが、モデルを使用する前であればいつでもダウンロードできます。

3.入力データに対して推論を実行します

モデルの入力形状と出力形状を取得します

TensorFlow Liteモデルインタープリターは入力として受け取り、出力として1つ以上の多次元配列を生成します。これらの配列には、 byteintlong 、またはfloat値のいずれかが含まれます。モデルにデータを渡したり、その結果を使用したりする前に、モデルが使用する配列の数と次元(「形状」)を知っておく必要があります。

モデルを自分で作成した場合、またはモデルの入力と出力の形式が文書化されている場合は、すでにこの情報を持っている可能性があります。モデルの入力と出力の形状とデータ型がわからない場合は、TensorFlowLiteインタープリターを使用してモデルを検査できます。例えば:

Python

import tensorflow as tf

interpreter = tf.lite.Interpreter(model_path="your_model.tflite")
interpreter.allocate_tensors()

# Print input shape and type
inputs = interpreter.get_input_details()
print('{} input(s):'.format(len(inputs)))
for i in range(0, len(inputs)):
    print('{} {}'.format(inputs[i]['shape'], inputs[i]['dtype']))

# Print output shape and type
outputs = interpreter.get_output_details()
print('\n{} output(s):'.format(len(outputs)))
for i in range(0, len(outputs)):
    print('{} {}'.format(outputs[i]['shape'], outputs[i]['dtype']))

出力例:

1 input(s):
[  1 224 224   3] <class 'numpy.float32'>

1 output(s):
[1 1000] <class 'numpy.float32'>

インタプリタを実行する

モデルの入力と出力の形式を決定したら、入力データを取得し、モデルに適切な形状の入力を取得するために必要なデータに対して変換を実行します。

たとえば、モデルが画像を処理し、モデルの入力次元が[1, 224, 224, 3]浮動小数点値である場合、次の例のように、画像の色値を浮動小数点範囲にスケーリングする必要があります。 :

迅速

let image: CGImage = // Your input image
guard let context = CGContext(
  data: nil,
  width: image.width, height: image.height,
  bitsPerComponent: 8, bytesPerRow: image.width * 4,
  space: CGColorSpaceCreateDeviceRGB(),
  bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue
) else {
  return false
}

context.draw(image, in: CGRect(x: 0, y: 0, width: image.width, height: image.height))
guard let imageData = context.data else { return false }

var inputData = Data()
for row in 0 ..&lt; 224 {
  for col in 0 ..&lt; 224 {
    let offset = 4 * (row * context.width + col)
    // (Ignore offset 0, the unused alpha channel)
    let red = imageData.load(fromByteOffset: offset+1, as: UInt8.self)
    let green = imageData.load(fromByteOffset: offset+2, as: UInt8.self)
    let blue = imageData.load(fromByteOffset: offset+3, as: UInt8.self)

    // Normalize channel values to [0.0, 1.0]. This requirement varies
    // by model. For example, some models might require values to be
    // normalized to the range [-1.0, 1.0] instead, and others might
    // require fixed-point values or the original bytes.
    var normalizedRed = Float32(red) / 255.0
    var normalizedGreen = Float32(green) / 255.0
    var normalizedBlue = Float32(blue) / 255.0

    // Append normalized values to Data object in RGB order.
    let elementSize = MemoryLayout.size(ofValue: normalizedRed)
    var bytes = [UInt8](repeating: 0, count: elementSize)
    memcpy(&amp;bytes, &amp;normalizedRed, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&amp;bytes, &amp;normalizedGreen, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
    memcpy(&ammp;bytes, &amp;normalizedBlue, elementSize)
    inputData.append(&amp;bytes, count: elementSize)
  }
}

次に、入力NSDataをインタープリターにコピーして実行します。

迅速

try interpreter.allocateTensors()
try interpreter.copy(inputData, toInputAt: 0)
try interpreter.invoke()

インタプリタのoutput(at:)メソッドを呼び出すことにより、モデルの出力を取得できます。出力の使用方法は、使用しているモデルによって異なります。

たとえば、分類を実行している場合、次のステップとして、結果のインデックスをそれらが表すラベルにマップできます。

迅速

let output = try interpreter.output(at: 0)
let probabilities =
        UnsafeMutableBufferPointer<Float32>.allocate(capacity: 1000)
output.data.copyBytes(to: probabilities)

guard let labelPath = Bundle.main.path(forResource: "retrained_labels", ofType: "txt") else { return }
let fileContents = try? String(contentsOfFile: labelPath)
guard let labels = fileContents?.components(separatedBy: "\n") else { return }

for i in labels.indices {
    print("\(labels[i]): \(probabilities[i])")
}

付録:モデルのセキュリティ

TensorFlowLiteモデルをFirebaseMLで利用できるようにする方法に関係なく、FirebaseMLはそれらを標準のシリアル化されたprotobuf形式でローカルストレージに保存します。

理論的には、これは誰でもモデルをコピーできることを意味します。ただし、実際には、ほとんどのモデルはアプリケーション固有であり、最適化によって難読化されているため、リスクは競合他社がコードを逆アセンブルして再利用する場合と同様です。それでも、アプリでカスタムモデルを使用する前に、このリスクに注意する必要があります。