【TensorFlow/Keras】CNN(畳み込みニューラルネットワーク)による画像分類の基本
による画像分類の基本.jpg)
TensorFlow / Kerasを用いて CNN (Convolutional Neural Network:畳み込みニューラルネットワーク) による画像分類を行う方法について解説します。例として MNIST(エムニスト)という手書き画像データセットの分類実装を紹介します。
目次
CNN(畳み込みニューラルネットワーク)
ディープラーニングは、2011 年以降コンピュータービジョン領域を最初のきっかけとして発展してきました。CNN (Convolutional Neural Network:畳み込みニューラルネットワーク) はディープラーニングに関するモデルで、2011 年頃に画像分類のコンテストでそれまでにない精度の成果を上げ、ディープラーニングの発展のきっかけになったものです。現在利用されているモデルの多くは CNN の考え方を色々と拡張したものとなっています。
この記事では、CNN の概要について紹介するとともに MNIST(エムニスト)の手書き文字認識を例に TensorFlow / Keras を用いた実装例を紹介します。
CNN の構成概要
CNN (Convolutional Neural Network:畳み込みニューラルネットワーク) は、「畳み込み層」と「プーリング層」という層を使って、特徴抽出を行うニューラルネットワークです。
以下の図は、この後に紹介するMNIST(エムニスト)という手書き画像データ分類の例におけるモデルを記載してみたものです。

CNN では、この図のように畳み込み層、プーリング層というものを繰り返し適用していきます。各層は以下のような層になっています。
- 畳み込み層:入力データの一部分に注目して、その部分の特徴を調べる層
- プーリング層:畳み込み層から得た情報を縮約する層
最後に全結合層につないでいるのは分類をするためで、この部分は解決したい問題によって変わります。
CNN では、畳み込み層で特徴を抽出するフィルター(カーネル)の重みを学習していきます。例えば、顔画像認識の例だと、入力に近い層の畳み込み層は「線や点といった細かな概念の特徴を抽出するフィルター」となり、出力層に近い層のフィルターは「目や鼻」のようなより大きな概念の特徴を抽出するものとなります。
次からは、畳み込み、プーリング、パディングといった CNN の要素についてもう少し具体的に紹介していきます。
畳み込み
畳み込みについて具体的に見ていきます。畳み込みとはフィルター(カーネル)という重みを入力にかけて、その部分に対する特徴を抽出します。フィルタを数画素ずつずらしながら入力の各部分に対する特徴の値を出力します。なお、このフィルタをずらす幅をストライドと言います。

上記の図を使ってもう少し説明します。この例は、フィルタサイズが 3 の場合です。
一般的には画像は幅や高さの他に深さがあります。例えば、カラー画像の場合には R、G、B それぞれの画素値を持っています。この時には深さは 3 となります。一方で、MNIST のようなグレースケール画像は深さは 1 です。
上記のような入力画像で濃い黒枠の画素に対してフィルタをかける場合を考えてみます。この時、フィルタの各重みと各画素の値のそれぞれ積をとって、すべてを足し合わせたものが出力値になります。
ここで、出力にも深さがある点にお気づきでしょうか。出力の深さというのは適用するフィルタの数を表します。このフィルタ数はモデルの設計時に指定するものです。ある画像のある部分に複数フィルタをかけると、それぞれのフィルタに対する出力値が出ます。その値が上の図での出力における濃い黒枠の部分ということになります。
プーリング
プーリングについて具体的に見ていきます。プーリング層は畳み込み層の出力を縮約することでデータ量を削減している層になります。
一般的に使用される方法は Max プーリングと言います。Max プーリングでは、フィルタをあてた中で最大値となる値を出力に採用する方法です。例えば、以下例の緑の部分に対して Max プーリングを適用する場合、最大値は 6 なので出力は 6 になります。

他にも、フィルタをあてたときの画素の平均をとる Average プーリングなどもありますが、Max プーリングが最もよく使われる方法かと思います。
畳み込みで得られる特徴は、画像のある部分を見たときには同じような特徴が集まっているため無駄があります。プーリングにより部分的な情報の損失を抑えつつデータを圧縮することができます。また、プーリングには元画像の平行移動でも影響を受けないようにするという役割も果たします。
パディング
これまで見てきたように、CNN でフィルタを適用すると画像が縮小されます。フィルタを適用した時の画像の縮小を抑えるために、入力画像の周囲に画素を追加することをパディングと言います。
また、一般的に追加するピクセルの画素数は 0 のためゼロパディングとも呼ばれます。

