【Python】 KerasでLeNet5っぽいネットワークを作成する
ディープラーニングの原点と言われるLeNet5(http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf)をKerasで実装し、MNISTの手書き文字を学習させました。LeNet5は以下の構造になっています。 “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に変更することで論文と同程度の正解率となりました)