post Image
ブラウザで手描き数字認識 (前処理付き版)

TL;DL

  • 機械学習において前処理って大事ですね。

はじめに

「そうだ、機械学習をしよう」と思って、とりあえず始めるのはMNISTを使った手書き数字認識だと思う。例えばChainerのサンプルとか動かして、ロスが減って「なんか学習できてるっぽいなぁ」と思うところまで行ったとする。次に「ちゃんと学習できてるか、学習させたモデルに自分の手書き文字を認識させてみよう」と思ってやってみると全然認識できなかったりして驚くわけですね。

この話を機械学習に詳しい人にしたら「ちゃんと前処理した?」と言われて、「いやしてないけど、こんな簡単な認識、前処理の有無でそうそう変わらないでしょ・・・」と思って放置してたのですが、自分の手書きデータをTensorFlowで予測するという記事を見て、「あぁ、やっぱり前処理って大事なんだ」と思って、ちゃんと前処理しました。

ソースコードは以下の場所においておきます。
https://github.com/kaityo256/mnist_check

オンラインで試すことができるデモはここです。
https://kaityo256.github.io/mnist_check/

とりあえず結果

オンラインデモを開くと、とりあえずこんな画面が出てきます。

image.png

データのロード中には「Now Loading … Please Wait」と表示されますが、それが消えたら操作可能です。

三画面ありますが、一番左が数字を描くキャンバス、真ん中がそのまま28×28に粗視化したもの、右が前処理を施したものです。

何か数字を描いてみましょう。前処理の効果が一番わかりやすいのは「1」だと思います。わざと左や右にずれた「1」を描いてみます。

image.png

image.png

右や左にずれた「1」を、そのまま学習済みモデルに食わせると「6」とか「4」に誤認識しますが、前処理が施されたものはちゃんと「1」と認識します。

「6」も左右にずれると誤認識しやすい数字です。

image.png

image.png

そのまま食わせた奴は「5」や「3」に誤認識しますが、前処理版は「6」と正しく認識しています。

一方、「9」は前処理版でもなかなか苦戦します。

image.png

image.png

なんか前処理なし版は「8」、前処理しても「4」と誤認識しますね。理由はよくわかりません。

前処理について

自分の手書きデータをTensorFlowで予測するという記事にある通り、MNISTのデータは前処理されています。

MNISTは20×20ピクセルに変換された画像(ただしアスペクト比は保ったまま)の重心を、28×28ピクセルの画像の中心に合わせた画像となってます。

実は僕はそれを知っていたのですが、「どうせたいして変わらんだろ」とそのままデータを28×28ピクセルに粗視化してモデルに食わせていました。しかし、前処理が大事そうだったので、入力イメージを20×20ピクセルに粗視化して、重心を移動したものを食わせるようにしましょう。ついでにオンラインデモのJavaScriptが何をやってるか簡単に説明します。学習済みモデルのJavaScriptでの読み込みについては前の記事を参照してください。

まず、入力データを28×28、もしくは20×20に粗視化したデータが欲しくなります。なのでCanvasのサイズを28と20の公倍数である420にしておきましょう。キャンバスに描かれたデータを、指定のサイズで粗視化したFloat32Arrayの配列で取得する関数はこんな感じにかけます。

function makedata(canvas, size){
  var h = canvas.height;
  var w = canvas.width;
  img = canvas.getContext('2d').getImageData(0,0,h,w);
  var data = new Float32Array(size*size);
  data.fill(0.0);
  var m = h/size;
  for(var i=0;i<size;i++){
    for(var j=0;j<size;j++){
      var sum = 0;
      for(var k=0;k<m;k++){
        for(var l=0;l<m;l++){
          x = i*m+k;
          y = j*m+l;
          var s = x+y*m*size;
          if (img.data[s*4]>128){
            sum++;
          }
        }
      }
    data[i+size*j] = 1.0*sum/m/m;
    }
  }
  return data;
}

JavaScript慣れてないのでいろいろこなれてないですがご容赦ください。これを使って、28×28のデータに粗視化してモデルに食わすには

data28 = makedata(canvas, 28);
var i = model.recognize(data28);

とすれば、変数iに認識された数字が返ってきます。

次に、前処理版です。とりあえず入力データを20×20に粗視化します。その後、その重心を計算し、中心からずれた分だけ補正して28×28のイメージにコピーします。20×20を28×28にコピーするので、上下左右に4ピクセルの余裕があると思って、最大+/-4ピクセルだけ補正することにします。したがって、重心が4ピクセル以上ずれた場合は補正しきれません。本当は画像が切れてしまっても重心を中心に補正してしまった方が良いのかもしれませんが、ここは手抜きします。

その処理をやってるのがdocs/draw.jsのこのあたりです。

  canvas.onmouseup =function(e){
    mouseDown=false;
    data28 = makedata(canvas, 28); //そのまま28x28に粗視化して食わせる
    data2canvas(data28, 28, canvas2) //粗視化した画像を真ん中のキャンバスに描画
    data20 = makedata(canvas, 20);  //まず20x20に粗視化する
    var xg, yg;
    [xg, yg] = centerofmass(data20,20); //重心を計算する
    xg -= 10.0;
    yg -= 10.0;
    xg = Math.min(xg, 4.0);
    yg = Math.min(yg, 4.0);
    xg = Math.max(xg, -4.0);
    yg = Math.max(yg, -4.0);
    var data28_s = datashift(xg, yg, data20); //重心のずれを補正したデータを取得
    data2canvas(data28_s, 28, canvas3) //右のcanvasに描画
    check(data28, data28_s); //補正なし、補正済みデータを数字認識させる
  }

こうして、左に入力した画像を、そのまま粗視化した場合と、前処理をして粗視化した場合の認識を確認するコードの出来上がりです。

まとめ

「機械学習は前処理が大事」とは耳にタコができるほど聞きますが、本当に大事だったんですね・・・ こうして実際に確認してみないとこういうのは実感できませんね。

参考


『 機械学習 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。