LoginSignup
0
0

More than 3 years have passed since last update.

TensorFlow.JSで手入力数字の認識を学習させてみた(初歩の初歩/その3)

Last updated at Posted at 2019-07-06

3、TensorFlow.JSで手入力数字の認識を学習

 定番中の定番であるMNISTを使用した学習をTensorFlow.JSで作成しました。 通常はMNIST特有仕様のデータを使用しますが、今回はPNG画像ファイルを使用しましたので学習データの取り扱いは容易かと思います。 但し、データの読み込みには時間を要しますが! 実際の動作は、ここ(TensorFlow.JSで手入力数字の認識を学習させてみた/2中間層)あるいはここ(TensorFlow.JSで手入力数字の認識を学習させてみた/3中間層)をクリックして下さい。 Windows 10の環境で「Firefox / Ver. 65.0.2 (64ビット)」及び 「Google Chrome / Ver. 73.0.3683.86 (64ビット)」で動作を確認しました。 また、Android 7.0でも動作しましたが、iOSでは上手く動作しませんでした。
 本プログラムの作成手順は、次の通りです。

3-1、準備

 学習データ、検証データ合わせて6万のPNG画像ファイルがありますので、次にようなファイルリスト(A列)とラベルデータ(B列)をセットにしたCSVファイルを作成しました。 このCSVファイルを作成するにはEXCEL+VBAを使用すると簡単ですよね。 なお、このCSVファイル内容を変更あるいは追加することで、任意の画像ファイルを学習データとすることが出来ます。
MNIST_File_List.png

3-2、学習モデルの作成

 MNISTの学習モデルについては、色々なサイトで解説されていますのでここでは割愛します。(「Deep Learning 深層学習 - 東京大学」が良い参考になると思います) 入力データは単色のPNG画像ですので、28(dw:横画素数)×28(dh:縦画素数)×1(色数)で1画像分のデータとなり、教師データは0~9までの数字10個のラベルに対応したコード(例:0は「1,0,0,0,0,0,0,0,0,0」、9は「0,0,0,0,0,0,0,0,0,1」の配列)にしています。 このため、評価結果は各配列に対応したそれぞれの確率で出力されます。 以下に2中間層と3中間層の2種類の学習モデルを異なるパラメータ表現で示します。 なお、ここでのパラメータは最適化したものではありません。

学習モデルの作成(2中間層)
let model = tf.sequential();
  model.add(
    tf.layers.conv2d({
      inputShape: [dw, dh, 1],
      kernelSize: 5,
      filters: 8,
      strides: 1,
      activation: "relu",
      kernelInitializer: "varianceScaling"
    })
  );
  model.add(tf.layers.maxPooling2d({ poolSize: [2, 2], strides: [2, 2] }));
  model.add(tf.layers.conv2d({ kernelSize: 5, filters: 16, activation: "relu", kernelInitializer: "varianceScaling" }));
  model.add(tf.layers.maxPooling2d({ poolSize: [3, 3], strides: [3, 3] }));
  model.add(tf.layers.flatten());
  model.add(tf.layers.dense({ units: 10, kernelInitializer: "varianceScaling", activation: "softmax" }));
学習モデルの作成(3中間層)
let model = tf.sequential();
  model.add(
    tf.layers.conv2d({
      inputShape: [dw, dh, 1],
      kernelSize: 3,
      filters: 16,
      activation: "relu"
    })
  );
  model.add(tf.layers.maxPooling2d({ poolSize: 2, strides: 2 }));
  model.add(tf.layers.conv2d({ kernelSize: 3, filters: 32, activation: "relu" }));
  model.add(tf.layers.maxPooling2d({ poolSize: 2, strides: 2 }));
  model.add(tf.layers.conv2d({ kernelSize: 3, filters: 32, activation: "relu" }));
  model.add(tf.layers.flatten({}));
  model.add(tf.layers.dense({ units: 64, activation: "relu" }));
  model.add(tf.layers.dense({ units: 10, activation: "softmax" }));

