Ruby で TensorFlow
===
2017/02/11 名古屋Ruby会議03
antimon2(後藤 俊介 / 機械学習名古屋)
---
## デモ
----
[Sinatra](http://www.sinatrarb.com/) + [tensorflow.rb](https://github.com/somaticio/tensorflow.rb) で動いていま~~す~~++した++。
<!-- .element: class="fragment" -->
<aside class="notes">このサイトは今回のデモ用に臨時に用意したものです。<S>週が明けたら閉じる予定</s><ins>閉じました</ins>。あとタイムテーブルで「Rails に組み込んだ…」と言っていましたがあれは嘘だ(時間がなかった)。</aside>
---
ここから本題…。
<aside class="notes">ここまでは枕</aside>
---
<!-- .slide: data-transition="convex-in slide-out" -->
# Ruby で TensorFlow してみたけれど辛かった
2017/02/11 名古屋Ruby会議03
antimon2(後藤 俊介 / 機械学習名古屋)
<aside class="notes">Ruby で TensorFlow してみたけれどなかなか大変だった</aside>
---
## 自己紹介
----
+ 名前:後藤 俊介
+ 所属:有限会社 来栖川電算
+ コミュニティ:**[機械学習名古屋](https://machine-learning.connpass.com/)**(主催の1人), [Ruby東海](https://rubytokai.doorkeeper.jp/), [Python東海](https://connpass.com/series/292/), …
+ 言語:Python, Julia, **Ruby**, Scala(勉強中), …
+ twitter: [@antimon2](https://twitter.com/antimon2)
+ Facebook: [antimon2](https://www.facebook.com/antimon2)
+ GitHub: [antimon2](https://github.com/antimon2/)
+ Qiita: [@antimon2](http://qiita.com/antimon2)
<aside class="notes">Rubyist としては、誰も使わないような gem を2つほど公開しています。機械学習とは、コンピュータにデータを学習させて自分の判断で色々仕事をさせること。自然言語処理とか画像認識とか。あと amazon の「この商品を買った人はこんな商品も」とか。今日の発表でも須藤さんの発表中にちらっと出てきましたね。</aside>
----
### 機械学習名古屋
[![mlngy_201703.png](https://qiita-image-store.s3.amazonaws.com/0/30400/deab3775-657e-bcc3-55bd-a3b234fe1017.png)](https://machine-learning.connpass.com/event/49365/)
[第9回勉強会](https://machine-learning.connpass.com/event/49365/) 2017/03/11 開催(ちょうど1ヶ月後)
<aside class="notes">機械学習名古屋は、「ビジネスで実用的に使える機械学習」を1つのテーマとしています。</aside>
---
## TensorFlow
----
### TensorFlow とは
+ https://www.tensorflow.org/
+ Google 製の「データフローグラフを用いた数値計算ライブラリ」(公式の説明を私訳)
+ 要するに、**機械学習ライブラリ**。
+ 最新 [v0.12](https://www.tensorflow.org/versions/r0.12/)(2017/02/11 現在)。[v1.0](https://github.com/tensorflow/tensorflow/tree/r1.0) が開発中。
----
### tensorflow.rb
+ https://github.com/somaticio/tensorflow.rb
+ TensorFlow を Ruby から操作するためのラッパーライブラリ。
+ [Docker イメージ](https://github.com/somaticio/tensorflow.rb/blob/master/README.md#docker) が用意されているので、手軽に試すことができる。
---
## tensorflow.rb のインストール
----
### `gem install tensorflow`…
```shell
$ gem install tensorflow
ERROR: Could not find a valid gem 'tensorflow' (>= 0) in any repository
...
```
<!-- .element: class="fragment" -->
----
### 人類にはまだ早かった
<aside class="notes">まだコマンド一発でインストールできるよう整備されているわけではありません。</aside>
----
## 自分でビルド
公式 [README](https://github.com/somaticio/tensorflow.rb/blob/master/README.md#outside-of-docker "Outside of Docker - Installation - tensorflow.rb") を参考に
> + 要 [Bazel](http://www.bazel.io/docs/install.html)
> + 要 [Swig](http://www.swig.org/download.html)
<aside class="notes">macOS なら brew で、Linux系なら apt-get/yum 等でインストールできると思います。</aside>
----
### 〜中略〜
<aside class="notes">libtensorflow.so のビルドに1時間くらい(READMEには10-15分とあるけれど)と時間かかる…。</aside>
----
### 確認
```shell
$ bundle exec rake spec
: 《中略》
dyld: lazy symbol binding failed: Symbol not found: _TF_NewStatus
Referenced from: /path/to/gems/tensorflow-0.0.1/lib/sciruby/Tensorflow.bundle
Expected in: flat namespace
dyld: Symbol not found: _TF_NewStatus
Referenced from: /path/to/gems/tensorflow-0.0.1/lib/sciruby/Tensorflow.bundle
Expected in: flat namespace
: 《後略》
$
```
<aside class="notes">libtensorflow.so のビルドがなんかおかしい…</aside>
----
## 人類にはまだ早かった
<aside class="notes">素直に Docker 使った方が取り敢えず幸せになれそう。</aside>
---
## Docker で tensorflow.rb
----
### Docker
+ https://www.docker.com/
+ (ry
<aside class="notes">コンテナ型仮想化</aside>
----
### tensorflow.rb の Docker イメージ
```shell
$ docker pull nethsix/ruby-tensorflow-ubuntu:0.0.1
```
----
### コンテナ実行
```shell
$ docker run --rm -it -v `pwd`:`pwd` --expose 4567 -p 8000:4567 \
nethsix/ruby-tensorflow-ubuntu:0.0.1 /bin/bash
```
<aside class="notes"><code>-v `pwd`:`pwd`</code> は、カレントディレクトリを同じ名前で docker 内にマウントする設定、<code>--expose 4567 -p 8000:4567</code> は、Sinatra を起動してポート番号 8000 でアクセスできるようにするための設定。あと便宜上改行しています。</aside>
----
### Gemfile
```ruby=
source 'https://rubygems.org'
gem 'tensorflow', path: '/repos/tensorflow.rb'
gem 'sinatra'
# gem 'sinatra-contrib'
gem 'thin'
```
----
### `bundle install`
```shell
$ bundle install
: #《中略》
Bundle complete! 3 Gemfile dependencies, 11 gems now installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
```
----
### 動作確認
<big><big><big><big>(ry</big></big></big></big>
<aside class="notes">先人たちの戦果を <a href="https://www.google.co.jp/search?q=tensorflow.rb&oq=tensorflow.rb" target="_blank">tensorflow.rb でググっ</a>て見てみてください。</aside>
---
## 学習
----
### してません!
<aside class="notes">時間的な問題で。その代わり学習済モデルを読み込む方針で。本当はワンストップで全部Rubyで、っていうデモができればと思ってたんですけど…</aside>
---
## 推測
----
### 学習済モデル準備
Python で以前学習したモデルを [Protocol Buffers](https://developers.google.com/protocol-buffers/)形式 で保存。
(詳細略)<!-- .element: class="fragment" -->
<aside class="notes"><a href="http://workpiles.com/2016/07/tensorflow-protobuf-dump/" target="_blank">TensorFlowで学習済みグラフを保存する方法</a> を参考にしました。なお Python のコードは出しません。Ruby 会議なので。</aside>
----
### 学習済モデル読込
```ruby=
require 'tensorflow'
sess = Tensorflow::Session.new
graph = Tensorflow::Graph.new
graph.read("trained_graph.pb")
sess.extend_graph(graph)
```
<aside class="notes"><strong>trained_graph.pb</strong> が学習済モデルを保存したファイルです。</aside>
----
#### 参考
.pb のフォーマットが間違っていると…
```shell
E tensorflow/core/common_runtime/executor.cc:334] Executor failed to create kernel. Invalid argument: NodeDef mentions attr 'Tshape' not in Op<name=Reshape; signature=tensor:T, shape:int32 -> output:T; attr=T:type>; NodeDef: Reshape = Reshape[T=DT_FLOAT, Tshape=DT_INT32, _device="/job:localhost/replica:0/task:0/cpu:0"](_recv_Placeholder_0, Reshape/shape)
[[Node: Reshape = Reshape[T=DT_FLOAT, Tshape=DT_INT32, _device="/job:localhost/replica:0/task:0/cpu:0"](_recv_Placeholder_0, Reshape/shape)]]
E tensorflow/core/client/tensor_c_api.cc:485] NodeDef mentions attr 'Tshape' not in Op<name=Reshape; signature=tensor:T, shape:int32 -> output:T; attr=T:type>; NodeDef: Reshape = Reshape[T=DT_FLOAT, Tshape=DT_INT32, _device="/job:localhost/replica:0/task:0/cpu:0"](_recv_Placeholder_0, Reshape/shape)
[[Node: Reshape = Reshape[T=DT_FLOAT, Tshape=DT_INT32, _device="/job:localhost/replica:0/task:0/cpu:0"](_recv_Placeholder_0, Reshape/shape)]]
/repos/tensorflow.rb/lib/tensorflow/session.rb:103: [BUG] Segmentation fault at 0x00000000000017
ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0007 p:---- s:0040 e:000039 CFUNC :tensor_size
c:0006 p:0015 s:0036 e:000035 METHOD /repos/tensorflow.rb/lib/tensorflow/session.rb:103
c:0005 p:0009 s:0029 e:000028 BLOCK /repos/tensorflow.rb/lib/tensorflow/session.rb:42 [FINISH]
: (ry
```
<!-- .element: class="fragment" -->
<aside class="notes">Segmentation fault で Ruby ごと落ちます。</aside>
----
#### <big><big>辛い</big></big>
<aside class="notes">辛い。</aside>
----
### 推測
```ruby=8
# x: 要素数 784 の数値の配列(を期待)
input1 = Tensorflow::Tensor.new(x, :float)
res = sess.run(
{ 'Placeholder' => input1.tensor },
['Softmax'],
nil
)
print res[0][0], "\n"
# => [7.959904735344026e-09, 2.4618149532806832e-11, 9.080870950128883e-05, 1.0324711918752172e-10, 2.9449205296572245e-09, 1.312004671571998e-10, 1.6875362218726764e-09, 5.2551704307624014e-12, 0.9999091625213623, 3.465244091671593e-08]
```
----
#### 参考
ちょっとでも間違ったモノを渡したりすると…
```shell
E tensorflow/core/client/tensor_c_api.cc:485] Input to reshape is a tensor with 783 values, but the requested shape requires a multiple of 784
[[Node: Reshape = Reshape[T=DT_FLOAT, _device="/job:localhost/replica:0/task:0/cpu:0"](_recv_Placeholder_0, Reshape/shape)]]
/repos/tensorflow.rb/lib/tensorflow/session.rb:103: [BUG] Segmentation fault at 0x00000000000017
ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0007 p:---- s:0040 e:000039 CFUNC :tensor_size
c:0006 p:0015 s:0036 e:000035 METHOD /repos/tensorflow.rb/lib/tensorflow/session.rb:103
c:0005 p:0009 s:0029 e:000028 BLOCK /repos/tensorflow.rb/lib/tensorflow/session.rb:42 [FINISH]
: (ry
```
<!-- .element: class="fragment" -->
<aside class="notes">Segmentation fault で Ruby ごと落ちます。</aside>
----
#### <big><big><big>辛い</big></big></big>
<aside class="notes">本当に辛い。</aside>
---
## Web アプリ組込
----
### 仕様
+ canvas を表示し、マウスもしくはタッチで、手書きで数字を書き込める。
+ 「Classify」ボタンを押すと、それを「0」〜「9」のどの数字か推測して結果を表示する。
----
### mnist.rb
```ruby=
# mnist.rb
require 'tensorflow'
class MNIST
def initialize(model)
sess = Tensorflow::Session.new
graph = Tensorflow::Graph.new
graph.read(model)
sess.extend_graph(graph)
@sess = sess
end
```
----
```ruby=13
def classify(x)
input1 = Tensorflow::Tensor.new(x, :float)
res = @sess.run(
{ 'Placeholder' => input1.tensor },
['Softmax'],
nil
)
res[0][0]
end
end
```
----
### app.rb(抜粋)
```ruby=
# app.rb
require 'sinatra'
require 'thin'
require 'json'
require_relative 'mnist'
configure do
# 略:IPの設定、Thin Backend の設定
```
----
```ruby=41
def classify(x)
@@mnist ||= MNIST.new("trained_graph.pb")
@@mnist.classify(x)
end
```
<aside class="notes"><code>@@mnist</code>(クラス変数)を使ってて行儀悪い(Warning が出る)けれど取り敢えずやっつけで。</aside>
----
```ruby=69
post '/classify' do
content_type :json
begin
x = JSON.parse(params["x"])
res = classify(x)
res.to_json
rescue Exception => e
"[]"
end
end
```
<aside class="notes"><code>x</code>という名前でパラメータ(長さ784の数値配列(JSON文字列))を受け取り、結果を配列(JSON文字列)で返す。例外が起きたら(ハンドリングできたら)空の配列を返している。</aside>
----
### HTML(+JavaScript)(抜粋)
```htmlmixed=15
<script>
// (ry
function classify() {
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var data = [];
// (ry: canvas からピクセルデータを読み込む処理
jQuery.ajax("classify", {
dataType: "json",
data: "x=" + JSON.stringify(data),
type: "POST",
success: function (res) {
// (ry: 整形して表示する処理
}
});
}
// (ry
</script>
```
----
```htmlmixed=138
<div id="board">
<canvas id="myCanvas" width="112px" height="112px"></canvas>
<p>
<button id="classify" onclick="classify()">Classify</button>
<button id="clear" onclick="myClear()">Clear</button>
</p><p>
Result: <input type="text" id="result_output" size="5" value=""><br> <textarea id="result_detail" cols="30" rows="10"></textarea>
</p>
</div>
```
<aside class="notes">idとか関数名とかが<code>myXxxx</code>になっていて、いかにもサンプルっぽいですよね!(名前付けをさぼっただけ…)</aside>
----
### 結果
<img width="414" alt="IMG_1426.png" src="https://qiita-image-store.s3.amazonaws.com/0/30400/2c4f8f2b-a7ea-d4c0-cd79-aa8c8ba9e39f.png">
<aside class="notes">なんとか動くところまで漕ぎ着けました…</aside>
----
#### 辛かった
---
## 結論
+ **人類にはまだ早い**<!-- .element: class="fragment" data-flagments-index="1" -->
+ **辛い**<!-- .element: class="fragment" data-flagments-index="2" -->
<aside class="notes"><ul>
<li>「誰でも手軽に組み込める」レベルはまだ遠い。</li>
<li>頑張れば動くものはできるけれど、結構大変。</li>
</ul></aside>
---
# おしまい
ご清聴ありがとうございます。
{"metaMigratedAt":"2023-06-14T12:23:28.343Z","metaMigratedFrom":"YAML","title":"Ruby で TensorFlow","breaks":true,"slideOptions":"{\"transition\":\"slide\"}","contributors":"[{\"id\":\"80062a4b-8dad-49ac-95bf-848ce0686e9e\",\"add\":36,\"del\":0}]"}