待ちに待った瞬間！TensorFlow.js にモデルのセーブ機能がついた！

今日は皆さん。

待ちに待った瞬間がついに来ました。
あの、tensorflow.js ( 旧deeplearn.js ) にモデルのセーブ機能がついたとのこと。
これはもうとっとと試すしかないな！と思うわけです。
いやー、私の仕事用・・・手元のPCはGTXなんて積んじゃいないんで、機械学習させるとCPUでやる羽目になって、遅いんですよね。
それがブラウザのWebGL経由でGPUを使えるのだから、存分に使わざるを得ないじゃないですか！

# TL;DR

- tensorflow.js で keras っぽくモデルが書ける
- tensorflow.js で モデルを保存する
- tensorflow.js で モデルをロードする

# まずはKerasっぽくモデルを書いてみる

tensorflow.js は本家の tensorflow にかなり寄せようとしているようで、tfの中に取り込まれているkeras風の書き方も簡単に実装できます。

超簡単に $f(x) = 2 x - 1$という単純な計算を学習するモデルを、( 無駄に )３層のdenseで書いてみます。

```index.html
<html>
<head>
  <meta charset="UTF-8">
</head>
<body>
  <script src='jssrc/test.js'></script>
</body>
</html>
```
このhtmlファイルに関してですが、以降で別のjsファイルを作ってますが、大元のindex.htmlの参照しているjsファイルも、それに従って変えてください。

```jssrc/test.js
import * as tf from '@tensorflow/tfjs'

const model = tf.sequential()
model.add(tf.layers.dense({units: 32, inputShape: [1]}));
model.add(tf.layers.dense({units: 64}));
model.add(tf.layers.dense({units: 1}));
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});

const xs = tf.tensor2d([[1], [2], [3], [4]], [4, 1]);
const ys = tf.tensor2d([[1], [3], [5], [7]], [4, 1]);// 2n - 1

async function main() {
  await model.fit(xs, ys, {epochs: 100});

  // Run inference with predict().
  model.predict(tf.tensor2d([[5]], [1, 1])).print();
}

main()
```
まあ、当然このままだとブラウザ上では動いてくれないので、手軽にParcelでも使います

```package.json
{
  "dependencies": {
    "@tensorflow/tfjs": "^0.11.1",
    "axios": "^0.18.0",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.7.0",
    "parcel-bundler": "^1.8.1"
  },
  "scripts": {
    "server": "parcel index.html -p 8888 --out-dir public --no-hmr",
    "watch": "parcel watch index.html --out-dir public --no-hmr",
    "build": "parcel build index.html --out-dir public"
  }
}
```
例によって、私はコンテナで動かすわけですが、最近のnode はyarn同梱なので、yarn使います

```
docker run -it --rm -v `pwd`:/var/www -p 8888:8888 node:alpine sh
cd var/www
yarn
```
これで動くかと思ったのですが、`.babelrc`をちゃんと書かなきゃいけないらしいので、次のファイルも追加しておきます。

```json:.babelrc
{
  "presets": ["env"],
  "plugins": [
    "transform-runtime"
  ]
}
```
あとは、コマンドラインでparcel動かしておけばいい感じです。

```
yarn server
```
これで、あとはブラウザでアクセスするだけです。
ただ、index.html見ればわかりますが、何も表示されません。
devtoolでconsoleをのぞいてください。

```
Tensor
     [[8.8142004],]
```
こんな値が出てくると思います。
$x = 5$の時の答え( $5 \times 2 - 1 = 9$ )を予想させているので、まあ、近い値にはなっていますね。

# モデルを保存してみる
ここからが今回の目玉ですね。
モデルの保存方法はブラウザ上ということもあって、いくつも用意されていますが、保存用のインターフェースは共通です。
先ほどの学習機にモデルのセーブを追加してみましょう

