#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ファイル内容を変更あるいは追加することで、任意の画像ファイルを学習データとすることが出来ます。
##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種類の学習モデルを異なるパラメータ表現で示します。 なお、ここでのパラメータは最適化したものではありません。
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" }));
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」となります。
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のパラメータもそれに合わせています。
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/」を参照ください。