手書き文字を作れるJavascriptをつくってTensorFlowで予測させてみた(2)

この前、「手書き文字を作れるJavascriptをつくってTensorFlowで予測させてみた」という投稿でブラウザ上で手書きした文字画像を、MNISTで訓練したモデルで予測してみましたが、ものすごく精度が悪かったです。今回改めて、CNNを使ってやってみたらかなり精度が上がりました。何%か測ったりしてませんが、自分の手書きだと90%は超える感じでした。やっぱりCNNはすごいなーと思いました。でももしかしたら前回のものにミスがあり、CNNではなくても精度は本当はもっと高い可能性はあります。

もうちょっとやるとしたら、文字を画像の中心に適度な大きさで書く必要があり、例えば右上に小さく2と書いても認識されません。あとは、現在はMNISTに合わせて、手書き文字画像も背景黒、文字色白で作成するように固定していますが、これらの色を変えても認識するようにしたいです。今度やってみます。

Github

https://github.com/endoyuta/mnist_test

index.html

cgi-bin/mnist.py

cgi-bin/mytensor.py

TensorFlow – Local Response Normalization(LRN)(局所的応答正規化)

参考:theanoで局所コントラスト正規化(Local Contrast Normalization)を使う

正規化の方法にはいろいろあり、代表的なものを挙げると

Global Contrast Normalization(GCN)
Local Contrast Normalization(LCN)
Local Response Normalization(LRN)
ZCA whitening
Local mean subtraction

CNN内の正規化層としては、LCNやらLRNが使われる。

LCNはその名の通り、特徴マップの局所領域内でコントラストを正規化する。この処理は一つの特徴マップ内で完結するので、すべての特徴マップに対して独立して行う。
対してLRNでは、同一位置における異なる特徴マップ間で正規化する。どちらもいくつかのハイパーパラメータはあるが、学習の対象となるパラメータはないので、誤差伝播が容易に可能である。

参考:theanoでLocal Response Normalization(LRN)を使う

LRNは端的に述べると、「同一位置(ピクセル)において複数の特徴マップ間で正規化する」ということだそうだ。元の論文にも書いてあるが、LRNは”brightness normalization”であり、LCNのように輝度の平均を減算して0にしないことがミソらしい。


$$\displaystyle
b^i_{x,y}=a^i_{x,y}/ \left( k+\alpha \sum^{min(N-1,i+\frac{n}{2})}_{j=max(0,i-\frac{n}{2})} (a^j_{x,y})^2 \right)^\beta
$$

k, n, α, βがパラメータである{a^i_{x,y}}はi番目の特徴マップの(x,y)のピクセルを、Nは特徴マップの総数を表す。
summationの部分は、「i番目の特徴マップに対して、n近傍の特徴マップの二乗和をとる」という意味である。

参考:tf.nn.local_response_normalization(input, depth_radius=None, bias=None, alpha=None, beta=None, name=None)

Local Response Normalization.

The 4-D input tensor is treated as a 3-D array of 1-D vectors (along the last dimension), and each vector is normalized independently. Within a given vector, each component is divided by the weighted, squared sum of inputs within depth_radius. In detail,

翻訳結果

4次元入力テンソルは、(最後の次元に沿って)1次元ベクトルの3次元配列として扱われ、各ベクトルは独立して正規化されます。 所与のベクトル内で、各成分は、depth_radius内の入力の加重二乗和で除算される。

使用例

Python3で関数をつくってみる

使ってみる

結果

tf.nn.lrnを使ってみる

コード

結果

おーほぼほぼ同じだ。適当な別の配列でも試してみよう。

適当な配列でも試してみる

適当な配列

結果

自作関数の結果

tf.nn.lrnの結果

ほぼ同じ。

大きい写真にLRNをしてみて結果をみてみる

画像はこれです。

コード

結果

これをCNNに入れ込むと効果が高まるって気づいた人すごいっす。

TensorFlow – tf.nn.max_pool(value, ksize, strides, padding, data_format=’NHWC’, name=None)

tf.nn.max_pool(value, ksize, strides, padding, data_format=’NHWC’, name=None)

  • value: A 4-D Tensor with shape [batch, height, width, channels] and
    type tf.float32.
  • ksize: A list of ints that has length >= 4. The size of the window for
    each dimension of the input tensor.
  • strides: A list of ints that has length >= 4. The stride of the sliding
    window for each dimension of the input tensor.
  • padding: A string, either 'VALID' or 'SAME'. The padding algorithm.
    See the comment here
  • data_format: A string. ‘NHWC’ and ‘NCHW’ are supported.
  • name: Optional name for the operation.

