mruby から TensorFlow Lite を操りブラックホールとポンデリングとオニオンフライを見分ける

# mruby-tflite を作りました

今朝、mruby から TensorFlow Lite が扱える様に mrbgems (mruby で言う gems)を作りました。

https://github.com/mattn/mruby-tflite

[Big Sky :: MRuby の TensorFlow Lite バインディングを書いた。](https://mattn.kaoriya.net/software/lang/c/20190417102631.htm)

これを使うと Python で機械学習したモデルファイルを TensorFlow Lite のモデルに変換し、mruby から利用できる様になります。例えば FizzBuzz を推論するモデルは以下の様に書けます。

```python
import numpy as np
from tensorflow.contrib.keras.api.keras.models import Sequential, model_from_json
from tensorflow.contrib.keras.api.keras.layers import Dense, Dropout, Activation
from tensorflow.contrib.keras.api.keras.optimizers import SGD, Adam
import tensorflow.contrib.lite as lite

def fizzbuzz(i):
    if   i % 15 == 0: return np.array([0, 0, 0, 1])
    elif i % 5  == 0: return np.array([0, 0, 1, 0])
    elif i % 3  == 0: return np.array([0, 1, 0, 0])
    else:             return np.array([1, 0, 0, 0])

def bin(i, num_digits):
    return np.array([i >> d & 1 for d in range(num_digits)])

NUM_DIGITS = 7
trX = np.array([bin(i, NUM_DIGITS) for i in range(1, 101)])
trY = np.array([fizzbuzz(i) for i in range(1, 101)])
model = Sequential()
model.add(Dense(64, input_dim = 7))
model.add(Activation('tanh'))
model.add(Dense(4, input_dim = 64))
model.add(Activation('softmax'))
model.compile(loss = 'categorical_crossentropy', optimizer = 'adam', metrics = ['accuracy'])
model.fit(trX, trY, epochs = 3600, batch_size = 64)
model.save('fizzbuzz_model.h5')

converter = lite.TFLiteConverter.from_keras_model_file('fizzbuzz_model.h5')
tflite_model = converter.convert()
open('fizzbuzz_model.tflite', 'wb').write(tflite_model)
```

※現状の TFLiteConverter はファイル経由でないと変換できないのが若干辛いですね。

この出力された `fizzbuzz_model.tflite` を mruby から以下の様に使います。

```ruby
def bin(n, num_digits)
  f = []
  0.upto(num_digits-1) do |x|
    f[x] = (n >> x) & 1
  end
  return f
end

def dec(b, n)
  b.each_with_index do |x, i|
    if x > 0.4
      return case i+1
      when 1; n.to_s
      when 2; 'Fizz'
      when 3; 'Buzz'
      when 4; 'FizzBuzz'
      end
    end
  end
  raise "OMG!"
end

model = TfLite::Model.from_file 'fizzbuzz_model.tflite'
interpreter = TfLite::Interpreter.new(model)
interpreter.allocate_tensors
input = interpreter.input_tensor(0)
output = interpreter.output_tensor(0)
1.upto(100) do |x|
  input.data = bin(x, 7)
  interpreter.invoke
  puts dec(output.data, x)
end
```

数値を7桁の one-hot にする為の関数 bin/dec と、TensorFlow Lite のインタプリタを操作するコートです。

# 本当は画像判定をやりたかった

本来ならば xor や FizzBuzz ではなく画像を読み込み、画像が何なのか判定する様なカッコいい事がやりたいのですが、ブログ記事にも書いた通り現状の mrbgems には画像の画素データにアクセスできるライブラリがありません。これは無理かなと思っていたのですが、ImageMagick の convert コマンドは、出力画像として `txt:-` を指定すると画素データの一覧を吐いてくれる事が分かりました。

```console
$ gm convert sample1.jpg -resize 299x299! txt:-
0,0: (  0,  2,  0) #000200
1,0: (  0,  2,  0) #000200
2,0: (  0,  2,  0) #000200
3,0: (  0,  2,  0) #000200
4,0: (  0,  2,  0) #000200
5,0: (  0,  2,  0) #000200
6,0: (  0,  2,  0) #000200
```

ImageMagick (GraphicsMagick) 凄い！！！

これがあれば勝てる、そう考えながらサクッと画素データを読み取るスクリプトを書きました。

```ruby
img = Array.new(wanted_height * wanted_width * wanted_channel, 0)
`gm convert #{ARGV[0]} -resize #{wanted_width}x#{wanted_height}! txt:-`.each_line do |x|
  r1, r2 = x.split(': (')
  y, x = r1.split(',')
  off = (y.to_i * wanted_width + x.to_i) * wanted_channel
  c = r2.split(')')[0].split(',').map {|x| x.strip.to_f / 255}
  img[off+0] = c[0]
  img[off+1] = c[1]
  img[off+2] = c[2]
end
```

# ブラックホールとポンデリングを見分ける

先日書いた「[TensorFlow を使ってブラックホールとポンデリングを見分ける (追記あり)](https://qiita.com/mattn/items/429fca1c72bcb99adbc3)」という記事の為に作った TensorFlow のモデルファイルが運良く消さずに残っていたので、TensorFlow Lite のモデルに変換しました。変換には toco コマンドを使います。
※そろそろ消そうかなと思っていたのでセーフ

```console
$ toco \
    --output_file pondehole.tflite \
    --graph_def_file output_graph.pb \
    --output_format TFLITE \
    --input_array=Placeholder \
    --input_shape=1,299,299,3 \
    --output_array=final_result 
```

入力ノード(`--input_array=Placeholder`)と出力ノード(`--output_array=final_result`)の名前は tensorboard の GRAPH タブを見ると確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665/be1b7a37-5433-bfcd-e31e-9a098fb2e94f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665/31c77b70-adc1-f731-a103-f0abbd3698df.png)

また `--input_shape` は入力データの形状を示す物ですが、これは学習した際に使用した `retrain.py` を見てどの画像にリサイズしているかを調べて下さい。この意味は 299x299 サイズ、3 チャネルの画像という意味になります。

ここまでくれば後は以下の様にスクリプトを作ります。

```ruby
if ARGV.empty?
  puts "usage: #{$0} [image file]"
  return
end

labels = File.read('output_labels.txt').lines.map {|x| x.strip}

model = TfLite::Model.from_file 'c:/dev/pondehole/pondehole.tflite'
interpreter = TfLite::Interpreter.new(model)
interpreter.allocate_tensors
input = interpreter.input_tensor(0)
wanted_width = input.dim(1)
wanted_height = input.dim(2)
wanted_channel = input.dim(3)

img = Array.new(wanted_height * wanted_width * wanted_channel, 0)

`gm convert #{ARGV[0]} -resize #{wanted_width}x#{wanted_height}! txt:-`.each_line do |x|
  r1, r2 = x.split(': (')
  y, x = r1.split(',')
  off = (y.to_i * wanted_width + x.to_i) * wanted_channel
  c = r2.split(')')[0].split(',').map {|x| x.strip.to_f / 255}
  img[off+0] = c[0]
  img[off+1] = c[1]
  img[off+2] = c[2]
end

output = interpreter.output_tensor(0)
input.data = img
interpreter.invoke
puts labels[output.data.each_with_index.max[1].to_i]
```

# mruby で画像判定やります

まずはポンデリング
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665/5d508960-934e-6ac6-d643-b8c6f1415a79.png)

次にブラックホール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665/23e85d51-0473-a6c1-d319-e87ea8be4547.png)

最後にオニオンフライ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/665/bd017c1d-1c94-1841-e186-ab02782a5beb.png)

いずれも正しく判定できています。

# まとめ

mruby から TensorFlow Lite を扱う為の gem 「mruby-tflite」を作り、画像を読み込んでブラックホールとポンデリング、さらにはオニオンフライを見分ける事に成功しました。これならば「今日のオヤツどれがいい？」とお母さんがブラックホールとポンデリングとオニオンリングを差し出してきてもポンデリングを選ぶ事ができるので安心ですね。良かったですね。

冗談さておき、現実として組み込み用途で使われる可能性がある mruby が機械学習の力を手に入れられる事になったので、アイデア次第で色々な物が作れる様になります。ラズベリーパイ上で mruby を動かし、カメラから取り込んだ画像で画像判定というのも難しくありません。またモデルファイルも mobilenet 等を使う事も出来るのです。ぜひ皆さんの手で面白い物を作ってみて下さい。

なお TensorFlow Lite を Go 言語から扱う為のパッケージも絶賛開発中です。興味のある方は使ってみて下さい。

https://github.com/mattn/go-tflite