旅行好きなソフトエンジニアの備忘録

プログラミングや技術関連のメモを始めました

【Python】 KerasでLeNet5っぽいネットワークを作成する

ディープラーニングの原点と言われるLeNet5(http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf)をKerasで実装し、MNISTの手書き文字を学習させました。LeNet5は以下の構造になっています。
f:id:ni4muraano:20170205221322j:plain
“LeNet5っぽい"とタイトルに書いたのは、完全に論文通りには実装できていないためです(活性化関数のカスタマイズ等が必要ですが、現時点でKerasをそこまで使いこなせていません)。この例ではここ(【Python】 MNIST手書き文字データを扱う - 旅行好きなソフトエンジニアの備忘録)で作成したmnist_handlingというモジュールを利用しています。

import mnist_handling

import numpy as np

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten
from keras.layers import Convolution2D, MaxPooling2D
from keras.optimizers import SGD
from keras.utils import np_utils

if __name__ == '__main__':
    # Load training data
    training_image_file = 'train-images-idx3-ubyte.gz'
    training_label_file = 'train-labels-idx1-ubyte.gz'
    training_images, training_labels = mnist_handling.load_image_and_label(training_image_file, training_label_file)

    # Load test data    
    test_image_file = 't10k-images-idx3-ubyte.gz'
    test_label_file = 't10k-labels-idx1-ubyte.gz'
    test_images, test_labels = mnist_handling.load_image_and_label(test_image_file, test_label_file)

    # Normalize data
    training_images = training_images.astype('float32')
    test_images = test_images.astype('float32')
    training_images /= 255
    test_images /= 255

    # Convert class vectors to binary class matrices
    class_count = 10
    training_labels = np_utils.to_categorical(training_labels, class_count)
    test_labels = np_utils.to_categorical(test_labels, class_count)

    # Build model
    model = Sequential()

    # Layer1
    kernel_count_layer1 = 6
    kernel_row_layer1 = 5
    kernel_col_layer1 = 5
    input_shape = (training_images.shape[1], training_images.shape[2], training_images.shape[3])
    model.add(Convolution2D(kernel_count_layer1, kernel_row_layer1, kernel_col_layer1, border_mode='same',
                            input_shape=input_shape, activation='tanh'))

    # Layer2
    pool_size_layer2 = (2, 2)
    model.add(MaxPooling2D(pool_size=pool_size_layer2))
    model.add(Dropout(6/16))

    # Layer3
    kernel_count_layer3 = 16
    kernel_row_layer3 = 5
    kernel_col_layer3 = 5
    model.add(Convolution2D(kernel_count_layer3, kernel_row_layer3, kernel_col_layer3, border_mode='valid',
                            activation='tanh'))

    # Layer4
    pool_size_layer4 = (2, 2)
    model.add(MaxPooling2D(pool_size=pool_size_layer4))

    # Layer5
    kernel_count_layer5 = 120
    kernel_row_layer5 = 5
    kernel_col_layer5 = 5
    model.add(Convolution2D(kernel_count_layer5, kernel_row_layer5, kernel_col_layer5, border_mode='valid',
                            activation='tanh'))

    # Layer6
    output_count_layer6 = 84
    model.add(Flatten())
    model.add(Dense(output_count_layer6, activation='tanh'))

    # Output layer    
    model.add(Dense(class_count, activation='softmax'))

    model.compile(loss='categorical_crossentropy',
                  optimizer=SGD(),
                  metrics=['accuracy'])

    # Start training
    epoch_count = 20
    batch_size = 128
    model.fit(training_images, training_labels, batch_size=batch_size, nb_epoch=epoch_count,
              verbose=1, validation_data=(test_images, test_labels))
    
    # Save model
    model_file_name = 'lenet.json'
    model_json = model.to_json()
    with open(model_file_name, 'w') as file:
        file.write(model_json)
    # Save weights
    weight_file_name = 'lenet_weights.hdf5'
    model.save_weights(weight_file_name)

論文と比較してトレーニングセットに対する正解率は2.5%、テストセットに対する正解率は1%程度低くなりました。論文では二層目、三層目が全結合になっていないため、代わりにドロップアウトを使用したのですが、これによりトレーニングセットに対する正解率がかなり低くなっているのかもしれません。(2017/02/09追記:最適化関数をAdamに変更することで論文と同程度の正解率となりました)