LoginSignup
2
2

More than 3 years have passed since last update.

SwiftでTensorflowLiteを試してみる

Posted at

はじめに

私用でTensorflowLiteを使いそうなので、iOSで使えるか試してみました。

環境

Python3.7
Swift 5.2.2
Xcode 11.4.1
iOS 13.4

TensorflowLiteとは

tensorflowlite intro.png

IoTデバイスやスマホなどで、Tensorflowで開発したモデルを利用することができるものらしいです。
詳細は良くわからないのですが、TensorflowLiteで学習してモデルを構築なんてことはできないそうですね。

公式のサンプル

一応、公式のサンプルプロジェクトがあります。
iOSのクイックスタート

動くは動くのだが....

1B91A8E3-3516-4491-955F-34DADA84B407_1_102_o.jpeg
サンプルプロジェクトはビルドすれば動くのですが、自分でTensorflowLiteを動かした感覚が皆無だったため、自分で作ったモデルをスホマで動くかを試してみました。

本編

1. Tensorflowでモデルを作る

まずは、スマホで使う機械学習モデルを作らなければなりません。
全コードは載せておきますが、細かい説明は省略します。
そもそも機械学習初心者なので、モデルは適当です。

サンプルコード
#%%

import tensorflow as tf
import tensorflow.keras as keras
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import math

#%%

pi = math.pi
t = 10 * np.sin(np.linspace(0, 2 * pi, 1000)) # sin波作成
data_set = []
for i in range(500):  # 1000個のsin波を作成
    x = np.linspace(0, 2 * pi, 1000)
    y = np.sin(x) + np.random.randn(1000) * 0.01  # 乱数かけて調整
    data_set.append(np.array([x, y]))
data_set = np.array(data_set)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.scatter(data_set[0, 0, :], data_set[0, 1, :], marker=".")
plt.show()

#%%

model = keras.Sequential([
    keras.layers.Flatten(input_shape=(1000,)),
    keras.layers.Dense(1000, use_bias=True)
])

# loss=平均二乗誤差
# optimizer -> 最適化関数, loss -> 誤差関数, metrics -> モデル作成時、コンソールに表示するパラメータ(任意)
model.compile(optimizer='adam',
              loss='mean_squared_error')

#%%

# animation用のfig作成
ims = []
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_ylim(-1.5, 1.5)

# トレーニング実行
TRAIN_NUM = 10
for _ in range(TRAIN_NUM):
    fit = model.fit(
        data_set[:, 0], # training-data
        data_set[:, 1], # training-label
        epochs=1)  # 繰り返す回数

    test_data = np.linspace(0, 2*pi, 1000)
    predictions = model.predict(np.expand_dims(test_data, 0))

    im = ax.scatter(test_data, predictions[0], marker='.', c='b')
    ims.append([im])


#%%

interval = 100
ani = animation.ArtistAnimation(fig=fig, artists=ims, interval=interval)
ani.save('./output/ani_tensorflow2-turorial.gif', writer='imagemagick')
# ani.save('./output/ani_tensorflow2-turorial.mp4', writer='ffmpeg')

#%%

# tfliteモデルを作成
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
open("data/converted_model.tflite", "wb").write(tflite_model)

sin波を学習しただけです

X軸の0~2πまでを1000個の座標に分け、それに対するY軸の値を学習させています。
ani_tensorflow2-turorial.gif

.tfliteファイルを作成する。

重要なのはこここです。
IoTデバイスやスマホでモデルを使うには、 .tfliteファイルという形でモデルファイルを保存します。
コードは以下の通りです。

kerasの場合

model = keras.Sequential([
    keras.layers.Flatten(input_shape=(1000,)),
    keras.layers.Dense(1000, use_bias=True)
])
# ~~~
# tfliteモデルを作成
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
open("data/converted_model.tflite", "wb").write(tflite_model)

.tfliteファイルの保存方法は使うモデルによってさまざまです。
TensorFlow Lite コンバータ

2. スマホ側で学習モデルを利用する

次に先ほど作成した.tffileをスマホ(iOS)で使っていきます。
今回作ったプロジェクトはGithubに載せておきます。
TEST Project

TnesorflowLiteをインストール

プロジェクトを作成したら、podを使ってTnesorflowLiteをインストールします。

Podfile
pod 'TensorFlowLiteSwift'

プロジェクトに.tffileを入れる

先ほど作成した.tffileをプロジェクトに入れてます。
スクリーンショット 2020-04-27 9.50.05.png

3. 実行コード

最初に全体の流れを見せておきます。

全体コード

