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

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 波の機械学習。スマホのテキストエディタで。』を公開しました。