はじめに
この記事では簡単なCNN(畳み込みニューラルネットワーク)を作成して、画像を分類するモデルを作成してみます。使用するデータセットはCIFAR-10です。
深層学習はTensorFlowライブラリのKerasを使用することによって簡単に利用することができます。
この記事で使用されているソースコードは以下の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 | 説明 |
---|---|
0 | airplane(飛行機) |
1 | automobile(自動車) |
2 | bird(鳥) |
3 | cat(猫) |
4 | deer(鹿) |
5 | dog(犬) |
6 | frog(カエル) |
7 | horse(馬) |
8 | ship(船) |
9 | truck(トラック) |
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)
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]
以上のように、ニューラルネットワークによる、手書き数字画像の分類モデルを作成することができました。
回帰問題はこちら ▼