Apr 09 2018 / 21:16:12
プログラミング > 機械学習 >
Keyword:

TensorFlow.jsを使って手書き数字解析器を作る上で躓いたこととか

こんにちは、元気ですか? 僕は最近鼻の調子が良くなくてずっと鼻をすすってます、CreativeGPです。

今日はTensorFlow.jsという比較的新しくできた(#1)TensorFlowという機械学習に使えるライブラリのJavascript版を使ってみようということで機械学習分野のHelloWorldらしいMNISTを使っての手書き数字解析器を作りたいと思いました。

制作の上ではこちらの記事を参考にさせていただきました。この記事はモデル作成からTensorFlow.jsを使ってWebアプリを作るところまでを幅広くカバーしているので同じような内容の記事が2つあってもデータの無駄、ということでこの記事はTensorFlow.jsを使う際に躓いたことに商店を当てて書いています(先程の記事では大まかな手順だけ書かれていたところですね。

#1 一応TensorFlowのリポジトリは2017年8月くらいに初期コミットがされてますが、この親であるdeeplearn.jsはもっと前から開発がされているようです。

※なんかここから常体になってますが気にしないで下さい※

ImageDataの縮小

ユーザーから入力された200x200のCanvasの画像を推論にかけるには28x28に縮小しなければならない。CanvasからはImageDataというオブジェクトが取得してこれが各ピクセルの色データを保持している。

このやり方も情報が少なく、このページが唯一僕が見つけられるページだった。ここのコメントにもあるようにこの回答は少し冗長なやり方をしているので改善したものをjsFiddleにうpしたので参考にしていただきたい。

入力用Tensorを作成

ImageDataはプロパティが全て読み取り専用だということで、つまりコンストラクタから生成しなければならないのだが、コンストラクタに指定できる配列のサイズが決まっていることと、型も決まっていることで扱いにくい。このような特性のせいで正規化をする際に小数の格納ができない。

なので最初にtf.fromPixels()を使ってTensorを生成しなければならないのだがここでハマった。

tf.fromPixels()はImageDataと1~4のチャンネル数をとる。チャンネル数が4の場合は


[255, 200, 0, 100, 50, 3, 80, 72, 40]:ImageData.data -> [[255, 200, 0, 100], [3, 80, 72, 40]]:Tensor

という風に色データが単純に4個ずつに区切られて入力される。この場合はモノクロなのでチャンネル数を1にするのだがこのときは、


[255, 200, 0, 100, 50, 3, 80, 72, 40]:ImageData.data -> [[255], [3]]:Tensor

という風に4個ずつに分解されたときの一番最初の要素だけを使ってTensorが構成される。しかし、Canvasから取得した色データはユーザーの入力が4i + 3番目に入っており、普通に渡したのでは期待する動作をしてくれないので、変換を行う必要があった(#2)。行った変換は単純で


let monodata = []; for (let i=0, len = imgdata.data.length/4; i < len; i += 1) { monodata.push(imgdata.data[i*4+3]); monodata.push(0); monodata.push(0); monodata.push(0); } let monoimgdata = new ImageData(new Uint8ClampedArray(monodata), 28, 28);

新たな配列を作ってそこにユーザーの入力データが4i番目に来るようにして後は0パディングするようにし、その後で新たなImageDataるようなものである。パディングはImageDataのコンストラクタに渡す配列の大きさはwidth(28) x height(28) x 4のサイズしか許されなかった為必須である。

ようやくできたmonoimgdataをtf.fromPixelsに渡して、整形して、型を変換して(#3)、正規化すれば(#4)、入力用テンソルが完成する。


let input = tf.fromPixels(monoimgdata, 1).reshape([1, 28, 28, 1]).cast('float32').div(tf.scalar(255));

#2 この変換はCanvasの塗る色の設定を変えることで不要になるかもしれない。

#3 理由は、この学習データを使った推論の入力Tensorはfloat32ではないといけないと怒られるということと、255で割ると小数が出てくるからということがある。

#4 データは0~255の範囲にあるので255で割る。