学習してないランダムなCNNでも特徴抽出の役に立つ

先月からディープラーニングを教えてくれる講座を受講しています。
今週からCNNに入ったのですが、先生が「CNNは特徴抽出器としてかなり優秀で、学習していないランダムなCNNでも高い精度が出せる」と教えてくれました。

「え、そうなの!?」とびっくりしましたので、MNISTを使って簡単に実験してみました。

まずはデータセットをロードして整形します。

import numpy as np

from keras.models import Sequential
from keras.layers import Conv2D, MaxPool2D, Dense, Flatten
from keras.losses import categorical_crossentropy
from keras.optimizers import Adam
from keras.metrics import categorical_accuracy
from keras.utils import to_categorical
from keras.datasets import mnist

# データセットのロード
(X_train, y_train), (X_test, y_test) = mnist.load_data()

train_samples, input_w, input_h = X_train.shape
test_samples = X_test.shape[0]
categories = len(np.unique(y_test))

# チャネル数 (1) を加える
X_train = X_train.reshape(train_samples, input_w, input_h, 1)
X_test = X_test.reshape(test_samples, input_w, input_h, 1)

# 0〜1に正規化
X_train = X_train.astype('float32') / 255
X_test = X_test.astype('float32') / 255

# 10次元のone-hotベクトル化
y_train = to_categorical(y_train, categories)
y_test = to_categorical(y_test, categories)

# 学習パラメータ
batch_size = 32
epochs = 10

比較対象となる全結合層でのみで構成したNNです。入力層 (784次元) -> 出力層 (10次元のソフトマックス) のみのシンプル過ぎなネットワークです。

  • パラメータ数は7850 (784×10 + 10) で、CNNを挟んでいない分、1層でもかなり多数になります
  • 精度は92.6%
model_nn = Sequential()

model_nn.add(Flatten(input_shape=(input_w, input_h, 1)))
model_nn.add(Dense(10, activation='softmax'))

model_nn.compile(
    loss=categorical_crossentropy, optimizer=Adam(), metrics=[categorical_accuracy]
)
print(model_nn.summary())
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# flatten_18 (Flatten)         (None, 784)               0         
# _________________________________________________________________
# dense_49 (Dense)             (None, 10)                7850      
# =================================================================
# Total params: 7,850
# Trainable params: 7,850
# Non-trainable params: 0
# _________________________________________________________________

model_nn.fit(
    X_train, y_train, batch_size=batch_size, epochs=epochs, 
    validation_data=(X_test, y_test), verbose=1
)
# Train on 60000 samples, validate on 10000 samples
# Epoch 1/10
# 60000/60000 [==============================] - 6s 101us/step - loss: 0.4653 - categorical_accuracy: 0.8794 - val_loss: 0.3068 - val_categorical_accuracy: 0.9128
# ...
# Epoch 10/10
# 60000/60000 [==============================] - 6s 93us/step - loss: 0.2507 - categorical_accuracy: 0.9306 - val_loss: 0.2644 - val_categorical_accuracy: 0.9262

次に、特徴抽出器としてランダムなCNNを全結合層の前段に加えたネットワークを構成します。

  • CNNは畳み込み+プーリングの2セットの計4層構成となっており、7x7x8で出力されます
    • cnn_layers.trainable=Falseとすることで、学習させないようにしてます
  • 28x28x1から7x7x8に削減されたことで、パラメータ数も約半分となってます
  • 精度は94.2%
# ランダムなCNN
cnn_layers = Sequential([
    Conv2D(4, (2, 2), padding='same', activation='relu', input_shape=(input_w, input_h, 1)),
    MaxPool2D((2, 2)),
    Conv2D(8, (2, 2), padding='same', activation='relu'),
    MaxPool2D((2, 2))
])

# 学習をさせない
cnn_layers.trainable = False

# 後ろに全結合層をつなげる
model_cnn = Sequential()

model_cnn.add(cnn_layers)
model_cnn.add(Flatten())
model_cnn.add(Dense(10, activation='softmax'))

model_cnn.compile(
    loss=categorical_crossentropy, optimizer=Adam(), metrics=[categorical_accuracy]
)

print(model_cnn.summary())
# _________________________________________________________________
# Layer (type)                 Output Shape              Param #   
# =================================================================
# sequential_34 (Sequential)   (None, 7, 7, 8)           156       
# _________________________________________________________________
# flatten_23 (Flatten)         (None, 392)               0         
# _________________________________________________________________
# dense_54 (Dense)             (None, 10)                3930      
# =================================================================
# Total params: 4,086
# Trainable params: 3,930
# Non-trainable params: 156
# _________________________________________________________________

model_cnn.fit(
    X_train, y_train, batch_size=batch_size, epochs=epochs, 
    validation_data=(X_test, y_test), verbose=1
)
# Train on 60000 samples, validate on 10000 samples
# Epoch 1/10
# 60000/60000 [==============================] - 7s 116us/step - loss: 0.9706 - categorical_accuracy: 0.8011 - val_loss: 0.5502 - val_categorical_accuracy: 0.8767
# ...
# Epoch 10/10
# 60000/60000 [==============================] - 6s 105us/step - loss: 0.2198 - categorical_accuracy: 0.9390 - val_loss: 0.2128 - val_categorical_accuracy: 0.9421

ということで、全てのピクセルを全結合層でつなぐよりも、ランダムなCNNで特徴抽出したほうが、パラメータ数が減り (7850 -> 3930) 、精度も向上する (92.6 -> 94.2) という結果が得られました。

ちなみに、Conv2Dを除いてMaxPool2Dのみ (つまり縮約のみ) にしたバージョンでも試してみましたが、精度は83.1%にとどまりました。このことからも、畳み込み計算が特徴抽出器として優秀であることがわかります。