パディングにより、画像の端のデータ値の特徴も考慮されるようになりますが、畳み込みの演算回数は増えます。
TensorFlow / Kerasを用いた CNN による
画像分類の実装
TensorFlow / Kerasを用いた CNN による画像分類の実装例を紹介します。上記の説明でも用いてきた以下モデルを実装してみます。

モデルの構造をまとめてみると以下のようになります。
畳み込み層設定
- フィルターサイズ:3
- ストライド:1
- パディング:なし
- 活性化関数:ReLU
プーリング層設定
- フィルターサイズ:2
- ストライド:2
- パディング:なし
ネットワーク設計
- フィルタ数は 32、64、128 と適用
- 最終的に 128 のプーリング層の出力を全結合層で 10 個に分類
- 分類の活性化関数としては softmax を使い、各文字に対する分類結果を確率値で出力
- 全結合層につなぐ前に過学習防止のためにドロップアウトを 0.5 で適用
- オプティマイザは Adam
- 損失関数はクロスエントロピー(sparse_categorical_crossentropy)
- 指標は正解率(accuracy)
なお、分類のためのモデルの構築方法が上記だけということではありません。ベースとなる実装方法として参考にしていただけるとよいかと思っています。
層の数や、フィルターサイズ、ストライド、フィルタ数、最適化手法(オプティマイザ)、損失関数等、色々と変えられるところがありますので、どういったモデル設定が性能が良いのか色々と試して比較してみてもらえるとよいかと思います。
実装例
上記で説明した CNN のモデルについて TensorFlow / Keras を用いて実装した例を 紹介します。
import matplotlib.pyplot as plt
import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.datasets import mnist
def main():
"""メイン関数"""
# ===== MNIST(エムニスト)データの読込
(train_imgs, train_labels), (test_imgs, test_labels) = mnist.load_data()
train_imgs = train_imgs.reshape((60000, 28, 28, 1))
test_imgs = test_imgs.reshape((10000, 28, 28, 1))
# 訓練データの一部(20%)を評価データとして使う
idx = int(train_imgs.shape[0] * 0.2)
train_imgs, val_imgs = train_imgs[idx:], train_imgs[:idx]
train_labels, val_labels = train_labels[idx:], train_labels[:idx]
# ===== CNNモデルの構築
# MNIST画像は28×28でチャンネルは1
inputs = keras.Input(shape=(28, 28, 1))
# 前処理0~1へ正規化
x = layers.Rescaling(1.0 / 255)(inputs)
# 畳み込み層とプーリング層の定義
x = layers.Conv2D(32, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(64, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(128, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
# 平坦化する
x = layers.Flatten()(x)
# ドロップアウトを設定
x = layers.Dropout(0.5)(x)
# 分類のために10のノードに接続
outputs = layers.Dense(10, activation="softmax")(x)
# モデルの作成
model = keras.Model(inputs=inputs, outputs=outputs)
# モデル構成の表示&画像保存
print(model.summary())
keras.utils.plot_model(model, "mnist_cnn_classifier.png", show_shapes=True)
# ===== オプティマイザ、損失関数、指標を設定してコンパイル
model.compile(
optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]
)
# ===== fitを使ったモデルの訓練
num_epochs = 5
history = model.fit(
train_imgs,
train_labels,
epochs=num_epochs,
batch_size=32,
validation_data=(val_imgs, val_labels),
)
# ===== history情報の可視化
# 損失関数(loss)の履歴
loss = history.history["loss"]
val_loss = history.history["val_loss"]
# 正解率(accuracy)の履歴
acc = history.history["accuracy"]
val_acc = history.history["val_accuracy"]
# 損失関数の履歴描画
x_epoch = range(1, num_epochs + 1)
plt.plot(x_epoch, loss, "r", label="training loss")
plt.plot(x_epoch, val_loss, "b", label="validation loss")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()
# 正解率の履歴描画
plt.figure()
plt.plot(x_epoch, acc, "r", label="training acc")
plt.plot(x_epoch, val_acc, "b", label="validation acc")
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.legend()
plt.show()
# ===== evaluateを使ったテストデータでの評価
result = model.evaluate(test_imgs, test_labels)
print(result)
# ===== predictを使って予測結果を表示
preds = model.predict(test_imgs)
print(f"予測: {np.argmax(preds[0])}, 正解: {test_labels[0]}")
if __name__ == "__main__":
main()【実行結果例】
Model: "model"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
input_1 (InputLayer) [(None, 28, 28, 1)] 0
rescaling (Rescaling) (None, 28, 28, 1) 0
conv2d (Conv2D) (None, 26, 26, 32) 320
max_pooling2d (MaxPooling2D (None, 13, 13, 32) 0
)
conv2d_1 (Conv2D) (None, 11, 11, 64) 18496
max_pooling2d_1 (MaxPooling (None, 5, 5, 64) 0
2D)
conv2d_2 (Conv2D) (None, 3, 3, 128) 73856
max_pooling2d_2 (MaxPooling (None, 1, 1, 128) 0
2D)
flatten (Flatten) (None, 128) 0
dropout (Dropout) (None, 128) 0
dense (Dense) (None, 10) 1290
=================================================================
Total params: 93,962
Trainable params: 93,962
Non-trainable params: 0
_________________________________________________________________
None
Epoch 1/5
1500/1500 [==============================] - 9s 4ms/step - loss: 0.3419 - accuracy: 0.8942 - val_loss: 0.1142 - val_accuracy: 0.9676
Epoch 2/5
1500/1500 [==============================] - 6s 4ms/step - loss: 0.1294 - accuracy: 0.9620 - val_loss: 0.0752 - val_accuracy: 0.9772
Epoch 3/5
1500/1500 [==============================] - 6s 4ms/step - loss: 0.0971 - accuracy: 0.9715 - val_loss: 0.0670 - val_accuracy: 0.9812
Epoch 4/5
1500/1500 [==============================] - 6s 4ms/step - loss: 0.0815 - accuracy: 0.9753 - val_loss: 0.0561 - val_accuracy: 0.9843
Epoch 5/5
1500/1500 [==============================] - 6s 4ms/step - loss: 0.0701 - accuracy: 0.9786 - val_loss: 0.0513 - val_accuracy: 0.9866
313/313 [==============================] - 1s 3ms/step - loss: 0.0455 - accuracy: 0.9868
[0.04547674208879471, 0.9868000149726868]
313/313 [==============================] - 0s 1ms/step
予測: 7, 正解: 7

実装内容の解説
上記で紹介した実装例の各部分ごとに内容を説明していきます。
必要モジュールのインポート
import matplotlib.pyplot as plt import numpy as np from tensorflow import keras from tensorflow.keras import layers from tensorflow.keras.datasets import mnist
まずは、必要なモジュール類をインポートします。今回は TensorFlow の Keras を使用するので、tensorflow から keras や layers をインポートしています。データセットとしては Keras 内の MNIST データセットをあわせてインポートします。
データセットの用意
# ===== MNIST(エムニスト)データの読込
(train_imgs, train_labels), (test_imgs, test_labels) = mnist.load_data()
train_imgs = train_imgs.reshape((60000, 28, 28, 1))
test_imgs = test_imgs.reshape((10000, 28, 28, 1))
# 訓練データの一部(20%)を評価データとして使う
idx = int(train_imgs.shape[0] * 0.2)
train_imgs, val_imgs = train_imgs[idx:], train_imgs[:idx]
train_labels, val_labels = train_labels[idx:], train_labels[:idx]今回使用する MNIST データセットを準備しています。MNIST データセットは、訓練データ 60,000、テストデータ 10,000というデータです。読み込みは load_data() で実行できます。また、訓練データのうち 20% を評価データとして使用します。
CNNモデルの構築
# ===== CNNモデルの構築
# MNIST画像は28×28でチャンネルは1
inputs = keras.Input(shape=(28, 28, 1))
# 前処理0~1へ正規化
x = layers.Rescaling(1.0 / 255)(inputs)
# 畳み込み層とプーリング層の定義
x = layers.Conv2D(32, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(64, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
x = layers.Conv2D(128, kernel_size=3, activation="relu")(x)
x = layers.MaxPooling2D(pool_size=2)(x)
# 平坦化する
x = layers.Flatten()(x)
# ドロップアウトを設定
x = layers.Dropout(0.5)(x)
# 分類のために10のノードに接続
outputs = layers.Dense(10, activation="softmax")(x)
# モデルの作成
model = keras.Model(inputs=inputs, outputs=outputs)
# モデル構成の表示&画像保存
print(model.summary())
keras.utils.plot_model(model, "mnist_cnn_classifier.png", show_shapes=True)ここがモデル構築の中心部分です。今回は Functional API を用いた実装を行っています。Keras の API の実装方法の違いについては「Keras APIでモデルを構築する色々な方法と違い(Sequential API, Functional API, Subclassing API)」を参考にしてください。
まず、データの前処理として Rescalling 層を使ってデータを 0 ~ 1 になるように正規化します。
畳み込み層では Conv2D 層を使用します。最初の引数がフィルタ数 (filters= で指定しても構いません) で kernel_size がフィルタサイズ、activation で "relu" を指定しています。
プーリング層では Max プーリングの MaxPooling2D 層を使用します。pool_size を 2 としています。
畳み込み層とプーリング層をモデルに従って積み上げた後に、分類のための 10 の全結合層に接続しますが、その前に Flatten で平坦化してから Dropout 層を追加します。
モデル作成は keras.Model にインプットとアウトプットを指定します。summary() でモデルの構造を表示すると共にモデル構造を以下のように画像で出力しています。このように可視化するとモデルのつながり関係が分かりやすくて便利です。

モデルのコンパイル
# ===== オプティマイザ、損失関数、指標を設定してコンパイル
model.compile(
optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]
)モデルのコンパイルは compile を使用します。optimizer、loss、metrics で今回の設定値を指定しています。
モデルの訓練(学習)
# ===== fitを使ったモデルの訓練
num_epochs = 5
history = model.fit(
train_imgs,
train_labels,
epochs=num_epochs,
batch_size=32,
validation_data=(val_imgs, val_labels),
)モデルの訓練は fit で実行します。今回は手短に実行の確認ができるようにエポックは 5 としましたが、数字は増やしたりして試してみてください。評価用データとして validation_data を指定することで、各エポックで評価も行います。
訓練(学習)状況の可視化
# ===== history情報の可視化
# 損失関数(loss)の履歴
loss = history.history["loss"]
val_loss = history.history["val_loss"]
# 正解率(accuracy)の履歴
acc = history.history["accuracy"]
val_acc = history.history["val_accuracy"]
# 損失関数の履歴描画
x_epoch = range(1, num_epochs + 1)
plt.plot(x_epoch, loss, "r", label="training loss")
plt.plot(x_epoch, val_loss, "b", label="validation loss")
plt.xlabel("Epochs")
plt.ylabel("Loss")
plt.legend()
# 正解率の履歴描画
plt.figure()
plt.plot(x_epoch, acc, "r", label="training acc")
plt.plot(x_epoch, val_acc, "b", label="validation acc")
plt.xlabel("Epochs")
plt.ylabel("Accuracy")
plt.legend()
plt.show()fit は返却値として、損失関数や指標の推移 (history) を返却します。上記部分では、history の中に保存されている損失関数 (loss) の履歴と正解率 (accuracy) の履歴を取得してきて matplotlib の plot で描画しています。なお、val_xxx となっているものは、評価データに対する値になります。
テストデータでの評価及び予測
# ===== evaluateを使ったテストデータでの評価
result = model.evaluate(test_imgs, test_labels)
print(result)
# ===== predictを使って予測結果を表示
preds = model.predict(test_imgs)
print(f"予測: {np.argmax(preds[0])}, 正解: {test_labels[0]}")上記は、テストデータを使った評価と予測に関する部分です。テストデータを使った評価は evaluate を使用します。結果は [損失 (loss), 正解率 (accuracy)] という形で返ってきます。今回の例では 98.7% の正解率であったことが分かります。
また、予測結果を取得するには predict を使用します。今回のモデルは各文字に対する確率値が計算されます。そのため、予測結果としては確率が最も高い値と判断できるため argmax で取得しています。
以上が、TensorFlow / Keras を用いた MNIST 画像分類の実装例でした。比較的少ないステップ数で精度がよいモデルが構築できていることが分かると思います。
まとめ
ディープラーニングの画像認識で中心となる CNN (Convolutional Neural Network:畳み込みニューラルネットワーク) について概要を説明しました。また、MNIST(エムニスト)の手書き文字認識を例に TensorFlow / Keras を用いた実装例を紹介しました。
CNN を理解するには、畳み込み、プーリングといった層とそれぞれハイパーパラメータとなるフィルター数やフィルター(カーネル)サイズ等について理解する必要があります。CNN を用いた色々な手法がありますが、この記事の内容はそれらのベースとなる基礎的な内容です。
基本的な内容を理解した上で、発展的なモデル(ResNet等)について勉強していくとより理解が深まると思います。
上記で紹介しているソースコードについては GitHub にて公開しています。参考にしていただければと思います。



の実装.jpg)



