tensorflowメモ(手書き文字認識その1)

ライブラリ「scikit-learn」の手書き文字データセット「digits」を使って、手書き文字認識を試してみる。

入力データ

データセットの構成は以下のとおり。

  • 8 x 8ピクセルの画像
  • 「0」から「9」までの手書き文字
  • 明暗を0から16までの値で表現
  • 画像数は1797

1番めの画像のデータ表現。
64個(=8x8)の値が1列に並んで格納されている。

array([ 0,  0,  5, 13,  9,  1,  0,  0,
        0,  0, 13, 15, 10, 15,  5,  0,
        0,  3, 15,  2,  0, 11,  8,  0,
        0,  4, 12,  0,  0,  8,  8,  0,
        0,  5,  8,  0,  0,  9,  8,  0,
        0,  4, 11,  0,  1, 12,  7,  0,
        0,  2, 14,  5, 10, 12,  0,  0,
        0,  0,  6, 13, 10,  0,  0,  0])

画像にしてみる。
f:id:ichou1:20181208085216p:plain

明度を反転。
f:id:ichou1:20181208085521p:plain

これは数字のゼロにあたるものとして、ラベル付けをしている。

学習する際は、0から1の範囲になるようスケーリングする(16で割る)。

array([0.    , 0.    , 0.3125, 0.8125, 0.5625, 0.0625, 0.    , 0.    ,
       0.    , 0.    , 0.8125, 0.9375, 0.625 , 0.9375, 0.3125, 0.    ,
       0.    , 0.1875, 0.9375, 0.125 , 0.    , 0.6875, 0.5   , 0.    ,
       0.    , 0.25  , 0.75  , 0.    , 0.    , 0.5   , 0.5   , 0.    ,
       0.    , 0.3125, 0.5   , 0.    , 0.    , 0.5625, 0.5   , 0.    ,
       0.    , 0.25  , 0.6875, 0.    , 0.0625, 0.75  , 0.4375, 0.    ,
       0.    , 0.125 , 0.875 , 0.3125, 0.625 , 0.75  , 0.    , 0.    ,
       0.    , 0.    , 0.375 , 0.8125, 0.625 , 0.    , 0.    , 0.    ])

全データ(1797個)のうち2割を検証用とし、残りの1437個をトレーニング用とする。

モデル

モデルを以下のとおり表現する。

trainY = X * W + b
  • trainY : 計算結果を格納する行列(minibatch-size x 10)
  • X : 入力データを格納する行列(minibatch-size x 64)
  • W : 重みを格納する行列(64 x 10)
  • b : バイアスを格納する1次元配列(要素数10)

tensorflowとしては以下のとおり定義する。

  • X : tf.placeholder(tf.float32, [None, 64])
  • W : tf.Variable(tf.zeros([64, 10]))
  • b : tf.zeros([10])

「W」と「b」については、オール0で初期化しているが、これでも最適解は求まる。

学習

損失関数として、計算結果と正解ラベルとのクロスエントロピーを計算する。

loss = - tf.reduce_sum(tf.log(tf.nn.softmax(trainY)) * "正解ラベル(one-hot表現)", axis=1)

この値を最小化する問題として、オプティマイザに渡す(ここでは「勾配降下法」を使う)

tf.train.GradientDescentOptimizer("learning-rate").minimize(loss)

ミニバッチサイズを「128」、学習率を「0.001」、エポックを「100」回に設定してトレーニングを実施。
各エポックにおける、テストデータに対する認識精度は以下のとおりであった。

学習 1回目完了のモデル :  accuracy = 80.6(%)
学習11回目完了のモデル :  accuracy = 88.6(%)
学習21回目完了のモデル :  accuracy = 90.8(%)
学習31回目完了のモデル :  accuracy = 91.7(%)
学習41回目完了のモデル :  accuracy = 93.1(%)
学習51回目完了のモデル :  accuracy = 93.6(%)
学習61回目完了のモデル :  accuracy = 93.9(%)
学習71回目完了のモデル :  accuracy = 94.7(%)
学習81回目完了のモデル :  accuracy = 95.0(%)
学習91回目完了のモデル :  accuracy = 95.0(%)

モデルの初期パラメータをオールゼロに設定したが、1回の学習で80%、最大95%の認識精度が出ている。
バイアスはオールゼロのままで、更新されることはなかった。


損失計算の過程を追ってみる。
11回目の学習が完了したモデルに、冒頭の画像データ("ゼロ"とラベリングされたもの)を渡すとする。

入力画像1つをモデルに通した後の結果
data_0 = np.dot(np.reshape(X[0], (1,64)), train_W) + train_b
[[ 2.6651433 -1.6024852 -0.6885104 -0.27110717 -0.09376842 0.19373618 -0.31471574 -0.5869334 0.07272757 0.62591416  ]]
softmax関数を通した結果
data_0_softmax = sess.run(tf.nn.softmax(data_0))
[[ 0.64753246 0.00907515 0.02263541 0.03436087 0.0410281 0.05469443 0.03289465 0.02505547 0.04846071 0.08426274 ]]
対数をとる
data_0_softmax_log = sess.run(tf.log(data_0_softmax)
[[ -0.43458635 -4.702215 -3.78824 -3.3708367 -3.193498 -2.9059935 -3.4144452 -3.6866632 -3.0270019 -2.4738154 ]]
入力画像に紐付けられる正解ラベル(one-hot表現)
Y[0]
[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
計算結果と正解ラベルとのクロスエントロピーを計算
data_0_softmax_log_xent = data_0_softmax_log * np.reshape(Y[0], (1,10))
[[ -0.43458635  -0.  -0.  -0.  -0.  -0.  -0.  -0.  -0.  -0. ]]
2次元目のレベルで足し合わせ
data_0_softmax_log_xent_sum = sess.run(tf.reduce_sum(data_0_softmax_log_xent, axis=1))
[ -0.43458635 ]

ここで尤度計算は、確率同士の掛け算を扱うことになるが、アンダーフローを引き起こすので、対数尤度に置き換えて最適化を考えている。

正解に近いほど値は大きくなり(=絶対値が小さくなる)、正解から乖離するほど値は小さくなる(絶対値が大きくなる)。

マイナスを掛けて最小化問題に置き換える
[ 0.43458635 ]

ミニバッチサイズ分、これと同様の値が得られる。

学習においては、この値が小さくなるよう、モデルのパラメータをチューニングする。