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])
画像にしてみる。
明度を反転。
これは数字のゼロにあたるものとして、ラベル付けをしている。
学習する際は、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割にあたる360個を検証用とし、残りの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 ]
ミニバッチサイズ分、これと同様の値が得られる。
学習においては、この値が小さくなるよう、モデルのパラメータをチューニングする。