// モデルファイルのパスを取得
guard let modelPath = Bundle.main.path(
      forResource: "converted_model",  // ファイル名
      ofType: "tflite"  // 拡張子
) else {
      print("Failed to load the model file with name: ~")
      return
}

do {
  // Interpreterでモデルファイルを読み込む
  interpreter = try Interpreter(modelPath: modelPath)
  // メモリ確保。これはモデル読み込み直後に必須
  try interpreter?.allocateTensors()
} catch let error {
  print("Failed to create the interpreter with error: \(error.localizedDescription)")
  return
}

guard let intp = interpreter else {
    return
}

do {
    // X(入力)データを作成。 Data型(byte列)で作成する必要あり。(後ほど説明)
    let inputData = createXData()  
    // Interpreterにデータを入力
    try intp.copy(inputData, toInputAt: 0)  
    // 実行
    try intp.invoke()
    // 出力データ取得
    let outputTensor = try intp.output(at: 0)  // Tensor型
    print(outputTensor)
    // 出力のData型のデータから、求める形にデコード
    let outputValues: [Float32] = decodeData(output: outputTensor.data)
    print(outputValues)
    // XとYを元にグラフ描画
    dispSinWave(X: createXLine(), Y: outputValues)
} catch let error {
    print("error:", error)
}

説明

Interpreterで学習モデルを読み取る

モデルへのパスを渡して、Interpreterインスタンスを作成します。

interpreter = try Interpreter(modelPath: modelPath)
// メモリ確保。これはモデル読み込み直後に必須
try interpreter?.allocateTensors()

入力データ作成

createXDataメソッドを作り、入力データを作成しています。
後ほど説明しますが、入力データはData型にする必要があります。(これで1日とられました...)

let inputData = createXData()  

Interpreterにデータを入力

copyメソッドで入力データを設定します。
toInputAtは、入力データが複数ある場合に指定します。
今回は入力は1つなので、0でOKです。

try intp.copy(inputData, toInputAt: 0)

実行

try intp.invoke()

出力データ取得

実行したら、outputメソッドで出力を取得します。
atは出力が複数ある場合に指定します。

// 出力データ取得
let outputTensor = try intp.output(at: 0)  // Tensor型

また、出力はTensor型であり、以下のように出力の型などがわかります。
スクリーンショット 2020-04-27 10.17.19.png

出力をData型から変換

今回の出力は、Float32の配列であるため、decodeDataメソッドで変換してます。
メソッドの中身は後ほど見せます。

// 出力のData型のデータから、求める形にデコード
let outputValues: [Float32] = decodeData(output: outputTensor.data)

入力、出力の作成

先ほど使っていたcreateXDatadecodeDataメソッドの説明です。

入力 - createXData

入力、出力ともにData型で作成します。
今回の入力データはFloat32の配列であるため、まずFloat32の値をData型に変換し、その配列をもう一度Data型に変換しています。

private func createXData() -> Data {
    var bytes: [UInt8] = []
    let xLine = createXLine()
    for var value in xLine {
      // FloatをData型に変換
        let valueBytes = Data(bytes: &value, count: (Float32.significandBitCount + Float32.exponentBitCount + 1) / 8)
      // Data型をByte配列に
        let bytesArray = Array<UInt8>(valueBytes)
        // Byte配列を足していく
        bytes += bytesArray
    }
    return Data(bytes: bytes, count: bytes.count)
}

// 0 ~ 2PI までを1000個の座標に分けた配列
private func createXLine() -> [Float32] {
    var array: [Float32] = []
    let NUM = 1000
    let delta = 2*Float.pi / Float(NUM)
    for i in 0 ..< NUM {
        let value = Float32(i)*Float32(delta)
        array.append(value)
    }
    return array
}

出力 - decodeData

出力もすべてData型なので、本来の[Float32]に直していきます。
Float32は4bytesなので、Data型の出力データから4bytesずつ取り出しFloat32型に変換しています。

private func decodeData(output: Data) -> [Float32] {
    var decodeValue: [Float32] = []
    let float32Bytes = (Float32.significandBitCount + Float32.exponentBitCount + 1) / 8   // Float32のバイト数
    for i in 0 ..< output.count / float32Bytes {
    // Float32のバイト数をとる
        let data = output[i*float32Bytes..<(i+1)*float32Bytes]  
    // バイト列からFloat32に変換
        let value = data.withUnsafeBytes { $0.load(as: Float32.self) }
        decodeValue.append(value)
    }

    return decodeValue
}

結果

私が作ったプロジェクトはボタンを押したらレイヤーが表示されます。

スクリーンショット 2020-04-27 10.24.12.png

参考記事

初心者に優しくないTensorflow Lite の公式サンプル
iOS - quickstart
TensorFlow Lite image classification iOS example application

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2