前回の直後に書き、下書きのまま放置されていた記事です。
前回は、TensorFlow版「Hello World」の解説と自筆文字の判定を行いました。
今回は、畳み込みニューラルネットワーク(CNN: convolutional neural network)の理解と実装をし、自筆文字の判定精度を上げます。
参考:定番のConvolutional Neural Networkをゼロから理解する - DeepAge
理論
1. 視覚の生理学
この項は、着想の話をします。難しかったら次の項へ進んでも構いません。そもそもニューラルネットワークは神経のモデル化を試みたものでした。CNNも視覚情報処理をヒントとしています。脳の一次視覚野には、単純型細胞・複雑型細胞・超複雑型細胞という細胞が見つかっています。
- 網膜:境界を抽出する。オン領域とオフ領域の組み合わせで判断している。
- 単純型細胞:特定位置・特定傾きの線分を認識する。オン領域とオフ領域の組み合わせで判断している。
- 複雑型細胞:位置がズレても特定傾きの線分を認識する。複数の単純型細胞から入力をもらうと実現可能。(実物は未解明)
- 超複雑型細胞:さらに複雑な形に対応。複数の複雑型細胞から入力をもらうと実現可能。(実物は未解明)
参考1:第一次視覚野:線の傾きを検出する細胞 - 脳の世界
参考2:花沢 明俊, 視覚心理 (第2回), 映像情報メディア学会誌, 2004, 58巻, 2号, p.199-204. - J-STAGE
参考3:Deep learningで画像認識②〜視覚野と畳み込みニューラルネットワーク〜 - IMACEL Academy
2. 畳み込み と プーリング
網膜と単純型細胞の機能は畳み込みによって、複雑型細胞の機能はプーリングによって実現できます。
畳み込み Convolution
畳み込みとは、ある範囲に対し特徴抽出を行う操作です。カーネル(フィルターともいう)との内積を一通り、スライドしながら計算します。このカーネルが視覚の受容野に相当します。
このままだと端の特徴を捉えられないので、端にデータを増やしてあげます。これをパディングと言います。MNISTでは端が0なので、ゼロパディングだとちょうどよさそうですね。
パディングを行ったので、出力の形は小さくなりません。
プーリング Pooling
プーリングとは、範囲内での代表値を計算する作業。
ちょっとぐらい位置がズレても対応できるようになります。本番は、このあとに活性化関数で次の層への出力を決めます。
実践
import tensorflow as tf
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# (60000枚,縦28,横28)->(60000枚,縦28,横28,1色)
x_train = x_train.reshape((60000,28,28,1))
# (10000枚,縦28,横28)->(10000枚,縦28,横28,1色)
x_test = x_test.reshape((10000,28,28,1))
x_train, x_test = x_train/255.0, x_test/255.0
MNISTのデータは(枚,縦,横)という形のテンソル1ですが、次に出てくるConv2D層に入れる前に(枚,縦,横,色)という形に変えなければなりません。reshape(枚,縦,横,色)
をする必要があります2。
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(32, (3, 3),padding='same', activation='relu',
input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.MaxPooling2D((2, 2)),
tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(64, activation='relu'),
tf.keras.layers.Dropout(0.25),
tf.keras.layers.Dense(10, activation='softmax')
])
モデルの層をそれぞれ作ってます。
Conv2D(32, (3, 3), padding='same', activation='relu', input_shape=(28, 28, 1))
(縦28,横28,1色)を、ゼロパディングした上で畳み込みします。3×3(×1色)のカーネルを32種類使って、活性化関数はReLU。(縦28,横28,32種類)のテンソルになりました。MNISTは端が0の画像なので、ゼロパディングが妥当です。
MaxPooling2D((2, 2))
(縦28,横28,32種)を2×2で最大値プーリングします。(縦14,横14,32種)になりました。
Conv2D(64, (3, 3), activation='relu')
(縦14,横14,32種)を、パディングせず畳み込みします。3×3(×32)のカーネルを64種類使って、活性化関数はReLU。(縦12,横12,64種)になりました。
MaxPooling2D((2, 2))
(縦12,横12,64種)を2×2で最大値プーリングします。(縦6,横6,64種)になりました。
Conv2D(64, (3, 3), activation='relu')
(縦6,横6,64種) → (縦4,横4,64種)
Flatten()
(縦4,横4,64枚) → (1024,)
Dense(64, activation='relu')
(1024,) → (64,)
Dropout(0.25)
25%、つまり16個を0にする。
Dense(10, activation='softmax')
「0」〜「9」の10個の神経細胞に接続。
-
スカラーを横に並べたものをベクトル、ベクトルを縦に並べたものを行列、行列を奥に並べたものを3次元テンソルと呼びます。例えば、行列
[[a,b],[c,d]]
を2つ並べた3次元テンソルは[ [[a,b],[c,d]], [[a,b],[c,d]] ]
です。MNISTのデータは「画像の行列を並べたもの」であり、このような形式になっています。 ↩ -
(枚,縦,横,色)のデータセットは、(縦,横,色)の画像を並べた配列です。本文中のreshape操作では、
[ [a,b], [c,d] ]
→[ [[a],[b]], [[c],[d]] ]
のように各ピクセルの値a
を[a]
へ変えました。これは、RGB画像を使いたいときに、各ピクセルが[aR,aG,aB]
のようなデータを扱うための仕様です。 ↩