深層学習③CNN I

Categories:

第3章 深層学習:CNN Ⅰ

📚 この章の学習目標

  • 深層学習の概要と人間の脳との類似性を理解する
  • TensorFlowとKerasの関係を学ぶ
  • CNNの基本構造(Feature Extractor + Classifier)を把握する
  • Convolution、Pooling、Padding、Strideなどの重要な概念を習得する
  • KerasのSequential APIを使ったモデル構築を実践する
  • MNISTデータセットを用いた手書き数字認識を実装する

🧠 深層学習の概要

💡 深層学習(Deep Learning)とは?

深層学習は、人間の脳の学習と記憶のメカニズムを模倣した方法です。

多層のニューラルネットワークを用いて、データから自動的に特徴を抽出し、複雑なパターンを学習します。

深層学習が学習すること

最適な関数を見つける

入力データが与えられたときに答えを推測してくれる最適な関数を見つけることです。

F(X) = w₀ + w₁·x₁ + w₂·x₂ + w₃·x₃ + ... + wₙ·xₙ

深層学習が学習するのは、まさに重みWの値です。

学習の仕組み

予測値 y = w₀ + w₁·x₁ + w₂·x₂ + w₃·x₃ + ... + wₙ·xₙ

💡 最適化アルゴリズム

学習データの予測誤差を最小化できるように、重み(W)を最適化するアルゴリズムで学習します。

🎯 深層学習の長所と短所

✅ 長所

  • 柔軟性: 非常に柔軟で拡張性の高いモデル構成が可能
  • CNN Feature Extractor、RNN、GAN、強化学習
  • 最適化能力: 過去には難しかった問題もモデル化可能
  • 広範な応用: 画像、音声、自然言語など様々な領域に適用可能

⚠️ 短所

  • 計算コスト: 非常に多くの計算演算が必要(GPUが必須)
  • 高次元最適化: 過度に複雑な最適化問題
  • 整形データの限界: 表形式データでは性能向上が限定的

🏆 画像認識で人間を超えたDeep Learning

ImageNet Large Scale Visual Recognition Challenge (ILSVRC)

2017年以降、CNNモデルのパフォーマンスが人間を超えたと判断され、大会は終了しました。

現在はKaggleでCNNモデルの評価が続けられています。

🎨 自動特徴抽出

💡 Deep Learningの革新

従来の機械学習では人間が特徴量(Feature)を手動で設計していましたが、深層学習ではモデル自身が特徴抽出を自動的に学習します。

従来の機械学習:

入力 → [手動特徴抽出] → 機械学習 → 出力

深層学習:

入力 → [自動特徴抽出 + 学習] → 出力

🔧 TensorFlowとKeras

💡 TensorFlow 2.x と Keras

Kerasは、TensorFlow 2.0からTensorFlowの公式な高レベルAPIとして統合されました(2019年)。

TensorFlowをインストールすれば、tf.kerasモジュールを通じてKerasの機能を利用できます。

Kerasの3つのモデル作成方法

方法特徴適用場面
Sequential Modelシンプルで直感的単純な層の積み重ね
Functional API柔軟な構造設計複数入出力、並列層
Model Subclassing最も自由度が高いカスタムモデル

この章では

Sequential Modelを使用して、シンプルで理解しやすいモデル構築を学びます。

🏗️ CNN Classification Network 構造

💡 CNNの基本構造

CNNは大きく2つの部分で構成されています:

  1. 特徴抽出器(Feature Extractor) – Convolution + Pooling
  2. 分類器(Classifier) – Fully Connected Layers
入力層 → [特徴抽出器] → [分類器] → 出力層
       Feature Extractor  Classifier
       (Feature Learning)

入力画像の配列次元

📊 2次元配列(グレースケール画像1枚)

Height × Width

📊 3次元配列(RGB画像1枚)

Height × Width × RGB (3チャンネル)

📊 4次元配列(複数の画像)

Batch × Height × Width × RGB

⚠️ 重要

Conv2Dレイヤーを使用する場合、入力データは4次元である必要があります。

🔍 Convolution演算の基礎

フィルタ(Filter)とカーネル(Kernel)

💡 FilterとKernelの関係

CNNでは両者がほぼ混用されていますが、厳密には:

  • フィルタ: 複数のKernelで構成
  • カーネル: フィルタ内の個々の重み行列