```jssrc/save_test.js
import * as tf from '@tensorflow/tfjs'

const model = tf.sequential()
model.add(tf.layers.dense({units: 32, inputShape: [1]}));
model.add(tf.layers.dense({units: 64}));
model.add(tf.layers.dense({units: 1}));
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});

const xs = tf.tensor2d([[1], [2], [3], [4]], [4, 1]);
const ys = tf.tensor2d([[1], [3], [5], [7]], [4, 1]);// 2n + 1

async function main() {
  await model.fit(xs, ys, {epochs: 100});

  // Run inference with predict().
  model.predict(tf.tensor2d([[5]], [1, 1])).print();
  model.save('localstorage://test')
}

main()
```
単純に最後の方に `model.save(storage)`が追加されました。
モデルの保存方法はこの`storage`の中身を変えることをで、いくつか選択することができます。
とりあえず、公式にある保存方法を紹介しましょう。

## Local Storage
今時のブラウザだったら大体使えるLocal Storageです。
こんな感じで書きます。

```js
await model.save('localstorage://test')
```
気軽に使えるのですが、容量制限があるので、それなりに大きなモデルを扱うと、すぐに制限に引っかかります。

## indexedDB
私、これ知らなかったんですが、Local Storageよりも大容量のデータを高速で検索できる、らしいです。
https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API

格納方法はLocal Storageに似ていて

```js

await model.save('indexeddb://test')
```
です。
容量の大きなデータも使えるのですが、secret modeでの対応ができていないブラウザが多いので、どんな時でも使えるのか不明です。

## ダウンロード
モデルをダウンロードするのも手です。
KerasみたいなCLIで使用できるモデルに直すには、converterが必要ではありますが、なんにせよ、ファイルとして落とすと色々と取り回しがしやすいかなと

例によって格納方法はこんな感じです。

```js
await model.save('downloads://test')
```
これによって、ファイルでモデルをダウンロードできますが、注意点として、ファイルは2つ、構造定義とパラメータ定義のファイルに分けられてダウンロードされます。
検証とかするときは、油断していると大量のファイルに四苦八苦する羽目になります。

## http アップロード
Webシステム上に学習機構を組み込んでいるならば、httpアップロードするのも手ですね。
ただ、Webシステム側に受け入れ機構が必要なのと、どのようなリクエスト形式になっているのかがよくわかっていないので、試していないです。

書き方はほぼ同じです。

```js
await model.save('https://example.com/upload')
```

# モデルをロードする
モデルのロードは前からできていましたが、今回のsave機能を利用して取得したモデルをロードすることで、ブラウザ上で学習を完結させることができます。

モデルは次のようにロードして、使用できます。

```load_test.js
import * as tf from '@tensorflow/tfjs'

async function main() {
  const model = await tf.loadModel('localstorage://test')
  model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
  model.predict(tf.tensor2d([[5]], [1, 1])).print();
}

main()
```
当然ですが、さきほどのsave_test.jsでモデルを保存した上で、これをブラウザ上で実行すると

```
Tensor
     [[8.8142004],]
```
この結果が即座に返ります。

モデルのロードの方法も、保存と同じようにLocal Storage, indexedDB, HTTP, ファイルアップロードの方法があります。
基本的にはモデルの保存と変わらず、`スキーム://place`みたいな感じになります。
ファイルのアップロードだけは、フォームを作る必要があり、

```html
<input id="json-upload" type="file" />
<input id="weights-upload" type="file" />
```
というフィールドを作っておき、

```js
const jsonUpload = document.getElementById('json-upload');
const weightsUpload = document.getElementById('weights-upload');

const model = await tf.loadModel(
    tf.io.browserFiles([jsonUpload.files[0], weightsUpload.files[0]]));
```
とする必要があります。
(公式だと、ファイルアップロード部分のフォームが、`id` => `name`になっていて、うまく動いてくれなかった)

# まとめ
というわけで、TensorFlow.jsにモデルの保存機能が入ったので、軽く触ってみました。
実は、conv1dを使った複雑なモデルを保存させてみたのですが、ロード時におかしな状態になっていたので、まだ発展途上なのかなって印象を感じています。

今回はこんなところです。

# 参考
[公式のチュートリアル?](https://js.tensorflow.org/tutorials/model-save-load.html)