モバイル&ワイヤレスブロードバンドでインターネットへ

gwaw.jp

tfjs で sin 波の機械学習。スマホのテキストエディタで。

TensorFlow.js (tfjs) は、ブラウザと Node.js 内で機械学習モデルの訓練とデプロイを行うための JavaScript ライブラリです。

https://www.tensorflow.org/js/tutorials

これまで、QuickEdit Text Editor Pro で TensorFlow.js の CODE SAMPLE FOR SCRIPT TAG SETUP をプレビューtfjs-vis で sin 波を。スマホのテキストエディタで。tfjs で Early Stopping。スマホのテキストエディタで。 の記事で tfjs についてみてきましたが、今回はひとつの目標としていた tfjs sin 波の機械学習を試します。



<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>サンプル</title>

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"> </script>
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-vis"></script>

<script>
var _debug;
var _dct = 0;
function _debugStart(){
  _debug = document.getElementById("DEBUG");
  _debug.textContent = "";
  _debugHTML("DEBUG START");
}
function _debugHTML(_deHTML){
  _debug.insertAdjacentHTML("afterbegin", "<div><em>" + ++_dct + " : </em>" + _deHTML + "<div>");
}
console.log = function(_dobj){
  _debugHTML(_dobj);
};
</script>
</head>
<body>

<div id="trainStatus"></div>
<div id="lossChart"></div>
<div id="predictStatus"></div>
<div id="sinChart"></div>

<div id="DEBUG"></div>

<script>
_debugStart();
_debugHTML("tfjs sin START");

function tensor_sin(T=100, ampl=0.05){
  return tf.tidy(() => {
    const L = 2 * T + 1;
    const noise = tf.randomUniform([L], -1.0, 1.0);
    const an = tf.mul(noise, tf.scalar(ampl));
    const x = tf.range(0, L);
    const xp = tf.mul(x, tf.scalar(2.0 * Math.PI));
    const sx = tf.sin(tf.div(xp, tf.scalar(T)));
    const sn = tf.add(sx, an);
    return sn;
 });
}

class EarlyStopping{
  constructor(patience=0, verbose=0){
    this._step = 0;
    this._loss = Number.POSITIVE_INFINITY;
    this.patience = patience;
    this.verbose = verbose;
  }
  validate(loss){
    if(this._loss < loss){
      this._step += 1;
      if(this._step > this.patience){
        if(this.verbose){
          console.log("early stopping");
        }
        return true;
      }
    }else{
      this._step = 0;
      this._loss = loss;
    }
    return false;
  }
}

// 即時関数 Immediately Invoked Function Expression ( IIFE )
(async () => {

  // データの生成
  const T = 100;
  const f = tensor_sin(T, 0.05);
  const v = tensor_sin(T, 0.05);
  const n = tensor_sin(T, 0.0);

  // 時系列データの作成
  const length_of_sequences = 2 * T;
  const maxlen = 25;  // ひとつの時系列データの長さ

//  const farray = f.dataSync();
  const varray = v.dataSync();
//  const narray = n.dataSync();

  const fdata = [];
  const vdata = [];
  const target = [];

  for(i=0; i < length_of_sequences - maxlen + 1; i++){
    fdata.push(f.slice(i, maxlen));
    vdata.push(v.slice(i, maxlen));
    target.push(n.slice(i + maxlen, 1));
  }

  const N_train = length_of_sequences - maxlen + 1;
  const N_validation = length_of_sequences - maxlen + 1;

  const X_train = tf.stack(fdata).reshape([N_train, maxlen, 1]);
  const X_validation = tf.stack(vdata).reshape([N_validation, maxlen, 1]);
  const Y_train = tf.stack(target).reshape([N_train, 1]);
  const Y_validation = tf.stack(target).reshape([N_validation, 1]);

  // モデル設定
  const n_in = 1;
  const n_hidden = 20;
  const n_out = 1;

  const model = tf.sequential();
  model.add(tf.layers.simpleRNN({
    units: n_hidden,
    kernelInitializer: "randomNormal",
    inputShape: [maxlen, n_in]
  }));
  model.add(tf.layers.dense({
    units: n_out,
    kernelInitializer: "randomNormal"
  }));
  model.add(tf.layers.activation({
    activation: "linear"
  }));

  // compile
  model.compile({
    loss: "meanSquaredError",
    optimizer: "adam",
    metrics: ["accuracy"]
  });

  // fit & predict
  const iterations = 100;
  const epochs = 1;
  const batchSize = 20;

  const lossValues = [[], []];
  const accuracyValues = [[], []];
  let epochcnt = 0;
  const early_stopping = new EarlyStopping(10, 1);

  for (let i = 0; i < iterations; ++i) {
    const beginMs = performance.now();
    const history = await model.fit(X_train, Y_train, {
      epochs: epochs,
      batchSize: batchSize,
      validationSplit: 0.1,
      validationData: [X_validation, Y_validation],
      callbacks: {
        onEpochBegin: async (epoch, logs) => {
          epochcnt = epoch;
        },
        onBatchEnd: async (batch, logs) => {
          let elapsedMs = performance.now() - beginMs;
          let modelFitTime = elapsedMs / 1000;
          document.getElementById("trainStatus").textContent = `Iteration ${i+1} of ${iterations} (Epoch ${epochcnt+1}, Batch ${batch}): Fit time ${modelFitTime.toFixed(3)}s`;
        },
        onEpochEnd: async (epoch, logs) => {
          // Early Stopping チェック
          if(early_stopping.validate(logs["val_loss"])){
            model.stopTraining = true;
           } 
         }
       }
    });

    let elapsedMs = performance.now() - beginMs;
    let modelFitTime = elapsedMs / 1000;
    document.getElementById("trainStatus").textContent = `Iteration ${i+1} of ${iterations}: Fit time ${modelFitTime.toFixed(3)}s`;

    lossValues[0].push({"x": i, "y": history.history.loss[0]});
    lossValues[1].push({"x": i, "y": history.history.val_loss[0]});
    accuracyValues[0].push({"x": i, "y": history.history.acc[0]});
    accuracyValues[1].push({"x": i, "y": history.history.val_acc[0]});

    const lossContainer = document.getElementById("lossChart");
    tfvis.render.linechart(
      lossContainer,
      {values: lossValues, series: ["train", "validation"]},
      {
        width: 300,
        height: 300,
         xLabel: "Iteration",
         yLabel: "loss"
    });

    let predicted = [];
    const predictMs = performance.now();
    tf.tidy(() => {
      let y = v.slice(0, maxlen).reshape([maxlen]);
      for(let ix=0; ix < T - maxlen + 1; ix++){
        const Z = v.slice(ix, maxlen).reshape([1, maxlen, 1]);
        const Y = model.predict(Z);
        y = y.concat(Y.slice(0, 1).reshape([1]));
      }
      predicted = y.dataSync();
    });
    let predictTime = (performance.now() - predictMs) / 1000;
   document.getElementById("predictStatus").textContent = `Iteration ${i+1} of ${iterations}: Predict time ${predictTime.toFixed(3)}s`;

    const sinValues = [[], []];
    for (let i = 0; i < T; i++) {
      sinValues[0].push({"x": i, "y": varray[i]});
      sinValues[1].push({"x": i, "y": predicted[i]});
    }
    const sinContainer = document.getElementById("sinChart");
    tfvis.render.linechart(
      sinContainer,
      {values: sinValues, series: ["varidation", "predicted"]},
      {
        width: 300,
        height: 300,
        xLabel: "time",
        yLabel: "value"
    });
    if(model.stopTraining){
      break;
    }
  }

})();