Conv2D(filters=32, kernel_size=3)

上記は「3×3のカーネルを持つフィルタが32個」という意味です。

カーネルサイズの特徴

カーネルサイズ特徴
3×3小さい受容野、パラメータ効率的、最も一般的
5×5中程度の受容野、バランスが良い
7×7大きい受容野、多くのパラメータ

実務での選択

  • カーネルサイズが大きいほど、入力から多くの情報を取得可能
  • しかし、計算量とパラメータ数が大幅に増加
  • 現代のCNNでは3×3カーネルが最も一般的

📐 Stride(ストライド)

💡 Strideとは?

Convolution Filterを適用する際、Sliding Windowが移動する間隔を意味します。

Stride=1の場合:

7×7入力 + 3×3フィルタ + Stride=1 → 5×5出力

Stride=2の場合:

7×7入力 + 3×3フィルタ + Stride=2 → 3×3出力

Strideの効果

  • Stride値が大きいほど、出力サイズが小さくなる
  • 計算量が減少するが、情報の損失が増える

🎯 Padding(パディング)

💡 Paddingとは?

Convolution演算を実行する際、出力Feature Mapが入力Feature Mapに比べて小さくなるのを防ぐために適用します。

Padding=’valid’(パディングなし):

入力: 5×5 → 出力: 3×3 (サイズ減少)

Padding=’same’(パディングあり):

入力: 5×5 → 出力: 5×5 (サイズ維持)

Paddingの種類

  • ‘valid’: パディングなし(サイズ減少)
  • ‘same’: 出力サイズを入力と同じにする(ゼロパディング)

🏊 Pooling(プーリング)

💡 Poolingとは?

Convolutionで生成されたFeature Mapの一定領域ごとに1つの値を抽出し、Feature Mapのサイズを縮小(subsampling)する処理です。

Poolingの種類

Max Pooling:

┌─────┬─────┐
│ 1 3 │ 2 4 │  →  ┌─────┐
│ 5 2 │ 6 1 │  →  │ 5 6 │
├─────┼─────┤      ├─────┤
│ 7 4 │ 3 8 │  →  │ 7 8 │
│ 2 1 │ 5 2 │  →  └─────┘
└─────┴─────┘
最大値を抽出

Average Pooling:

各領域の平均値を抽出

Poolingの効果

  • Feature Mapのサイズを縮小
  • 位置の変化に対するロバスト性向上
  • パラメータ数の削減
  • 計算効率の向上

🛠️ KerasでのCNN実装

Convolution + Poolingの適用

from tensorflow.keras.layers import Input, Conv2D, MaxPooling2D

# 入力層(32×32のRGB画像)
input_tensor = Input(shape=(32, 32, 3))

# Convolution層
conv_out = Conv2D(
    filters=16,           # 16個のフィルタ
    kernel_size=3,        # 3×3カーネル
    activation='relu',    # ReLU活性化関数
    strides=1,           # ストライド1
    padding='same'       # パディングあり
)(input_tensor)

# Pooling層
pool_out = MaxPooling2D(pool_size=2)(conv_out)

💡 パラメータの意味

  • filters=16: 16個の異なるフィルタで特徴抽出
  • kernel_size=3: 3×3の重み行列
  • activation=’relu’: 非線形性を導入
  • padding=’same’: 出力サイズを維持

多チャンネル入力への適用

重要な原則

  • Convolution Filterのチャンネル数は入力Feature Mapのチャンネル数と同じでなければならない
  • 適用するFilterの個数が出力Feature Mapのチャンネル数になる

例:

入力: 6×6×3 (3チャンネル)
フィルタ: 3×3×3 (入力と同じチャンネル数)
フィルタ個数: 16個
↓
出力: 4×4×16 (16チャンネル)

🔬 実習:MNISTによる手書き数字認識

実習① Simple Neural Network(Dense層のみ)

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.utils import to_categorical

# データセットの読み込み
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

# データの正規化
train_images = train_images.reshape((60000, 784)).astype('float32') / 255
test_images = test_images.reshape((10000, 784)).astype('float32') / 255

# ラベルのOne-hot encoding
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)

One-hot encoding

ラベル「3」 → [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]

そのクラスに対応する位置のみ1、その他は0

モデルの定義と学習

