【深層学習入門】Kerasによる分類ニューラルネットワークの実装

機械学習

はじめに

この記事では簡単なニューラルネットワークを作成して、手書き数字画像(MNISTデータセット)を分類するモデルを作成してみます。深層学習はTensorFlowライブラリのKerasを使用することによって簡単に利用することができます。

この記事で使用されているソースコードは以下のGitHubや、Google Colabで試すことができます。

GitHub - Joichiro433/Blog-classification-nn
Contribute to Joichiro433/Blog-classification-nn development by creating an account on GitHub.
Google Colaboratory

\begin{align*}
\newcommand{\mat}[1]{\begin{pmatrix} #1 \end{pmatrix}}
\newcommand{\f}[2]{\frac{#1}{#2}}
\newcommand{\pd}[2]{\frac{\partial #1}{\partial #2}}
\newcommand{\d}[2]{\frac{{\rm d}#1}{{\rm d}#2}}
\newcommand{\e}{{\rm e}}
\newcommand{\T}{\mathsf{T}}
\newcommand{\(}{\left(}
\newcommand{\)}{\right)}
\newcommand{\{}{\left\{}
\newcommand{\}}{\right\}}
\newcommand{\[}{\left[}
\newcommand{\]}{\right]}
\newcommand{\dis}{\displaystyle}
\newcommand{\eq}[1]{{\rm Eq}(\ref{#1})}
\newcommand{\n}{\notag\\}
\newcommand{\t}{\ \ \ \ }
\newcommand{\tt}{\t\t\t\t}
\newcommand{\argmax}{\mathop{\rm arg\, max}\limits}
\newcommand{\argmin}{\mathop{\rm arg\, min}\limits}
\def\l<#1>{\left\langle #1 \right\rangle}
\def\us#1_#2{\underset{#2}{#1}}
\def\os#1^#2{\overset{#2}{#1}}
\newcommand{\case}[1]{\{ \begin{array}{ll} #1 \end{array} \right.}
\newcommand{\s}[1]{{\scriptstyle #1}}
\definecolor{myblack}{rgb}{0.27,0.27,0.27}
\definecolor{myred}{rgb}{0.78,0.24,0.18}
\definecolor{myblue}{rgb}{0.0,0.443,0.737}
\definecolor{myyellow}{rgb}{1.0,0.82,0.165}
\definecolor{mygreen}{rgb}{0.24,0.47,0.44}
\newcommand{\c}[2]{\textcolor{#1}{#2}}
\newcommand{\ub}[2]{\underbrace{#1}_{#2}}
\end{align*}

手書き数字のデータセット「MNIST」

今回用いるデータセット「MNIST」は、0 ~ 9 の手書き数字画像と正解ラベルを集めたデータセットです。訓練データ 60,000 件、テストデータ 10,000 件が含まれています。画像はグレースケールで、 サイズは 28 × 28 ピクセルになります。

TensorFlow には、このデータセットを読み込む機能が用意されています。

from tensorflow.keras.datasets import mnist

(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

print(train_images.shape)
print(train_labels.shape)
print(test_images.shape)
print(test_labels.shape)
## 出力
(60000, 28, 28)
(60000,)
(10000, 28, 28)
(10000,)
▲ MNISTデータセット。0 ~ 9 の手書き数字画像と正解ラベルを集めたデータセットである。画像はグレースケールで、 サイズは 28 × 28 ピクセル

Kerasによるニューラルネットワークの実装

この記事では以下のプロジェクト構成で実装を進めていきます。

.
├── requirements.txt
└── app/
    ├── logs/
    ├── model.py
    ├── params.py
    ├── predict.py
    ├── preprocessing.py
    └── train.py

app/ ディレクトリ配下にソースコードを配置します。それぞれの説明は下記の通りです。

  • logs/ : 機械学習モデルの学習過程の情報が出力されるディレクトリです
  • model.py : 機械学習モデルの定義を記載します
  • params.py : モデルのパラメータ等を記載します
  • predict.py : 学習済みモデルを用いた推論用コードを記載します
  • preprocessing.py : 前処理用の関数を記載します
  • train.py : モデルの学習用コードを記載します

また、requirements.txt の内容は次の通りです。

numpy==12.2.4
pandas==1.4.2
matplotlib==3.5.2
seaborn==0.11.2
scikit-learn==1.1.1
nptyping==2.1.1
tensorflow==2.9.1
# tensorflow-macos==2.9.2  # M1macの場合はこちらを使用する
pydot==1.4.2
graphviz==0.20
rich==12.5.1

今回作成するニューラルネットワークのアーキテクチャは下図のようなシンプルなものとします。

以上を踏まえて、それぞれのソースコードをみてみましょう。

params.py

params.py ではモデルのアーキテクチャを決めるパラメータ等を記載します。

from datetime import datetime
from pathlib import Path

LOG_DIR = Path('logs/fit') / datetime.now().strftime("%Y%m%d-%H%M%S")

BACHSIZE = 500
EPOCHS = 50
VALIDATION_SPLIT = 0.2

INPUT_SIZE = 784
HIDDEN1_SIZE = 256
HIDDEN2_SIZE = 128
OUTPUT_SIZE = 10

MODEL_FILE_PATH = 'model.h5'

model.py

model.py ではニューラルネットワークモデルの定義を記載します。ここでは Keras の Functional API を使用しています。

import tensorflow as tf
from keras.api._v2 import keras
from keras.models import Model
from keras.layers import Input, Dense, Dropout

import params


class DNNModel:
    def __init__(
            self,
            input_size: int = params.INPUT_SIZE,
            hidden1_size: int = params.HIDDEN1_SIZE,
            hidden2_size: int = params.HIDDEN2_SIZE,
            output_size: int = params.OUTPUT_SIZE,
            dropout_rate: float = 0.5) -> None:
        self.input: Input = Input(shape=(input_size,), name='input')
        self.hidden1: Dense = Dense(hidden1_size, activation='relu', name='hidden1')
        self.hidden2: Dense = Dense(hidden2_size, activation='relu', name='hidden2')
        self.dropout: Dropout = Dropout(rate=dropout_rate, name='dropout')
        self.output: Dense = Dense(output_size, activation='softmax', name='output')

    def build(self) -> Model:
        input = self.input
        x = self.hidden1(input)
        x = self.hidden2(x)
        x = self.dropout(x)
        output = self.output(x)
        return Model(inputs=input, outputs=output)
        

ここで用いている「Dropout」は、「過学習」を防いでモデルの精度を上げるための手法の 1 つです。

任意の層のユニットをランダムに無効にすることで、特定のニューロンの存在への依存を防ぎ、汎化性能の向上が期待できます。

preprocessing.py

preprocessing.py では画像の前処理用関数を記載します。

処理内容は、2次元の画像の shape: (28 × 28) を 1次元 (786) に変換し、ラベルに 「one-hot エンコーディング」を適用します。

from typing import Tuple, Optional, Union

import tensorflow as tf
from keras.api._v2 import keras
from keras.utils import to_categorical
from nptyping import NDArray, Shape, Int, Float


# 型情報
Images = NDArray[Shape['Sample, Width, Height'], Int]
Labels = NDArray[Shape['Sample'], Int]
PImages = NDArray[Shape['Sample, Width_x_Height'], Int]
PLabels = NDArray[Shape['Sample, Class'], Int]


def preprocess_dataset(
        images: Images, 
        labels: Optional[Labels] = None) -> Union[PImages, Tuple[PImages, PLabels]]:
    images: PImages = images.reshape((images.shape[0], -1))
    if labels is None:
        return images
    labels: PLabels = to_categorical(labels)
    return images, labels

ここでは型情報と numpy arrey の次元を明確にするために、nptyping ライブラリを使用しています。

train.py

train.py はモデルの学習を行います。

import tensorflow as tf
from keras.api._v2 import keras
from keras.models import Model
from keras.datasets import mnist
from keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard
from keras.utils import plot_model

import params
from preprocessing import preprocess_dataset
from model import DNNModel


def train():
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    train_images, train_labels = preprocess_dataset(images=train_images, labels=train_labels)

    model: Model = DNNModel().build()

    model.compile(
        optimizer='adam',
        loss='categorical_crossentropy',
        metrics=['acc']
    )
    model.summary()
    plot_model(model, to_file='model.pdf', show_shapes=True)

    callbacks = [
        EarlyStopping(patience=5),
        ModelCheckpoint(filepath=params.MODEL_FILE_PATH, save_best_only=True),
        TensorBoard(log_dir=params.LOG_DIR)
    ]

    model.fit(
        x=train_images,
        y=train_labels,
        batch_size=params.BACHSIZE,
        epochs=params.EPOCHS,
        validation_split=params.VALIDATION_SPLIT,
        callbacks=callbacks)

    
if __name__ == '__main__':
    train()

ここでは、Keras に用意されたコールバックを利用することで、学習中のバッチやエポックの開始 / 終了時にメゾットを呼び出しています。使用しているコールバックは以下の通りです。

  • EarlyStopping : 過学習を避けるためのコールバックで、一定のエポック数、学習指標に改善が見られなくなった時に自動的に学習を停止します。
  • ModelCheckpoint : エポック終了時にモデルを保存するコールバックです。学習中にモデル保存が自動的にされ、もし学習が中断された場合、中断された時点から学習を再開することができます。
  • TensorBoard : 学習指標やモデルの構造を可視化できるツールです。引数 log_dir に指定したディレクトリに学習過程のさまざまな指標が自動的に格納されます。

predict.py

predict.py は、train.py で学習したモデルを活用して推論を行います。

import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
from keras.api._v2 import keras
from keras.datasets import mnist
from rich import print

import params
from preprocessing import preprocess_dataset


def predict(images):
    model = keras.models.load_model(params.MODEL_FILE_PATH)
    pred = model.predict(images)
    return np.argmax(pred, axis=1)


if __name__ == '__main__':
    (train_images, train_labels), (test_images, test_labels) = mnist.load_data()
    test_images, test_labels = test_images[:10], test_labels[:10]  # 10枚だけ推論をおこなう
    test_images = preprocess_dataset(images=test_images)
    pred = predict(test_images)
    print(f'prediction: {pred}')
    print(f'labels: {test_labels}')
    # 推論した画像を表示する
    for idx, image in enumerate(test_images):
        plt.subplot(1, 10, idx+1)
        plt.imshow(image.reshape((28, 28)), 'gray')
        plt.axis("off")    
    plt.show()

ここでは、MNISTデータセットのテストデータ 10 枚だけを例として推論しています。

ニューラルネットワークの実行

モデルの学習

train.py を実行してニューラルネットワークモデルの学習をおこないます。

$ python train.py
## 出力
Model: "model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input (InputLayer)          [(None, 784)]             0         
                                                                 
 hidden1 (Dense)             (None, 256)               200960    
                                                                 
 hidden2 (Dense)             (None, 128)               32896     
                                                                 
 dropout (Dropout)           (None, 128)               0         
                                                                 
 output (Dense)              (None, 10)                1290      
                                                                 
=================================================================
Total params: 235,146
Trainable params: 235,146
Non-trainable params: 0
_________________________________________________________________
Epoch 1/50
96/96 [==============================] - 1s 5ms/step - loss: 9.6860 - acc: 0.4858 - val_loss: 0.8857 - val_acc: 0.7192
Epoch 2/50
96/96 [==============================] - 0s 5ms/step - loss: 1.2142 - acc: 0.6090 - val_loss: 0.6163 - val_acc: 0.8107
.
.
.
Epoch 24/50
96/96 [==============================] - 0s 4ms/step - loss: 0.1821 - acc: 0.9410 - val_loss: 0.2341 - val_acc: 0.9592
Epoch 25/50
96/96 [==============================] - 0s 4ms/step - loss: 0.1797 - acc: 0.9416 - val_loss: 0.2252 - val_acc: 0.9586

学習が終了すると、app/ ディレクトリ内に学習済モデル model.h5 とモデルの構造が記載された model.pdf が生成されます。model.pdf 内容は下図の通りです。

▲ model.pdf

TensorBordをつかってみる

モデルの学習過程を確認するために、TensorBord を利用してみます。

コマンドラインで下記のコマンドを実行します。

$ tensorboard --logdir ./logs/fit
## 出力
Serving TensorBoard on localhost; to expose to the network, use a proxy or pass --bind_all
TensorBoard 2.9.1 at http://localhost:6006/ (Press CTRL+C to quit)

http://localhost:6006/ にアクセスすることで、下図のように、学習過程における各指標の推移を確認することができます。

学習済モデルによる推論

学習したモデルを用いて、MNISTデータセットのテストデータを推論してみます。コマンドラインで次のコマンドを実行します。

$ python predict.py
## 出力
prediction: [7 2 1 0 4 1 4 9 5 9]
labels: [7 2 1 0 4 1 4 9 5 9]
▲ 推論したテストデータ画像


以上のように、ニューラルネットワークによる、手書き数字画像の分類モデルを作成することができました。

回帰問題はこちら ▼

タイトルとURLをコピーしました