_debugHTML("tfjs sin E N D");
</script>
</body>
</html>

 

まず、 tensor_sin() ファンクションは、 tfjs-vis で sin 波を。スマホのテキストエディタで。 にて作成した sin 波のデータをテンソルで返す関数です。そして、 EarlyStopping クラスは、tfjs で Early Stopping。スマホのテキストエディタで。にて作成した Early Stopping を判断するためのクラスです。

sin 波のデータは、3つ用意しました。訓練用のデータ(ノイズあり)と、検証用のデータ(ノイズあり)、そしてノイズなしのデータとしました。

そして、訓練用と検証用のデータについて、時系列データを作成します。連続する 25 の個々のデータをひとつの時系列データとします。それをひとつずつずらしながら繰り返して作成いきます。また、ノイズなしのデータは、26 番目のデータからひとつずつ取っていきます。連続する 25 のデータからそのひとつ先の値を学習して予測することになります。

tf.slice() は、テンソルから指定された位置で切り出します。 tf.stack() は、複数の同形のテンソルをひとつのテンソルにまとめます。テンソル(オブジェクト)を JavaScript の配列の要素にしたものが引数となります。 tf.reshape() は、テンソルを変形します。 tf.concat() は、テンソルを連結します。

モデルの fit predict の組み合わせで1イテレーションとして、これを 100 回繰り返します。エポック数は1としました。これで学習の進み具合を頻繁に確認できるようにしました。

なお、 このページが表示されると、イテレーション 10 回で学習を開始して、学習の進み具合を更新しています。10 回では不完全なのですが、良さそうに進行していることは感じられると思います。

ここまで、まだまだ不慣れで、どうなんだろう?、ということがまだまだあります。それでも、 TensorFlow.js (tfjs) で、ファーストステップ、ひとつの目標に届いたかな思います。

さて、今回使用している環境は、Android スマホのテキストエディタアプリ QuickEdit Text Editor Pro です。プレビュー機能で HTML ドキュメントの JavaScript コードを実行することで確認しています。 QuickEdit Text Editor Pro で TensorFlow.js の CODE SAMPLE FOR SCRIPT TAG SETUP をプレビュー で記事投稿した方法です。文法 Error の Debug はできませんが、console.log を出力するためのコードを追加しています。

今回参照している書籍です。なお、本書での実装は Python です。

詳解 ディープラーニング ~TensorFlow・Kerasによる時系列データ処理~

Amazon.co.jp
商品詳細リンク

『tfjs で sin 波の機械学習。スマホのテキストエディタで。』を公開しました。