# Sequential Modelの作成
model = Sequential([
    Dense(128, activation='relu', input_shape=(784,)),
    Dense(64, activation='relu'),
    Dense(10, activation='softmax')
])

# モデルのコンパイル
model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    metrics=['accuracy']
)

# モデルの学習
model.fit(
    train_images, 
    train_labels, 
    epochs=5, 
    batch_size=128, 
    validation_split=0.2
)

# モデルの評価
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print(f'Test accuracy: {test_acc:.4f}')

実習② CNNモデルの実装

MNISTデータセットの読み込み

import numpy as np
import matplotlib.pyplot as plt

# データの読み込み
(x_train, y_train), (x_test, y_test) = mnist.load_data()

print(f'訓練データ数: {len(x_train)}')
print(f'テストデータ数: {len(x_test)}')
print(f'画像のshape: {x_train[0].shape}')

# データの確認
plt.imshow(x_train[0], cmap=plt.cm.binary)
plt.title(f'Label: {y_train[0]}')
plt.show()

CNNモデルの設計

model = keras.models.Sequential()

# 第1ブロック:Convolution + Pooling
model.add(keras.layers.Conv2D(
    16, (3, 3), 
    activation='relu', 
    input_shape=(28, 28, 1)
))
model.add(keras.layers.MaxPool2D(2, 2))

# 第2ブロック:Convolution + Pooling
model.add(keras.layers.Conv2D(32, (3, 3), activation='relu'))
model.add(keras.layers.MaxPooling2D((2, 2)))

# Flatten層
model.add(keras.layers.Flatten())

# 全結合層
model.add(keras.layers.Dense(32, activation='relu'))

# 出力層
model.add(keras.layers.Dense(10, activation='softmax'))

print(f'モデルに追加されたLayer数: {len(model.layers)}')
model.summary()
# モデルのサマリー出力例
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 26, 26, 16)        160     
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 13, 13, 16)        0       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 11, 11, 32)        4640    
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 5, 5, 32)          0       
_________________________________________________________________
flatten (Flatten)            (None, 800)               0       
_________________________________________________________________
dense (Dense)                (None, 32)                25632   
_________________________________________________________________
dense_1 (Dense)              (None, 10)                330     
=================================================================
Total params: 30,762
Trainable params: 30,762
Non-trainable params: 0

パラメータ数の計算

🔍 Conv2D(16, (3,3)) のパラメータ数

カーネル: 3×3×1 (入力チャンネル)
フィルタ数: 16
バイアス: 16
↓
総パラメータ数 = 3×3×1×16 + 16 = 160

🔍 Conv2D(32, (3,3)) のパラメータ数

カーネル: 3×3×16 (前層の出力チャンネル)
フィルタ数: 32
バイアス: 32
↓
総パラメータ数 = 3×3×16×32 + 32 = 4,640

🔍 Dense(32) のパラメータ数

入力: 800 (5×5×32をFlatten)
出力: 32
バイアス: 32
↓
総パラメータ数 = 800×32 + 32 = 25,632

データの前処理

# 正規化(0-1の範囲に)
x_train_norm = x_train / 255.0
x_test_norm = x_test / 255.0

print("Before Reshape - x_train_norm shape:", x_train_norm.shape)
print("Before Reshape - x_test_norm shape:", x_test_norm.shape)

# Reshapeで4次元に変換
x_train_reshaped = x_train_norm.reshape(-1, 28, 28, 1)
x_test_reshaped = x_test_norm.reshape(-1, 28, 28, 1)

print("After Reshape - x_train_reshaped shape:", x_train_reshaped.shape)
print("After Reshape - x_test_reshaped shape:", x_test_reshaped.shape)
# 出力結果
Before Reshape - x_train_norm shape: (60000, 28, 28)
Before Reshape - x_test_norm shape: (10000, 28, 28)
After Reshape - x_train_reshaped shape: (60000, 28, 28, 1)
After Reshape - x_test_reshaped shape: (10000, 28, 28, 1)

モデルの学習と評価

# コンパイル
model.compile(
    optimizer='adam',
    loss='sparse_categorical_crossentropy',
    metrics=['accuracy']
)

# 学習
model.fit(x_train_reshaped, y_train, epochs=5)

