tensorflow.jsでクリスマス度を数値化してみる


こんにちは。NIJIBOXのエンジニアのつんあーです。

こちらの記事は[NIJIBOX Advent Calendar 2019](https://qiita.com/advent-calendar/2019/nijibox)の12日目の記事です。

https://github.com/ats05/christmas_scouter
[デモはこちら](https://ats05.github.io/christmas_scouter/)

※画面を開いてからモデルのロードが完了するまで`--.-%`と表示されます。しばらくお待ちください。
※カメラの映像が滑らかに表示できません。なんとかしたい。



TensorFlow.jsの情報を調べてみると、意外にまだまだ「コレ！」といった情報が少ない印象でした。
情報を探している方の助け&web技術の発展の助けになれば幸甚です。


---

## 導入

さて、厚手のコートを着ていても吹く風の冷たさを感じる今日この頃。
街はクリスマスムード真っ只中です。

そこかしこにイルミネーションが輝き、少し浮かれ気味なムードが漂っています。

実は、私はこの華やかな時期の街中が結構好きなのです。
イルミネーションが綺麗な街で、「ああクリスマスっぽいなぁ」ってぼんやりするのは結構楽しいものです。


はたと思いました。

クリスマスっぽいとは一体なんなのでしょう。

なぜだかワクワクするような気持ちを感じさせる街の雰囲気とは、
一体どんなところから溢れてくるのでしょう。



とりあえず検索してみましょう。

![スクリーンショット 2019-12-06 18.56.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/a853e0bb-07d7-c3a2-40a5-70c977d2e954.png)

いいですね。

こういう雰囲気ですよ。こういう雰囲気。



今度はクリスマスを感じない街を検索してみます。

![スクリーンショット 2019-12-10 22.01.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/eaffc802-9435-099f-476c-cf1f85a1a352.png)

普通ですね。（そりゃそうだ）

画像検索してるのにキーワードに「写真」と入れているあたりに茶目っ気を感じます。


---


ところで、私達はこれらの画像検索の結果から「クリスマスっぽさ」
をなんとなく感じ取ることができています

では、これらの画像をひたすら学習させれば、コンピュータでも
「ああクリスマスっぽいなぁ」と感じさせることができるのではないでしょうか。



モノは試し、やってみましょう。


というわけで、今回はTensorflowを使い、画像のクリスマスっぽさをコンピュータに判断させてみます。
ついでに、NIJIBOXはWebに強い会社ということで、
これをWebに移植しスマホからでも簡単に実行できるようにしてみましょう。

今回は[コチラの記事](https://qiita.com/quotto/items/645b01cf9c3919a52b12)を参考に、画像の分類を行ってみました。

--- 

## Tensorflowでクリスマスっぽさを判断させる 

### 画像を集める

まず、こちらのツールを使ってGoogle画像検索から画像を集めます。
https://github.com/hardikvasa/google-images-download

1度に100件以上の画像をダウンロードする際はChromeDriverを入れないといけないようなので、導入します。

```
$ brew tap homebrew/cask
$ brew cask install chromedriver
```

```
$ which chromedriver
=> /usr/local/bin/chromedriver # 入った！
```


先ほどと同じキーワードで、クリスマスっぽさを感じる画像を集めてみます。
とりあえず上限は10000件にしてみました。欲張りですね！

```
$ googleimagesdownload --keywords "クリスマス 写真 昼間 街" --limit 10000 --chromedriver /usr/local/bin/chromedriver
```

と思ったら残念、376件までしかダウンロードしてくれませんでした。ガックシガックシ。

```
Unfortunately all 10000 could not be downloaded because some images were not downloadable. 376 is all we got for this search filter!
```

集まった画像はこんな感じ。良さそうですね。


![スクリーンショット 2019-12-06 18.31.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/ed6fe643-b114-bf05-0b16-691b67c011ba.png)
画像は`実行したディレクトリ/download/キーワード`という場所に保存されます。


同様の手順で、クリスマスっぽくない画像も集めます。

```
$ googleimagesdownload --keywords "街 写真" --limit 10000 --chromedriver /usr/local/bin/chromedriver

...

Unfortunately all 10000 could not be downloaded because some images were not downloadable. 379 is all we got for this search filter!
```
やはり途中で終わってしまいました。こちらは379件。





先ほどのスクリーンショットを見ていただくとわかるのですが、
データが壊れていて開けない画像や、重複した画像、そもそもクリスマスとあまり関係のない画像が混ざっています。

これらを手作業で除外していきます。


その結果、
`クリスマスっぽい画像：300枚くらい`
`クリスマスっぽくない画像:350枚くらい`

まで減りました。


画像は、「chrsitmas」ディレクトリと「nochristmas」ディレクトリに分けておきます。


```
downloads/
├── christmas
└── nochristmas
```


<img width="157" alt="スクリーンショット 2019-12-10 22.18.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/5852b82c-9301-2ab6-6a4b-0d4e39364f5c.png">



ちなみに、クリスマス画像にはやたらと合コン募集の画像が入っていました。
**どうしろと言うのでしょう。**


---


### 画像を学習させる

画像が集まったので、これらをTensorflowで読み込ませ学習させていきます。


既に基本的な訓練をされたモデルがあるので、それを利用します。
下記のスクリプトをダウンロード。
https://github.com/tensorflow/hub/raw/r0.1/examples/image_retraining/retrain.py

とりあえず、実行してみます。

*--追記--*
*ここでは`--saved_model_dir`オプションをつける必要がありました。*
*私はこれに気付かずかなりの時間を使ってしまいました。*

```
$ python retrain.py --image_dir ./google-images-download/downloads/ --saved_model_dir ./SavedModel

...

AttributeError: module 'tensorflow' has no attribute 'app'
```


まあ、一発でうまく行くわけがないですよね。
エラーが出ました。



上記エラーの内容を調べてみると、TensorFlow2.x以降だと`tf.app`が使えないからエラーになっているようです。
手元のTensorflowのバージョンを確認してみます。

```
$ pip show tensorflow
=> Name: tensorflow
   Version: 2.0.0
```

残念、2.0.0が入っていました。。。。

仕方ないので、1.x系にダウングレードします。
今入っているものを削除し、1.x系の最新をインストール。

```
$ pip uninstall tensorflow 
$ pip install tensorflow==1.15
$ pip uninstall tensorflow-hub 
$ pip install tensorflow-hub 

$ pip show tensorflow
=> Name: tensorflow
   Version: 1.15.0
```


もう一度、学習のスクリプトを実行してみます。

```
$ python retrain.py --image_dir ./google-images-download/downloads/   
```

お、何か始まりましたね。

<img width="1034" alt="スクリーンショット 2019-12-06 18.54.56.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/fd29b3c9-2698-a5d9-fea9-10858b42eacb.png">



```
...

OSError: [Errno 63] File name too long: '/tmp/bottleneck/christmas/363.%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%A8%E3%83%BC%E3%82%AF-%E3%82%AF%E3%83%AA%E3%82%B9%E3%83%9E%E3%82%B9%E3%82%B7%E3%83%BC%E3%82%B9%E3%82%99%E3%83%B3-%E3%82%AB%E3%83%AB%E3%83%86%E3%82%A3%E3%82%A8.jpg_https~tfhub.dev~google~imagenet~inception_v3~feature_vector~1.txt'
```


。。。と思ったらまたエラーが出ました。
読み込んだ画像のファイル名が長すぎるそうです。（そんなことあるのか。。）




ダウンロードした画像のファイル名を修正し、再々チャレンジ。

```
INFO:tensorflow:Froze 378 variables.
I1206 19:20:09.427611 140737240245184 graph_util_impl.py:334] Froze 378 variables.
INFO:tensorflow:Converted 378 variables to const ops.
I1206 19:20:09.723118 140737240245184 graph_util_impl.py:394] Converted 378 variables to const ops.
```

今度は成功しました。
所要時間は私の環境で30分ほど。あれ？思ったより少ない。





学習の状況が知りたいですね。
とりあえずどの説明でもTensorBoardを使って確認しているようなので、同じようにやってみました。

```
$ pip install tensorboard
$ tensorboard --logdir /tmp/retrain_logs
=> TensorBoard 1.15.0 at http://localhost:6006/ (Press CTRL+C to quit)
```

![スクリーンショット 2019-12-06 19.36.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/5bf2b420-135f-eed6-465a-5b25cf03b275.png)


ほうほう、それっぽいそれっぽい。



**私は読み方がわからないので何も活用ができないのですが、それっぽいのでヨシとしましょう。**






ちなみに、TensorBoardで表示するのに必要なログの出力先は`retrain.py`の中に書いてありました。


---

### 実行してみる


適当に用意した画像を使って、実行してみましょう。

実行用には、こちらのスクリプトを使います。
https://github.com/tensorflow/tensorflow/raw/master/tensorflow/examples/label_image/label_image.py

`retrain.py`は、`--saved_model_dir`オプションをつけない場合`/tmp`配下にモデルファイルが生成されるようですので、
`--graph`と`--labels`オプションでそれらを指定します。

*--追記--*
*--saved_model_dirオプションが付いているとここはうまくいかないようです。*
*pythonで実行してみたい方は、上記オプションを外してみてください。*

``` 
$ python label_image.py \
--graph=/tmp/output_graph.pb --labels=/tmp/output_labels.txt \
--input_layer=Placeholder \
--output_layer=final_result \
--image=画像ファイル
```

では、早速いってみましょう。

---


こちらは、**表参道のイルミネーション**の写真です。
![illmi.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/5c7509a1-1b34-2252-f4b5-2cede62ef850.jpeg)

```
christmas 0.9571908
nochristmas 0.042809136
```

約96%でクリスマスと判定されました。
なかなかの精度です。

---

こちらは、お肉のフリー素材サイト[oniku images](http://www.tenandoproject.com/onikuimages/index.html)様から頂いた**サーロイン**の画像です。
とても美味しそうです。
![oniku-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/5527bc74-8be2-0a1d-5f1b-a858b8372e2b.jpeg)


```
christmas 0.7308841
nochristmas 0.26911587
```

若干クリスマス度が高めです。
やはりクリスマスといえばご馳走ということなのでしょう。


---


こちらは、いつのまにか**クリスマス仕様になっていた弊社エントランス**です。

![niji.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/ac6a8b8d-7f1d-2b4f-809f-0d35e23fb9af.jpeg)

```
christmas 0.994396
nochristmas 0.0056040385
```

これもクリスマスと判定されました。
かなりいいですね。

---

こちらは、お肉のフリー素材サイト[oniku images](http://www.tenandoproject.com/onikuimages/index.html)様から頂いた**動きのあるお肉**の画像です。

躍動感に溢れています。
![oniku-2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/bb05c3b2-55c4-2ea7-4bba-02d3264869a6.jpeg)

```
christmas 0.5222237
nochristmas 0.47777635
```

お肉に躍動感はいらないかもしれません。



## Webに乗せる


さて、クリスマス度を判定してくれるモデルができたところで、
これをTensorFlow.jsで扱えるように変換していきます。

TensorFlowには、公式で変換ツールが用意されています。
https://github.com/tensorflow/tfjs/tree/master/tfjs-converter

`tfjs-converter`はpythonのTensorFlow.jsに付属しています。
ので、インストール。

```
$ pip install tensorflowjs
```


<details><summary>ここからの試行錯誤が色々ありました。興味のある方は開いてみてください。</summary><div>

https://github.com/tensorflow/tfjs/tree/master/tfjs-converter
こちらのReadmeを参照しながら、試してみます。


こんなことが書いてあります。
>The converter expects a TensorFlow SavedModel, TensorFlow Hub module, TensorFlow.js JSON format, Keras HDF5 model, or tf.keras SavedModel for input.

どうやら一口に「TensorFlowのモデル」といっても色々な形式があるようです。
変換時にはそれを指定する必要があります。


さて、今使っているモデルの形式は何なんでしょう？


>https://github.com/tensorflow/hub/raw/r0.1/examples/image_retraining/retrain.py

こちらは先ほどの学習済みモデルのスクリプトなのですが、urlには`tensorflow/hub`という記載があります。

ということは、
>TensorFlow Hub module

が正しそう？

```
$ tensorflowjs_converter \
    --input_format=tf_hub \
    /tmp/output_graph.pb \
    ./output/web_model

=> OSError: SavedModel file does not exist at: /tmp/output_graph.pb/{saved_model.pbtxt|saved_model.pb}
```

指定するパスが変なことになっていますね。`output_graph.pb`を使って欲しいのですが、
指定するのはディレクトリだけみたいですね。

```
$ tensorflowjs_converter \
    --input_format=tf_hub \
    /tmp \
    ./output/web_model

=> OSError: SavedModel file does not exist at: /tmp/{saved_model.pbtxt|saved_model.pb}
```

おおん？

エラーをよくみると、
>/tmp/{saved_model.pbtxt|saved_model.pb}

とあります。
つまり、`saved_model.pbtxt`か`saved_model.pb`というファイル名でないといけないようです。

リネームしてもう一度。

```
$ mv /tmp/output_graph.pb /tmp/saved_model.pb  
$ tensorflowjs_converter \
    --input_format=tf_hub \
    /tmp \
    ./output/web_model

=> RuntimeError: MetaGraphDef associated with tags 'serve' could not be found in SavedModel. To inspect available tag-sets in the SavedModel, please use the SavedModel CLI: `saved_model_cli`
```

`serve`というtagが見つからないそうです。何だそれは。

>`saved_model_cli`を使ってくれよな！

という記述があります。

やってみます。

```
$ saved_model_cli  show --dir /tmp
=> The given SavedModel contains the following tag-sets:
```

tagの中身は何も無いようです。。。
というか、さっきから`SavedModel`というのが何度も出てきています。

先ほどのtfjs_converterの説明によると、

>The converter expects a TensorFlow SavedModel, TensorFlow Hub module, TensorFlow.js JSON format, Keras HDF5 model, or tf.keras SavedModel for input.

どうやら`SavedModel`というのもモデルの形式の一つのようです。

このモデルは`tf_hub`でなく`SavedModel`なのでしょうか？


やってみましょう。
`--signature_name=serving_default`と`--saved_model_tags=serve`というオプションが追加されているようです。

```
$ tensorflowjs_converter \
    --input_format=tf_saved_model \
    --output_format=tfjs_graph_model \
    --signature_name=serving_default \
    --saved_model_tags=serve \
    /tmp \
   ./output/web_model

=> RuntimeError: MetaGraphDef associated with tags 'serve' could not be found in SavedModel. To inspect available tag-sets in the SavedModel, please use the SavedModel CLI: `saved_model_cli`
```

同じエラーが。。。


グラフの中に`serve`というタグが無いとのことです。
調べたところ、これを解消するには、`tf.saved_model.builder`を使うときに`[tf.saved_model.tag_constants.SERVING]`というキーワードを入れてあげれば良いようです。

`retrain.py`でモデルを保存している箇所に、その記述を追加すれば良さそうですね。

`retrain.py`の中を見てみます。
私はpythonリテラシーが極低なので、`save`とかそれっぽいキーワードでgrepして怪しいところを探してみました。


```python:retrain.py

...
    # Save out the SavedModel.
    builder = tf.saved_model.builder.SavedModelBuilder(saved_model_dir)
    builder.add_meta_graph_and_variables(
        sess, [tf.saved_model.tag_constants.SERVING],
        signature_def_map={
            tf.saved_model.signature_constants.
            DEFAULT_SERVING_SIGNATURE_DEF_KEY:
                signature
        },
        legacy_init_op=legacy_init_op)
    builder.save() # <- ここ？
```


お、`[tf.saved_model.tag_constants.SERVING]`に関する記述が初めからありますね。


この部分は`export_model（）`という関数の中にあります。
そもそもこの関数が呼ばれているのかどうかを確認調べてみたところ、下記の記述が。

```python:retrain.py

...
    if FLAGS.saved_model_dir:
      export_model(module_spec, class_count, FLAGS.saved_model_dir)
...
```

`FLAGS.saved_model_dir`が真でないと、そもそも呼ばれなさそうですね。
`FLAGS.saved_model_dir`がどこで代入されるかを調べると、こんな感じ。

```python:retrain.py

  parser.add_argument(
      '--saved_model_dir',
      type=str,
      default='',
      help='Where to save the exported graph.')
  FLAGS, unparsed = parser.parse_known_args()
```

どうやら、実行時のオプションにて`--saved_model_dir`を指定しないと正しい形で保存してくれないようです。
/tmp配下に保存されるものは一体。。。？

</div></details>


結論から言いますと、
**学習時に`--saved_model_dir`パラメータをつける**のが正しそうです。

```
$ python retrain.py --image_dir ./google-images-download/downloads/ --saved_model_dir ./SavedModel
```

これで、SavedModel配下にモデルが保存されます。
このモデルを`saved_model_cli`で確認してみると、

```
saved_model_cli show --dir ./SavedModel/                                                                                  (2019ac) 
The given SavedModel contains the following tag-sets:
serve
```

`serve`というタグが設定されています！良さそうですね。
もう一度、`tfjs_converter`で変換してみます。



*--追記--*
*この変換の際に、`--quantization_bytes`オプションをつけることで、量子化バイト数を指定し、出来上がるモデルの精度とサイズを調整することができました。*
*デフォルトでは`--quantization_bytes=4`になっているそうです。*


```
$ tensorflowjs_converter \
  --input_format=tf_saved_model \
  --output_format=tfjs_graph_model \
  --signature_name=serving_default \
  --saved_model_tags=serve \
  ./SavedModel/ \
  ./WebModel/web_model

...


2019-12-10 15:48:40.072180: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:788]   arithmetic_optimizer: Graph size after: 332 nodes (0), 376 edges (0), time = 168.506ms.
2019-12-10 15:48:40.072183: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:788]   dependency_optimizer: Graph size after: 332 nodes (0), 376 edges (0), time = 58.479ms.
2019-12-10 15:48:40.072186: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:788]   remapper: Graph size after: 332 nodes (0), 376 edges (0), time = 111.758ms.
2019-12-10 15:48:40.072273: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:788]   constant_folding: Graph size after: 332 nodes (0), 376 edges (0), time = 263.073ms.
2019-12-10 15:48:40.072296: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:788]   arithmetic_optimizer: Graph size after: 332 nodes (0), 376 edges (0), time = 165.07ms.
2019-12-10 15:48:40.072308: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:788]   dependency_optimizer: Graph size after: 332 nodes (0), 376 edges (0), time = 55.012ms.
Writing weight file ./WebModel/web_model/model.json...

```


おお！終わった！

上記コマンドで指定した`/WebModel/web_model`配下に、何やらファイルが作成されました。

```
WebModel/
└── web_model
    ├── group1-shard10of21.bin
    ├── group1-shard11of21.bin
    ├── group1-shard12of21.bin
    ├── group1-shard13of21.bin
    ├── group1-shard14of21.bin
    ├── group1-shard15of21.bin
    ├── group1-shard16of21.bin
    ├── group1-shard17of21.bin
    ├── group1-shard18of21.bin
    ├── group1-shard19of21.bin
    ├── group1-shard1of21.bin
    ├── group1-shard20of21.bin
    ├── group1-shard21of21.bin
    ├── group1-shard2of21.bin
    ├── group1-shard3of21.bin
    ├── group1-shard4of21.bin
    ├── group1-shard5of21.bin
    ├── group1-shard6of21.bin
    ├── group1-shard7of21.bin
    ├── group1-shard8of21.bin
    ├── group1-shard9of21.bin
    └── model.json
```

**多っ。**
全部で87MBもあります。

*--追記--*
*このあと、`--quantization_bytes=1`のオプションをつけて実行したところ、22MBまで削減できました。*
*正確に1/4になっていますね。*
*この程度の用途であれば精度への影響はほとんど感じませんでした。*
*デモもこの軽量化したモデルを使用しています。*


### JSから呼び出してみる

ここからはJSを使っていきます。

とりあえず、npmのモジュールとしては下記が必要になります。

```
npm i -S @tensorflow/tfjs @tensorflow/tfjs-converter
```

作成したモデルはやたら重たいですが、とりあえずjsから呼び出してみます。
呼び出すのは`model.json`だけでいいとのことですが、同じディレクトリにgroup1-xxxxx.binが全て揃っているような状態にします。


```javascript

import * as tf from '@tensorflow/tfjs';
import {loadGraphModel} from '@tensorflow/tfjs-converter';

...

tf.loadGraphModel('...../model.json').then((model) => {
    console.log(model)
})

```

何か出ました！今のところエラーは出ていないので順調のようです。

<img width="537" alt="スクリーンショット 2019-12-10 20.21.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/9de08a6d-55fb-c443-8d62-902ba6cd5192.png">


canvas要素に適当な画像を突っ込んで、getElementByIdとかで取得して、モデルに渡してみます。

```javascript
let model = null;
tf.loadGraphModel('...../model.json').then((model) => {
    model = model;
})

const channels = 3;
let inputImage = tf.browser.fromPixels(canvasElement, 3); // <- canvas要素からテンソルを作ってくれるらしい
console.log(inputImage);

let result = model.predict(inputImage);

```

下記のようなエラーが出ました。

<img width="1063" alt="スクリーンショット 2019-12-10 20.46.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/592fc36a-ba5e-ece2-b365-3ee3a93dba5c.png">


>入力に使うテンソルのシェイプは [-1, 299, 299, 3] でないといけないが、入力されたのは[720, 406, 3]である

てなところでしょうか。

渡したcanvas要素のシェイプを確認してみます。

```
let inputImage = tf.browser.fromPixels(canvasElement, 3);
console.log(inputImage.shape);
// => [720, 406, 3]
```

720, 406というのは渡したcanvas要素の縦横サイズです。
これを[-1, 299, 299, 3]に直さないといけないようです。

`label_image.py`で画像を分類させるときに、pythonスクリプト内で行なっている画像の下処理と同様のことを、JSで行わないといけないようです。


`label_image.py`はこんな感じ

```python:label_image.py

  float_caster = tf.cast(image_reader, tf.float32)
  dims_expander = tf.expand_dims(float_caster, 0)
  resized = tf.image.resize_bilinear(dims_expander, [input_height, input_width])
  normalized = tf.divide(tf.subtract(resized, [input_mean]), [input_std])
  sess = tf.compat.v1.Session() # <-ここはセッションの起動だから、JSでは無視
  result = sess.run(normalized) # <-ここで計算
```

tensorflow.jsのリファレンスをみながら、頑張ってみます。
https://js.tensorflow.org/api/1.0.0/

こんな感じ。

```javascript

// Pyrhonだと : float_caster = tf.cast(image_reader, tf.float32)
let float_caster = tf.cast(inputImage, 'float32');
console.log(float_caster.shape);
// => [720, 406, 3]

// Pythonだと : dims_expander = tf.expand_dims(float_caster, 0)
let dims_expander = float_caster.expandDims(0);
console.log(dims_expander);
// => [1, 720, 406, 3]

// Pythonだと : resized = tf.image.resize_bilinear(dims_expander, [input_height, input_width])
// ※ input_height, input_widthは共に299
let resized = tf.image.resizeBilinear(dims_expander, [299, 299]);
console.log(resized);
// => [1, 299, 299, 3]

// Pythonだと : normalized = tf.divide(tf.subtract(resized, [input_mean]), [input_std])
// ※ input_meanは0, input_stdは255
let normalized = tf.div(tf.sub(resized, [0]), [255]);
console.log(normalized);
// => [1, 299, 299, 3]


this.state.model.predict(normalized).print();
```


※シェイプの一つ目が`-1`ではなく`1`になっていますが、なぜかうまくいきました。理由はよくわかりません。



それっぽい結果がコンソールに出力されました。

<img width="242" alt="スクリーンショット 2019-12-10 21.11.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/dfeeb01a-cdd0-b147-bc5e-4081a088ebd5.png">

これがどうやら、pythonで画像分類を行なったときに出た

```
christmas xxx
nochristmas xxx
```

という結果と対応しているようです。
ラベルがないとわかりにくいですね。


### コードを整理する

生成したテンソルがメモリを食いつぶさないようにtidyで囲み、推定結果をpromiseで非同期に受け取るようにしました。
結果もテンソルではなく、配列で受け取るようにしています。

その他Reactで実装したりアレコレやっていますが、必要なnpmモジュールは[Github](https://github.com/ats05/christmas_scouter)を参照してもらえたらと思います。

```javascript

let tensor = tf.tidy(() => {
    const channels = 3;
    let inputImage = tf.browser.fromPixels(this.videoElement, 3);
    let float_caster = tf.cast(inputImage, 'float32');
    let dims_expander = float_caster.expandDims(0);
    let resized = tf.image.resizeBilinear(dims_expander, [299, 299]);
    let normalized = tf.div(tf.sub(resized, [0]), [255]);
    return normalized;
})
new Promise((resolve, reject) => {
    let result = this.state.model.predict(tensor).array(); // 値はarray()で取り出せる
    resolve(result);
}).then(result => {
    console.log(result);
})
```

<img width="392" alt="スクリーンショット 2019-12-10 21.19.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/dcb04c22-02db-4530-6277-7aec74f6d250.png">



**これらを元に、ブラウザとwebカメラで推定を行えるようにした[デモ](https://ats05.github.io/christmas_scouter/)を作成しました。**
https://github.com/ats05/christmas_scouter

アクセスしてしばらくは `--.-%`と表示されるかと思います。
TensorFlow.jsはどうも読み込む際に時間がかかるみたいです。



これを持って少し街に繰り出してみました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/1be4980a-3258-4966-5423-cd1973d5d2f2.png" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/f36dc2ba-8e86-b79d-fbe5-fac35e771654.png" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/84cd0cb6-e693-6ee4-b8ba-bc9457bdde8f.png" width=50%>
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/151247/431f810f-f7a1-4fab-705f-8c1c551aaac9.png" width=50%>

やはりイルミネーションは高スコアになりますね。






おしまい。

※特にオチはありません。