max poolingは、特定の領域から最大の数字を取り出します。ksizeが領域の大きさ、stridesが間隔です。ksizeが[1, 2, 2, 1]で、stridesが[1, 2, 2, 1]であれば、2 x 2のmax poolingです。2 x 2の領域から最大値をとります。間隔が2の場合出力サイズは半分になります。ksizeが[1, 3, 3, 1]で、stridesが[1, 2, 2, 1]の場合、3 x 3の領域から最大値をとり、間隔が2になるので、出力サイズは半分のままですが、数値を広範囲から取得することになるので、よりちょっとの差に動じなくなります。

tf.nn.max_poolの動きを確認してみます。
この画像を使います。

画像を読み込む

とりあえず画像を読み込んで、shapeを表示させてみます。

結果

Max poolingしてみる

結果

おーできてるっぽい。

画像を表示してみる。

結果

では、わかり易くksizeを[1, 10, 10, 1]でやってみます。

結果

TensorFlow – weight decay

機械学習のweight decayは、重みの2乗ノルム(L2ノルム)を損失関数に加えること。これによって重みが大きいと損失関数の値が大きくなるので、重みが大きくなりすぎないようになる。過学習は重みが大きくなることで発生することが多いからこういうことする。L2ノルムは、各次元の値の2乗の和。

サンプル(TensorFlow使ってない)

サンプル(TensorFlow)

上記の、_variable_on_cpu()は、下記。

そして、_variable_with_weight_decay()は下記のように使われる。

