Lenetを試してみたよ【Keras】

畳み込みニューラルネットワークの元祖として「Lenet」があります。1995年に発表された論文ですね。Kerasで実際に動かしてみた例がありました。

ソースコード

import os
import keras
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dense
from keras.datasets import mnist
from keras.optimizers import Adam
from keras.callbacks import TensorBoard
from pathlib import Path

__file__ = "C:\\Users\\admin\\log"
print(__file__)

def lenet(input_shape, num_classes):
    model = Sequential()

    # extract image features by convolution and max pooling layers
    model.add(Conv2D(
        20, kernel_size=5, padding="same",
        input_shape=input_shape, activation="relu"
        ))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Conv2D(50, kernel_size=5, padding="same", activation="relu"))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    # classify the class by fully-connected layers
    model.add(Flatten())
    model.add(Dense(500, activation="relu"))
    model.add(Dense(num_classes))
    model.add(Activation("softmax"))
    return model

class MNISTDataset():

    def __init__(self):
        self.image_shape = (28, 28, 1)  # image is 28x28x1 (grayscale)
        self.num_classes = 10

    def get_batch(self):
        (x_train, y_train), (x_test, y_test) = mnist.load_data()

        x_train, x_test = [self.preprocess(d) for d in [x_train, x_test]]
        y_train, y_test = [self.preprocess(d, label_data=True) for d in
                           [y_train, y_test]]

        return x_train, y_train, x_test, y_test

    def preprocess(self, data, label_data=False):
        if label_data:
            # convert class vectors to binary class matrices
            data = keras.utils.to_categorical(data, self.num_classes)
        else:
            data = data.astype("float32")
            data /= 255  # convert the value to 0~1 scale
            shape = (data.shape[0],) + self.image_shape  # add dataset length
            data = data.reshape(shape)

        return data

class Trainer():

    def __init__(self, model, loss, optimizer):
        self._target = model
        self._target.compile(
            loss=loss, optimizer=optimizer, metrics=["accuracy"]
            )
        self.verbose = 1
        logdir = "logdir_lenet"
        self.log_dir = os.path.join(os.path.dirname(__file__), logdir)

    def train(self, x_train, y_train, batch_size, epochs, validation_split):
        if os.path.exists(self.log_dir):
            import shutil
            shutil.rmtree(self.log_dir)  # remove previous execution
        os.mkdir(self.log_dir)

        self._target.fit(
            x_train, y_train,
            batch_size=batch_size, epochs=epochs,
            validation_split=validation_split,
            callbacks=[TensorBoard(log_dir=self.log_dir)],
            verbose=self.verbose
        )

dataset = MNISTDataset()

# make model
model = lenet(dataset.image_shape, dataset.num_classes)

# train the model
x_train, y_train, x_test, y_test = dataset.get_batch()
trainer = Trainer(model, loss="categorical_crossentropy", optimizer=Adam())
trainer.train(
    x_train, y_train, batch_size=128, epochs=12, validation_split=0.2
    )

# show result
score = model.evaluate(x_test, y_test, verbose=0)
print("Test loss:", score[0])
print("Test accuracy:", score[1])

まとめ

__file__ = “C:\\Users\\admin\\log" という箇所がありますが、ここは適宜変更してください。割と重要みたいで、これが無いと正常に動作しませんでした。Linux環境だとうまくいくのかもしれません。

Train on 48000 samples, validate on 12000 samples
Epoch 1/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.1838 - acc: 0.9443 - val_loss: 0.0583 - val_acc: 0.9827
Epoch 2/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0493 - acc: 0.9848 - val_loss: 0.0472 - val_acc: 0.9839
Epoch 3/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0327 - acc: 0.9895 - val_loss: 0.0482 - val_acc: 0.9846
Epoch 4/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0236 - acc: 0.9925 - val_loss: 0.0357 - val_acc: 0.9889
Epoch 5/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0187 - acc: 0.9939 - val_loss: 0.0415 - val_acc: 0.9866
Epoch 6/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0143 - acc: 0.9955 - val_loss: 0.0365 - val_acc: 0.9901
Epoch 7/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0119 - acc: 0.9961 - val_loss: 0.0410 - val_acc: 0.9882
Epoch 8/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0088 - acc: 0.9970 - val_loss: 0.0301 - val_acc: 0.9913
Epoch 9/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0066 - acc: 0.9978 - val_loss: 0.0452 - val_acc: 0.9898
Epoch 10/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0084 - acc: 0.9969 - val_loss: 0.0452 - val_acc: 0.9899
Epoch 11/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0067 - acc: 0.9977 - val_loss: 0.0353 - val_acc: 0.9912
Epoch 12/12
48000/48000 [==============================] - 74s 2ms/step - loss: 0.0052 - acc: 0.9984 - val_loss: 0.0465 - val_acc: 0.9895
Test loss: 0.04366540142124295
Test accuracy: 0.9888

実行結果はこんな感じです。

脅威の「98.88%」の正答率を誇っていますね!

学習時間が大変なことになっています。なんと1ステップに74秒も掛かっています。2018年のコンピュータでコレなのですから、1995年の技術水準でLenetが発表されたのは奇跡的ですね。その時点で現在に近い水準のコンピュータリソースにアクセスできた人が居たというのはすごいことです。現在でも20年後のコンピュータ環境に近いものがあるのでしょうが、人工知能の研究には資金力も必要になりそうですね。

システム開発

Posted by @erestage