【深層学習入門】Kerasによる画像分類CNNの実装

機械学習

はじめに

この記事では簡単なCNN(畳み込みニューラルネットワーク)を作成して、画像を分類するモデルを作成してみます。使用するデータセットはCIFAR-10です。

深層学習はTensorFlowライブラリのKerasを使用することによって簡単に利用することができます。

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

GitHub - Joichiro433/Blog-cnn-impl
Contribute to Joichiro433/Blog-cnn-impl development by creating an account on GitHub.
Google Colab

\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*}

写真のデータセット「CIFAR-10」

「CIFAR-10」は、以下の 10 クラスの画像と正解ラベルを集めたデータセットです。訓練データ 50,000 件、テストデータ 10,000 件が含まれています。画像は RGB の3チャンネルカラー画像で、サイズは 32 × 32 ピクセルです。

ID説明
0airplane(飛行機)
1automobile(自動車)
2bird(鳥)
3cat(猫)
4deer(鹿)
5dog(犬)
6frog(カエル)
7horse(馬)
8ship(船)
9truck(トラック)
▲ CIFAR-10 のクラスラベル

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

from tensorflow.keras.datasets import cifar10

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

print(train_images.shape)
print(train_labels.shape)
print(test_images.shape)
print(test_labels.shape)
## 出力
(50000, 32, 32, 3)
(50000, 1)
(10000, 32, 32, 3)
(10000, 1)
▲ CIFAR-10データセット

KerasによるCNNの実装

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

.
├── 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

今回作成するCNNのアーキテクチャは下図のようなものとします。

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

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")
MODEL_FILE_PATH = Path('model.h5')

# model architecture parameters
INPUT_SHAPE = (32, 32, 3)
CONV_PARAMS = {
    'conv1_filters': 64, 
    'conv1_kernel_size': (3, 3),
    'conv2_filters': 64,
    'conv2_kernel_size': (3, 3),
    'maxpool_size': (2, 2),
    'dropout_rate': 0.25}
DENSE_UNITS = 512
DROPOUT_RATE = 0.5
OUTPUT_UNITS = 10

# fitting parameters
EPOCHS = 20
BATCH_SIZE = 128
VALIDATION_SPLIT = 0.1

model.py

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

from typing import Tuple, Dict, Any

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

import params


