1次元畳み込みを利用した時系列解析をTensorFlow.jsを使ってやって見た

今日はみなさん

[前回の記事にて](https://qiita.com/niisan-tokyo/items/964787a30dfd3a21beb2)TensorFlow.jsでconv1dのモデルロード時におかしなことになっているということを書いたのですが、先ほど (2018/5/24 16:20現在) 解消されたことが確認されたため、早速１次元畳み込みを利用したモデルを作ってみようと思い立ちました。

# TensorFlow.jsにおけるconv1d

まずは、モデルを決めるわけですが、問題とモデルについては、以前にKerasのアドカレでやったものを流用することにします。
https://qiita.com/niisan-tokyo/items/a94dbd3134219f19cab1

ここにあるモデルを同じものをTensorFlow.jsで組むには、次のように書きます。

```model.js
const model = tf.sequential()
model.add(tf.layers.conv1d({filters: 64, kernelSize: 8, padding: 'same', inputShape: [64, 1], activation: 'relu'}))
model.add(tf.layers.maxPooling1d({poolSize: 2, padding: 'same'}))
model.add(tf.layers.conv1d({filters: 64, kernelSize: 8, padding: 'same', activation: 'relu'}))
model.add(tf.layers.maxPooling1d({poolSize: 2, padding: 'same'}))
model.add(tf.layers.conv1d({filters: 32, kernelSize: 8, padding: 'same', activation: 'relu'}))
model.add(tf.layers.conv1d({filters: 1, kernelSize: 8, padding: 'same', activation: 'tanh'}))
```
PythonのKerasで書いたのと同じようにかけるわけです。
注意点としては、Kerasのパラメータの書き方は snake_case でしたが、TensorFlow.js では、 lowerCamelCase になっているということくらいです。
知らないと割と戸惑いますが、一回書いちゃえば楽チンです。

# 訓練させてみる
1次元畳み込みモデルで、時系列データを解析させて見ましょう。

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

const save_model = 'indexeddb://conv1d_test'
const load_model = 'indexeddb://conv1d_test'

// モデルの作成
const model = tf.sequential()
model.add(tf.layers.conv1d({filters: 64, kernelSize: 8, padding: 'same', inputShape: [64, 1], activation: 'relu'}))
model.add(tf.layers.maxPooling1d({poolSize: 2, padding: 'same'}))
model.add(tf.layers.conv1d({filters: 64, kernelSize: 8, padding: 'same', activation: 'relu'}))
model.add(tf.layers.maxPooling1d({poolSize: 2, padding: 'same'}))
model.add(tf.layers.conv1d({filters: 32, kernelSize: 8, padding: 'same', activation: 'relu'}))
model.add(tf.layers.conv1d({filters: 1, kernelSize: 8, padding: 'same', activation: 'tanh'}))

model.compile({optimizer: 'adam', loss: 'meanSquaredError'})

// データの作成
let raw_data = []
for (let i = 0; i < 10000; i++) {
  raw_data.push((Math.sin(i) + Math.sin(3 * i) + Math.sin(10 * i) + Math.cos(5 * i) + Math.cos(7 * i)) / 5)
}

let xs = []
let ys = []

for (let j = 0; j < 9900; j++) {
  xs.push(raw_data.slice(j, j+64))
  ys.push(raw_data.slice(j+64, j+80))
}

const train_X = tf.tensor2d(xs, [9900, 64]).reshape([9900, 64, 1])
const train_Y = tf.tensor2d(ys, [9900, 16]).reshape([9900, 16, 1])

// 訓練
async function train() {
  const start = new Date()
  console.log('start!!')
  await model.fit(train_X, train_Y, {epochs: 10, validationSplit: 0.1})
  console.log('end!!')
  const saveResult = await model.save(save_model)
  model.predict(tf.tensor3d(raw_data.slice(9600, 9664), [1, 64, 1])).print()
  console.log(raw_data.slice(9664, 9680))
  const end = new Date()
  console.log((end - start) / 1000)
}

train()
```
データの作成が若干面倒なものの、そこまで難しいコードになっているわけでもありませんね。
時間計測は `console.log((end - start) / 1000)`で出力していて、10epochで78.6秒という結果になりました。
訓練後はモデルのサイズが大きいので、localstorage ではなく indexedDBに入れています。

# 予測してみる

せっかくモデルが保存できたのだから、モデルをロードして予測してみましょう。

```use.js
const load_model = 'indexeddb://conv1d_test'
async function predict(input, output) {
  const model = await tf.loadModel(load_model)
  tf.tidy(() => {
    const x = tf.tensor3d(input, [1, 64, 1])
    const y = tf.tensor3d(output, [1, 16, 1])
    const res = model.predict(x)
    res.print()
    y.print()
    y.sub(res).print()
  })
}

const output = raw_data.slice(9664, 9680)
const input = raw_data.slice(9600, 9664)
predict(input, output)
```
予測値と期待値、およびその誤差を出してみました。
誤差はこんな感じです

```
Tensor
    [[[0.0092286 ],
      [0.0060897 ],
      [-0.0017973],
      [0.0042615 ],
      [-0.0065808],
      [0.0222413 ],
      [0.0032337 ],
      [0.0058675 ],
      [-0.0066878],
      [-0.005791 ],
      [0.0043528 ],
      [-0.0015932],
      [0.005485  ],
      [-0.0038473],
      [-0.0019049],
      [0.0048645 ]]]
```
うーん、まあ、こんなところなのかな。

# でもやっぱり遅い？
ついでなので、PythonのKerasを使った場合との速度差を比較して見ます。
モデル自体は[以前の記事](https://qiita.com/niisan-tokyo/items/a94dbd3134219f19cab1)を参照ください。
で、これで比較すると、Kerasを使った場合私の環境では、10epochsの学習時間が38.9秒となりました。
つまり、CPUを使っているはずのKerasの方が2倍も早いということです。

ちなみに、`tf.setBackend('cpu')`によって、jsの方のバックエンドをcpuにすると、これだけの学習がいつまでも終わらなくなります。

よくわからないですが、TensorFlow.js + webgl はpython の TensorFlow使った場合に比べて遅いらしいです。
https://www.publickey1.jp/blog/18/tensorflowjswebwebglgpu.html
KerasはバックエンドでTensorFlow使っていますし( xeonじゃないけど )、計測した内容に近いので、まあ、そうなんでしょう。
まだまだ、一般gpuによる超高速学習への道は険しいのかもしれません。

# まとめ
conv1dを利用した時系列解析をTensorFlow.jsで実装して見ました。
ついでに、修正されたモデルの保存機能を試して、別のjsから保存したモデルをロードする機能を試して、推量を実施して見ました。

ブラウザ上で動かしているためか、どうしても計算速度が遅くなってしまうところですが、そこはwebglを使って補っている感じでしょうか。
なんにせよ、JavaScriptでも本格的な機械学習ができるようになりつつあって、喜ばしい限りです。

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