3-3、学習用データ、検証用データの読込

 読込んだ画像データ1枚毎にCANVASに28×28画素で表示させ、各1画素づつ数値データ化しています。 この数値データは0~255で表現されますが、そのままの数値「int32」形式では上手く動作しませんでしたので255で割った「float32」形式にしてxTrainDataに格納しました。 教師データとしてのラベル・コードは、yTrainDataに格納しています。 検証用データ(xTestDataとyTestData)も同様に処理しました。

学習用データの読込
    var ctx_train = canvas_train.getContext('2d');
    var Train_img = new Image();
    Train_img.src = "./Data/mnist_train/" + Temp_file;
    Train_img.onload = function() {
    ctx_train.drawImage(Train_img, 0, 0, dw * ds, dh * ds);
    };
    for(y=0; y<dh; y++) {
      for(x=0; x<dw; x++) {
        Temp_img = ctx_train.getImageData((x * ds), (y * ds), 1, 1);
        xTrainData.push( Temp_img.data[0] / 255 );
      }
    }
    Temp_label = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    if(Train_File_List[ Temp_num ][1] == Label[1]) {
        Temp_label = [0, 1, 0, 0, 0, 0, 0, 0, 0, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[2]) {
        Temp_label = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[3]) {
        Temp_label = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[4]) {
        Temp_label = [0, 0, 0, 0, 1, 0, 0, 0, 0, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[5]) {
        Temp_label = [0, 0, 0, 0, 0, 1, 0, 0, 0, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[6]) {
        Temp_label = [0, 0, 0, 0, 0, 0, 1, 0, 0, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[7]) {
        Temp_label = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[8]) {
        Temp_label = [0, 0, 0, 0, 0, 0, 0, 0, 1, 0];
    } else if (Train_File_List[ Temp_num ][1] == Label[9]) {
        Temp_label = [0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
    } else {
        Temp_label = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0];
    }
    yTrainData.push(Temp_label);

3-4、学習データと検証データをTensorFlow.JS用に変換

 画像データであるxTrainDataとxTestDataは tf.tensor4d(データ数、画像の横画素数、縦画素数、色数の4個)で、ラベルデータであるyTrainDataとyTestDataは tf.tensor2d(データ数、ラベル数の2個)でTensorFlow.JS用に変換しました。 ここでの色数は、単色ですので「1」となります。

学習データと検証データをTensorFlow.JS用に変換
  const xTrain = tf.tensor4d( xTrainData , [ (xTrainData.length/dw/dh), dw, dh, 1 ], 'float32' );
  const yTrain = tf.tensor2d( yTrainData, [ yTrainData.length, Label.length ], 'int32' );
  const xTest = tf.tensor4d( xTestData , [ (xTestData.length/dw/dh), dw, dh, 1 ], 'float32' );
  const yTest = tf.tensor2d( yTestData, [ yTestData.length, Label.length ], 'int32' );

3-5、学習の実行

 作成した学習データ xTrain と yTrain を使用して学習させ、検証データとして xTest と yTest を指定しています。 なお、callbacksの中身は、途中経過の表示です。

学習の実行
// 学習過程の設定
  model.compile({
    loss: "categoricalCrossentropy",
    optimizer: tf.train.sgd( optimize ),
    metrics: ['accuracy']
  });

// 学習と学習経過をdspに格納
  const history = await model.fit( xTrain, yTrain, {
    batchSize: BatchSize,
    epochs: epochData,
    validationData:[ xTest, yTest ],
    shuffle: true,
    callbacks: {
        onEpochEnd: async (epoch, logs) => {
        sampleData_x01.push(Train_count * 1.0 + epoch * 1.0);
        sampleData_y01.push(logs.loss);
        sampleData_z01.push(logs.val_loss);
        sampleData_x02.push(Train_count * 1.0 + epoch * 1.0);
        sampleData_y02.push(logs.acc);
        sampleData_z02.push(logs.val_acc);
        Learn_Chart_01();
        Learn_Chart_02();
        if(((epoch + 1) % 10) == 0) {
            dsp = dsp + ("      " + (Train_count + epoch)).slice(-7) + " : " + (logs.loss + "000000000000").slice(0,12) + " / " + (logs.acc + "000000000000").slice(0,12) + "  /  " + (logs.val_loss + "000000000000").slice(0,12) + " / " + (logs.val_acc + "000000000000").slice(0,12) + "\n";
            document.getElementById("dump").value = dsp;
        }
        },
    }
  });

3-6、評価用データの読込

 評価用データの入力には signature_pad を利用しています。 入力した手書き数字を28×28の学習データと同一サイズに変換して数値データ化しました。

評価用データの読込
    var EvData = [];
    var ctx_Ev = canvas_Ev.getContext('2d');
    var ctx_Ev_28 = canvas_Ev_28.getContext('2d');
    for(y=0; y<dh; y++) {
      for(x=0; x<dw; x++) {
        Temp_img = ctx_Ev.getImageData((x * ds_Ev), (y * ds_Ev), 1, 1);
        ctx_Ev_28.putImageData(Temp_img, x, y);
        EvData.push( Temp_img.data[0] / 255 );
      }
    }

3-7、評価用データをTensorFlow.JS用に変換

 学習用データと同様に評価用データもTensorFlow.JS用に変換します。 評価用データ数は1個ですので、tf.tensor4dのパラメータもそれに合わせています。

評価用データをTensorFlow.JS用に変換
    const inputData = tf.tensor4d( EvData, [ 1, dw, dh, 1 ] );

3-8、評価の実行

 ここでは、resultsに10個のラベル(0~9)に対応した評価結果(それぞれの確率)が配列として格納されます。

評価の実行
    const outputData = model.predict(inputData);
// 評価結果の表示
    var results = outputData.dataSync();
    document.getElementById('result_00').value = (Label[0] + ": " + Math.floor(results[0] * 100000 ) / 1000 + " %");
    document.getElementById('result_01').value = (Label[1] + ": " + Math.floor(results[1] * 100000 ) / 1000 + " %");
    document.getElementById('result_02').value = (Label[2] + ": " + Math.floor(results[2] * 100000 ) / 1000 + " %");
    document.getElementById('result_03').value = (Label[3] + ": " + Math.floor(results[3] * 100000 ) / 1000 + " %");
    document.getElementById('result_04').value = (Label[4] + ": " + Math.floor(results[4] * 100000 ) / 1000 + " %");
    document.getElementById('result_05').value = (Label[5] + ": " + Math.floor(results[5] * 100000 ) / 1000 + " %");
    document.getElementById('result_06').value = (Label[6] + ": " + Math.floor(results[6] * 100000 ) / 1000 + " %");
    document.getElementById('result_07').value = (Label[7] + ": " + Math.floor(results[7] * 100000 ) / 1000 + " %");
    document.getElementById('result_08').value = (Label[8] + ": " + Math.floor(results[8] * 100000 ) / 1000 + " %");
    document.getElementById('result_09').value = (Label[9] + ": " + Math.floor(results[9] * 100000 ) / 1000 + " %");

 実際の動作は、ここ(TensorFlow.JSで手入力数字の認識を学習させてみた/2中間層)あるいはここ(TensorFlow.JSで手入力数字の認識を学習させてみた/3中間層)をクリックして下さい。


TensorFlow.JSを使用してみたの記事内容

1、TensorFlow.JSで簡単な関数を学習させてみた
2、TensorFlow.JSでアヤメの分類を学習させてみた
3、TensorFlow.JSで手入力数字の認識を学習させてみた
4、TensorFlow.JSで画像の認識(分類)を学習させてみた

ここに記載したHTML/JavaScriptの動作を確認したい方は、「http://hal322.html.xdomain.jp/」を参照ください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0