class ConvBlock:
    def __init__(
            self, 
            conv1_filters: int, 
            conv1_kernel_size: Tuple[int, int],
            conv2_filters: int, 
            conv2_kernel_size: Tuple[int, int], 
            maxpool_size: int, 
            dropout_rate: float) -> None:
        self.conv1: Conv2D = Conv2D(filters=conv1_filters, kernel_size=conv1_kernel_size, activation='relu', padding='same')
        self.conv2: Conv2D = Conv2D(filters=conv2_filters, kernel_size=conv2_kernel_size, activation='relu', padding='same')
        self.maxpool: MaxPool2D = MaxPool2D(pool_size=maxpool_size)
        self.dropout: Dropout = Dropout(rate=dropout_rate)
    
    def __call__(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.maxpool(x)
        x = self.dropout(x)
        return x


class CNNModel:
    def __init__(
            self,
            input_shape: Tuple[int, ...] = params.INPUT_SHAPE,
            conv_params1: Dict[str, Any] = params.CONV_PARAMS,
            conv_params2: Dict[str, Any] = params.CONV_PARAMS,
            dense_units: int = params.DENSE_UNITS,
            dropout_rate: float = params.DROPOUT_RATE,
            output_units: int = params.OUTPUT_UNITS) -> None:
        self.input: Input = Input(shape=input_shape)
        self.convblock1: ConvBlock = ConvBlock(**conv_params1)
        self.convblock2: ConvBlock = ConvBlock(**conv_params2)
        self.flatten: Flatten = Flatten()
        self.dense: Dense = Dense(units=dense_units, activation='relu')
        self.dropout: Dropout = Dropout(rate=dropout_rate)
        self.output: Dense = Dense(units=output_units, activation='softmax')

    def build(self) -> Model:
        input = self.input
        x = self.convblock1(input)
        x = self.convblock2(x)
        x = self.flatten(x)
        x = self.dense(x)
        x = self.dropout(x)
        output = self.output(x)
        return Model(inputs=input, outputs=output)

ConvBlock クラスでは、「畳み込み層→畳み込み層→プーリング層」という畳み込み演算をおこなうレイヤを定義します。

CNNModel クラスで、このレイヤを2つ重ね、次に「Flatten」で「畳み込み層」の 3 次元の出力を「全結合層」向けに 1 次元に変換します。最後に「全結合層」を2つ重ねています。この部分では「分類」を行っています。

preprocessing.py

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

今回、画像はRGBなので各ピクセルは、「0 ~ 255」の数値をとります。255で割って「0.0 ~ 1.0」の範囲に収めています。また、ラベルには 「one-hot エンコーディング」を適用します。

from typing import Tuple, Optional, Union

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


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

def preprocess_dataset(
        images: Images, 
        labels: Optional[Labels] = None) -> Union[Images, Tuple[Images, PLabels]]:
    images: Images = images.astype('float32') / 255.0
    if labels is None:
        return images
    labels: Labels = to_categorical(labels, num_classes=10)
    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 cifar10
from keras.optimizers import Adam
from keras.callbacks import EarlyStopping, ModelCheckpoint, TensorBoard
from keras.utils import plot_model

import params
from preprocessing import preprocess_dataset
from model import CNNModel


def main():
    (train_images, train_labels), (test_images, test_labels) = cifar10.load_data()
    train_images, train_labels = preprocess_dataset(images=train_images, labels=train_labels)

    model: Model = CNNModel().build()
    model.compile(optimizer=Adam(learning_rate=0.001), 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.BATCH_SIZE,
        epochs=params.EPOCHS,
        validation_split=params.VALIDATION_SPLIT,
        callbacks=callbacks)


if __name__ == '__main__':
    main()
    

ここでは、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 cifar10
from rich import print

import params
from preprocessing import preprocess_dataset


def predict(images):
    images = preprocess_dataset(images=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) = cifar10.load_data()
    test_images, test_labels = test_images[:10], test_labels[:10]  # 10枚だけ推論をおこなう
    pred = predict(images=test_images)
    print(f'prediction: {pred}')
    print(f'labels: {test_labels.flatten()}')
    # 推論した画像を表示する
    for idx, image in enumerate(test_images):
        plt.subplot(1, 10, idx+1)
        plt.imshow(image)
        plt.axis("off")    
    plt.show()

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

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

モデルの学習

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

$ python train.py
## 出力
Model: "model"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_1 (InputLayer)        [(None, 32, 32, 3)]       0         
                                                                 
 conv2d (Conv2D)             (None, 32, 32, 64)        1792      
                                                                 
 conv2d_1 (Conv2D)           (None, 32, 32, 64)        36928     
                                                                 
 max_pooling2d (MaxPooling2D  (None, 16, 16, 64)       0         
 )                                                               
                                                                 
 dropout (Dropout)           (None, 16, 16, 64)        0         
                                                                 
 conv2d_2 (Conv2D)           (None, 16, 16, 64)        36928     
                                                                 
 conv2d_3 (Conv2D)           (None, 16, 16, 64)        36928     
                                                                 
 max_pooling2d_1 (MaxPooling  (None, 8, 8, 64)         0         
 2D)                                                             
                                                                 
 dropout_1 (Dropout)         (None, 8, 8, 64)          0         
                                                                 
 flatten (Flatten)           (None, 4096)              0         
                                                                 
 dense (Dense)               (None, 512)               2097664   
                                                                 
 dropout_2 (Dropout)         (None, 512)               0         
                                                                 
 dense_1 (Dense)             (None, 10)                5130      
                                                                 
=================================================================
Total params: 2,215,370
Trainable params: 2,215,370
Non-trainable params: 0
_________________________________________________________________
Epoch 1/20
352/352 [==============================] - 65s 182ms/step - loss: 1.5780 - acc: 0.4235 - val_loss: 1.1559 - val_acc: 0.5800
Epoch 2/20
352/352 [==============================] - 64s 181ms/step - loss: 1.1218 - acc: 0.6009 - val_loss: 0.8795 - val_acc: 0.6932
.
.
.
Epoch 12/20
352/352 [==============================] - 61s 173ms/step - loss: 0.4221 - acc: 0.8494 - val_loss: 0.6031 - val_acc: 0.8070
Epoch 13/20
352/352 [==============================] - 65s 183ms/step - loss: 0.3932 - acc: 0.8588 - val_loss: 0.5974 - val_acc: 0.8092

学習が終了すると、app/ ディレクトリ内に学習済モデル model.h5 とモデルの構造が記載された 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/ にアクセスすることで、下図のように、学習過程における各指標の推移を確認することができます。

学習済モデルによる推論

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

$ python predict.py
## 出力
prediction: [3 8 1 0 6 6 1 6 3 1]
labels:     [3 8 8 0 6 6 1 6 3 1]
▲ 推論したテストデータ画像

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

回帰問題はこちら ▼

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