はじめに
私用でTensorflowLiteを使いそうなので、iOSで使えるか試してみました。
環境
Python3.7
Swift 5.2.2
Xcode 11.4.1
iOS 13.4
TensorflowLiteとは
IoTデバイスやスマホなどで、Tensorflowで開発したモデルを利用することができるものらしいです。
詳細は良くわからないのですが、TensorflowLiteで学習してモデルを構築なんてことはできないそうですね。
公式のサンプル
一応、公式のサンプルプロジェクトがあります。
iOSのクイックスタート
動くは動くのだが....

本編
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軸の値を学習させています。
.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
をインストールします。
pod 'TensorFlowLiteSwift'
プロジェクトに.tffile
を入れる
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型であり、以下のように出力の型などがわかります。
出力をData型から変換
今回の出力は、Float32の配列であるため、decodeData
メソッドで変換してます。
メソッドの中身は後ほど見せます。
// 出力のData型のデータから、求める形にデコード
let outputValues: [Float32] = decodeData(output: outputTensor.data)
入力、出力の作成
先ほど使っていたcreateXData
とdecodeData
メソッドの説明です。
入力 - 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
}
結果
私が作ったプロジェクトはボタンを押したらレイヤーが表示されます。

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