(tf.nn.l2_loss()

多分2乗した合計かその半分が出てくるんじゃないかと思うので、確かめてみる。

結果

2乗和の半分っぽい。

TensorFlowのサンプルのように、wdを0.0にしてるってことは、weight decayに含めないってことか。畳み込み層は含まず、全結合層だけwdを0.004にしてる。

TensorFlowで損失関数にL2ノルムのweight decayを足してるところ

バッチ処理してるので、cross_entropyの平均をとって、weigt decayが入った’losses’というコレクションに入れて、コレクション内の数字を全部足している。

TensorFlow – tf.add_to_collection

tf.add_to_collection(name, value)は、tf.Graph.add_to_collection(name, value)のラッパーだそうです。

引数

  • name: The key for the collection. The GraphKeys class
    contains many standard names for collections.
  • value: The value to add to the collection.

すごい単純だな。

結果

TensorFlow – tf.train.MonitoredTrainingSession

tf.train.MonitoredTrainingSessionを確認します。
訓練をモニターするのに特化したセッションという感じでしょうか?チュートリアルのコードでは下記のような使われ方をしています。

普通のセッションを使う代わりに使っています。

https://www.tensorflow.org/api_docs/python/train/distributed_execution#MonitoredTrainingSession

tf.train.MonitoredTrainingSession(master=”, is_chief=True, checkpoint_dir=None, scaffold=None, hooks=None, chief_only_hooks=None, save_checkpoint_secs=600, save_summaries_steps=100, config=None)

引数的に、勝手に変数の保存をしてくれたりするようです。

For a chief, this utility sets proper session initializer/restorer. It also creates hooks related to checkpoint and summary saving. For workers, this utility sets proper session creator which waits for the chief to inialize/restore.

引数は下記です。

  • master: String the TensorFlow master to use.
  • is_chief: If True, it will take care of initialization and recovery the
    underlying TensorFlow session. If False, it will wait on a chief to
    initialize or recover the TensorFlow session.
  • checkpoint_dir: A string. Optional path to a directory where to restore
    variables.
  • scaffold: A Scaffold used for gathering or building supportive ops. If
    not specified, a default one is created. It’s used to finalize the graph.
  • hooks: Optional list of SessionRunHook objects.
  • chief_only_hooks: list of SessionRunHook objects. Activate these hooks if
    is_chief==True, ignore otherwise.
  • save_checkpoint_secs: The frequency, in seconds, that a checkpoint is saved
    using a default checkpoint saver. If save_checkpoint_secs is set to
    None, then the default checkpoint saver isn’t used.
  • save_summaries_steps: The frequency, in number of global steps, that the
    summaries are written to disk using a default summary saver. If
    save_summaries_steps is set to None, then the default summary saver
    isn’t used.
  • config: an instance of tf.ConfigProto proto used to configure the session.
    It’s the config argument of constructor of tf.Session.

hookというのはコールバック的な感じで、session.runの前後に実行できるクラスらしい。これを紐づけることができて、リストで複数のhookを登録することができる。カスタマイズできるのが、SessionRunHookで、それ以外に用途が決まっているhookが複数事前に提供されているといったような感じのイメージを持った気がする。

class tf.train.SessionRunHook
class tf.train.StopAtStepHook
class tf.train.NanTensorHook

configは設定で、log_device_placementは、手動でデバイス指定してる場合にTrueにすると、手動設定したデバイスを選んでくれる的なやつっぽい。自分の非力な1体のPC環境ではあまり関係がないと思う。非力な1体のPC環境の場合、hookは自作する_LoggerHook()だけで、configは未設定でもよさそう。

引数でcheckpoint_dirを聞いてるくらいだから、tf.summary.FileWriter(‘.\logs’, sess.graph)みたいのを書かなくてもログが保存されるようになったりしてるのか試してみようと思います。

結果

そして、logsディレクトリにログファイルが登録されていて、TensorBoardでも見られた。
before_runのreturnで、tf.train.SessionRunArgs(add)とやると、after_runのrun_values.resultsに結果が入ってくる。

TensorFlow – Readerクラスでバッチ処理

参考:
TensorFlow : How To : データを読む
Inputs and Readers
TensorFlowチュートリアル – 畳み込みニューラルネットワーク(翻訳)

tf.train.shuffle_batchというのを使う。シャッフルが不要な時は、tf.train.batchを使う。

tf.train.shuffle_batch

tf.train.shuffle_batch(tensors, batch_size, capacity, min_after_dequeue, num_threads=1, seed=None, enqueue_many=False, shapes=None, allow_smaller_final_batch=False, shared_name=None, name=None)

# min_after_dequeue はバッファ、そこからランダムにサンプリングします、がどのくらい大きいかを定義します
# – 大きければより良いシャッフリング、しかし開始が遅くなりメモリがより多く使用されることを意味します。
# capacity は min_after_dequeue よりも大きくなければなりません。
# そして the amount larger は事前読み込みする最大値を決めます。
# 推奨:
# min_after_dequeue + (num_threads + 小さい安全マージン) * batch_size
min_after_dequeue = 10000
capacity = min_after_dequeue + 3 * batch_size
example_batch, label_batch = tf.train.shuffle_batch(
[example, label], batch_size=batch_size, capacity=capacity,
min_after_dequeue=min_after_dequeue)
return example_batch, label_batch

TensorFlowのcifar10のチュートリアルのコード(cifar10_input.py)に下記のように使われている。

上記コードのmin_after_dequeue(min_queue_examples)は、下記のように取得されている。

上記のNUM_EXAMPLES_PER_EPOCH_FOR_TRAINは下記のように設定されている。

バッチもシャッフルしてくれる。シャッフルするために、事前にデータを何個か読み込んでおいて、その中からシャッフルしてくれる。事前読み込み数がmin_after_dequeueで、これが多いと沢山の中からシャッフルできるからシャッフルされ具合がいい感じになる。でも読み込み数が多いからその分遅くなる。でも途中からは裏で読み込んでおいてくれるので遅くならない。ただメモリ使用量は増える。って感じすか??上記事例だとエポック当たり5万件のデータがある場合、それに0.4をかけた数をmin_after_dequeueにしている。そして推奨値の数式に従って、capacityは、min_after_dequeue + 3 * バッチサイズにしている。

バッチ処理してみる

コードサンプル
下記のfile0.csvとfile1.csvはここで使ったものと同じ内容。

結果

TensorFlow – 学習精度を上げるために画像加工して増やす

参考:Images
上記を見ると色々な加工関数があるんですね。デコードエンコードも色々便利そうなのがあるんですね。

TensorFlowのチュートリアル「Convolutional Neural Networks」で、cifar10の画像を学習精度を上げるために画像を色々加工して増やしています。何をしていて、どうやればいいのか確認します。ソースコードはここです。

Readクラスでファイルから画像・ラベルデータを取り出した後に下記をしています。この5つの関数達を試していこうと思います。

すでに同じことをやってるサイトを発見しました。
参考:画像の水増し方法をTensorFlowのコードから学ぶ

なんか、上記5つの関数のうち最後の1つが参考サイトだと違う。参考サイトだとホワイトニングしている。参考サイトのおかげで答えが分かりましたが、一応自分でも試しておきます。

tf.random_crop

コード

結果

seek設定してないけどランダムになった。seekを明示した場合、seekが同じだと同じ結果になるってことらしい。

tf.image.per_image_standardization

これが参考サイトにない。その代りホワイトニングがなくなっている。
コード

結果は恐ろしい色合いになった。

こんな恐ろしい画像から学習しているんだなー。

チュートリアルの5関数を適用してみる

コード

結果

TensorFlow – 画像ファイルをReaderクラスで読み込む

参考:TensorFlowのReaderクラスを使ってみる

こんな風にしたら単一ファイルを読み込めるらしい。

コード

結果

TensorFlow – Readerクラスでデータを読み込んでみる

参考:TensorFlow : How To : データを読む

ファイルからデータを読む場合の処理の流れ

・ファイルパスのリストを、tf.train.string_input_producer 関数に渡す(shuffle=Trueにしたらepochでファイル名をシャッフルする)
・読み込むデータに合わせてreaderを選択する。
・ファイル名キューをreaderのreadメソッドに渡す。readメソッドは、ファイルとレコード識別キーと、スカラ文字列値を返す。
・スカラ文字列を、サンプルを構成するテンソルに変換するためにデコーダと変換 OPs の一つ(あるいはそれ以上)を利用します。

csvファイルを読んでみる

上記コードの動きを色々なパターンでチェックしてみまっす

ちなみに、csvファイルは、下記です。

file0.csv

file1.csv

下記を試してみます。コードは基本上記と同じですが、exmapleとlabelを3回表示させています。結果は、file0.csvとfile1.csvのどちらかの1~3行目を順番に取得していることが分かりました。ファイルから取り出すのは1行目から順番ですが、取り出すファイル自体は勝手にランダムな感じにしてくれてるっぽいです。

結果(下記のときと、file1.csvの内容の時がランダムに変わる)

epochでファイルが変わるか確認する

tf.train.string_input_producerの引数にshuffle=Trueを渡すと、epochでファイル名をシャッフルすると書いてありますので、それも試してみようと思います。まずは、shuffle=Trueを設定しない場合。

結果
下記のようになりました。file0と1だけだと、シャッフルしてるか分かりづらいですね。。とりあえずこれはシャッフルはしてないです。何回かやってみます。

同じコードをもう一度実行したら下記になりました。これぞまさにシャッフルですね。ということは、デフォルトでshuffle=Trueが設定されているようです。

それでは、shuffle=False設定にしてみます。
コードで変更するのは下記だけです。

結果
何回やっても下記になりました。shffle=Falseにすると、最初に取得するファイルも、tf.train.string_input_producerに渡した順番通りで固定のようです。

何をしたらエラーになるか確認してみる

上記のコードで、csvファイルの1行当たりのカラム数が5より大きい場合はエラーになりました。

カラム数が5より小さくてもエラーになりました。

record_defaultsを下記のように変えたらエラーになりました。record_defaultsの形状をみて、想定しているデータの個数やデータ型を想定しているようです。それと合わないデータが入ってたらエラーを出すようです。

エラー

record_defaultsを下記のように変えたらエラーになりました。shapeのランクが1じゃないといけないらしいです。shapeが(5, 0)になるからダメで、(5, 1)になるようにしないといけないってことかな?

エラー

record_defaultsを下記にしたら、エラーになりました。numpyの配列だとダメっぽいです。

エラー

これだとOKでした。

固定長バイナリデータファイルを読み込む

各レコードがバイトの固定数である、バイナリ・ファイルを読むためには、 tf.decode_raw 演算とともに tf.FixedLengthRecordReader を使用します。decode_raw 演算は文字列から uint8 テンソルに変換します。

例えば、CIFAR-10 データセット は、各レコードがバイトの固定長を使用して表される、ファイルフォーマットを使用します: ラベルのための1 バイト、続いて 3072 バイトの画像データです。ひとたび uint8 テンソルを持てば、標準操作で各部分をスライスし必要に応じて再フォーマットすることができます。CIFAR-10 については、どのように読みデコードするかを tensorflow/models/image/cifar10/cifar10_input.py で見ることができ、このチュートリアル で説明されています。

コードサンプル

結果

これで、cifar10のデータを読み込めました。

TensorFlow – tf.strided_slice関数を調べる

tf.strided_sliceを調べます。TensorFlowのGithubにのってる説明ページはこれです。

tf.strided_slice(input_, begin, end, strides=None, begin_mask=0, end_mask=0, ellipsis_mask=0, new_axis_mask=0, shrink_axis_mask=0, var=None, name=None) {#strided_slice}

実験してみる

コード

結果

とりあえず、inputは元データ、beginは開始位置、endは終了位置、stridesは間隔のようです。以前はstridesを指定しない場合、デフォルトでstridesを1とみなしていたようですが、最近のtensorflowのアップデートで、stridesも明示しないとエラーになるようになったようです。ちなみに、終了位置は普通の配列のスライスと同じで、指定したインデックスのひとつ前までになります。

これだけなら簡単なのですが、他にも色々引数があるし、inputもbeginも多次元配列に対応しているようです。

コード

結果

begin、end、stridesはnumpyのshapeのような感じで入れていくようです。コードだとbeginは[0, 5]ですが、これは1次元目は0から始まり、2次元目は5から始まるということになるようです。

Python3 – cifar10をダウンロードして画像を表示させてみる

参考:
Convolutional Neural Networks
https://github.com/tensorflow/models/tree/master/tutorials/image/cifar10/

上記githubに実際のコードがあります。
Cifar10というのは、10種類のカラー画像が沢山入ってるやつで、機械学習によく使われるようです。場所は下記です。
http://www.cs.toronto.edu/~kriz/cifar.html
Cifar100というのもあるようです。

データは、pythonバージョンとかバイナリバージョンとかありますが、TensorFlowが使ってるのは、バイナリバージョンです。バイナリバージョンの説明として、上記本家サイトに下記のように書いてあります。

The binary version contains the files data_batch_1.bin, data_batch_2.bin, …, data_batch_5.bin, as well as test_batch.bin. Each of these files is formatted as follows:
<1 x label><3072 x pixel>

<1 x label><3072 x pixel>
In other words, the first byte is the label of the first image, which is a number in the range 0-9. The next 3072 bytes are the values of the pixels of the image. The first 1024 bytes are the red channel values, the next 1024 the green, and the final 1024 the blue. The values are stored in row-major order, so the first 32 bytes are the red channel values of the first row of the image.

Each file contains 10000 such 3073-byte “rows” of images, although there is nothing delimiting the rows. Therefore each file should be exactly 30730000 bytes long.

There is another file, called batches.meta.txt. This is an ASCII file that maps numeric labels in the range 0-9 to meaningful class names. It is merely a list of the 10 class names, one per row. The class name on row i corresponds to numeric label i.

ダウンロード&展開

ここにダウンロードのコードをいくつか書きました。ここではTensorFlowのコードサンプルと同じくurllib.request.urlretrieveを使います。

展開された内容

最初の1バッチがラベル、その次からは赤、緑、青の順で1024バイトずつ入ってる。画像の大きさは32 x 32(=1024)。画像は各ファイルに1万枚分ある。
(1024 x 3 + 1) x 10000 = 30,730,000(bytes)

試しに画像を表示させてみる

試しにdata_batch_1.binから1枚分取得して表示させてみます。下記のreshapeとかtransposeで頭がこんがらがったのでここテストして頭を整理しました。

10枚取得してラベルもあわせて表示してみる

コードサンプル

結果

Numpy – 配列の形を変えるテスト

TensorFlowでは頻繁に配列の形を変換しますが、結構混乱して理解するのに時間がかかります。Numpy.reshapeとかNumpy.transposeの動きをシンプルな配列で確認してみます。

これから操作する配列は、下記のようなCifar10の画像データの構造をイメージしてます。
最初の4個は赤。次の4個は緑、最後の4個は青。
最初は上記が一次元の配列に格納されている状態にして、最終的にはこれを下記の並びにしたいです。縦、横、チャンネルの3次元配列にしたいということです。

4個ずつ赤・緑・青があるとういことは、2×2の画像サイズになります。よって、配列のshapeは、[2, 2, 3]になることを想定しております。このshapeになりつつ、配列データの構造に不整合がなければ形の変換が成功したことになります。

コード

結果

TensorFlow – tf.gfile.Exists

tf.gfile.Existsを調べます。

コード例

サンプルコード

hogeディレクトリがない状態で実行すると、hogeディレクトリが作られる。
空のhogeディレクトリがある状態で実行すると、hogeディレクトリが削除されて、また作られる。
適当なファイルやディレクトリを入れたhogeディレクトリがある状態で実行すると、hogeディレクトリが削除されて、また作られる。

なんかただディレクトリあったら消してつくってるだけで、何か特別なファイルがあったらそのファイルだけ消すとかそういう特別なことしてるわけじゃないっぽい。なんでわざわざこれ使うのかな?

TensorFlow – tf.app.flags.FLAGS(ファイル実行時にパラメタを付与できるようにする)

tf.app.flags.FLAGSを使うと、TensorFlowのPythonファイルを実行する際にパラメタを付与できるようになる。

下記のようにすると、パラメタ付与が可能になり、デフォルト値やヘルプ画面の説明文を登録できる。tf.app.flags.DEFINE_stringは、String型用で、他にtf.app.flags.DEFINE_boolean、tf.app.flags.DEFINE_integer等を型に合わせて使う。

コードサンプル (test.py)

ヘルプを表示する

結果

実行例

結果