# 評価
test_loss, test_accuracy = model.evaluate(x_test_reshaped, y_test, verbose=2)
print(f"test_loss: {test_loss:.4f}")
print(f"test_accuracy: {test_accuracy:.4f}")
# 学習結果例
Epoch 1/5
1875/1875 - 8s - loss: 0.1582 - accuracy: 0.9525
Epoch 2/5
1875/1875 - 7s - loss: 0.0542 - accuracy: 0.9835
Epoch 3/5
1875/1875 - 7s - loss: 0.0370 - accuracy: 0.9883
Epoch 4/5
1875/1875 - 7s - loss: 0.0273 - accuracy: 0.9914
Epoch 5/5
1875/1875 - 7s - loss: 0.0207 - accuracy: 0.9935

test_loss: 0.0423
test_accuracy: 0.9862

損失関数の選択

💡 損失関数の使い分け

損失関数用途ラベル形式
categorical_crossentropy多クラス分類One-hot encoding
sparse_categorical_crossentropy多クラス分類整数ラベル
binary_crossentropy二値分類0 or 1

予測結果の確認

# 予測
predicted_result = model.predict(x_test_reshaped)
predicted_labels = np.argmax(predicted_result, axis=1)

# 最初のテストデータを確認
idx = 0
print("model.predict() 結果:", predicted_result[idx])
print("予測ラベル:", predicted_labels[idx])
print("実際のラベル:", y_test[idx])

plt.imshow(x_test[idx], cmap=plt.cm.binary)
plt.title(f'予測: {predicted_labels[idx]}, 実際: {y_test[idx]}')
plt.show()

誤分類されたデータの分析

import random

# 誤分類されたデータのインデックスを収集
wrong_predict_list = []
for i, _ in enumerate(predicted_labels):
    if predicted_labels[i] != y_test[i]:
        wrong_predict_list.append(i)

print(f'誤分類数: {len(wrong_predict_list)} / {len(y_test)}')
print(f'精度: {(1 - len(wrong_predict_list)/len(y_test))*100:.2f}%')

# ランダムに5個選択して表示
samples = random.choices(population=wrong_predict_list, k=min(5, len(wrong_predict_list)))

for n in samples:
    print(f"\n予測確率分布: {predicted_result[n]}")
    print(f"予測ラベル: {predicted_labels[n]}, 実際のラベル: {y_test[n]}")
    plt.imshow(x_test[n], cmap=plt.cm.binary)
    plt.title(f'予測: {predicted_labels[n]}, 実際: {y_test[n]}', color='red')
    plt.show()

🎯 実習課題

🎯 課題:CIFAR-10データセットの分類

課題内容:

TensorFlowのCIFAR-10データセットを読み込んで、CNNモデルを使って10クラス分類の性能評価を行ってください。

参考URL:
https://www.tensorflow.org/datasets/catalog/cifar10

CIFAR-10データセット:

  • 10クラス(飛行機、自動車、鳥、猫、鹿、犬、カエル、馬、船、トラック)
  • 訓練データ: 50,000枚
  • テストデータ: 10,000枚
  • 32×32ピクセルのカラー画像

要件:

  1. データセットの読み込みと前処理
  2. CNNモデルの設計(Conv2D、MaxPooling、Denseを使用)
  3. 5-fold交差検証による性能評価
  4. EpochごとのTrainとValidationのloss/accuracyをグラフ化
  5. 誤分類された画像の分析

ヒント:

from tensorflow.keras.datasets import cifar10

(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# ラベル名
class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer',
               'dog', 'frog', 'horse', 'ship', 'truck']

期待される成果物:

  • モデルアーキテクチャの説明
  • 学習曲線(loss/accuracyのグラフ)
  • 交差検証の結果
  • 誤分類の分析と考察

📚 第3章のまとめ

🎯 第3章の重要ポイント

  • 深層学習は多層ニューラルネットワークで特徴を自動抽出
  • CNNは特徴抽出器(Feature Extractor)と分類器(Classifier)で構成
  • Convolution層でパターンを検出し、Pooling層でサイズを縮小
  • Keras Sequential APIで簡単にモデル構築が可能
  • データの次元に注意(Conv2Dには4次元が必要)
  • 適切な前処理(正規化、reshape)が重要
  • パラメータ数の計算を理解することでモデル設計を最適化できる

💡 次章への繋がり

第3章ではCNNの基礎を学びました。次の第4章では、CNNモデルの性能を向上させるための高度な技法(Data Augmentation、Batch Normalization、Dropoutなど)を学びます。