9日目:画像認識の扉を開く!CNN(畳み込みニューラルネットワーク)入門
皆さん、こんにちは!AI学習ロードマップ9日目を迎えました。この一週間と一日で、データの準備から、線形回帰やロジスティック回帰といった基本的な機械学習モデルまで、幅広い知識を習得してきましたね。
今日からは、AIの世界で最もホットな分野の一つである「画像認識」に足を踏み入れます。その主役となるのが、ディープラーニングモデルの中でも特に画像処理に特化した「CNN(Convolutional Neural Network:畳み込みニューラルネットワーク)」です。
線形回帰やロジスティック回帰とは全く異なるアプローチで画像データを扱うCNNは、画像分類、物体検出、画像生成など、現代のAIアプリケーションの多くを支える基盤技術です。本日は、CNNの基本原理、主要な構成要素、そしてなぜ画像認識にCNNが不可欠なのかを、分かりやすく解説していきます。
1. なぜ画像認識は難しいのか?CNNが必要な理由
これまでの機械学習モデル(線形回帰など)は、表形式の数値データを扱うのに適していました。しかし、画像をそのままこれらのモデルに投入しようとすると、いくつかの大きな課題に直面します。
1.1. 画像データの特性と課題
- 高次元性: たとえば、28x28ピクセルの白黒画像でも、$28 \times 28 = 784$ 個の数値(ピクセル値)が特徴量になります。カラー画像(RGB)ならさらに3倍です。これらの膨大なピクセル値をそのままフラットにしてモデルに入力すると、特徴量の数が爆発的に増え、計算コストが高くなり、過学習しやすくなります。
- 空間的関係性の欠如: 画像において「猫の耳」や「犬の鼻」といった特徴は、特定のピクセルだけでなく、それらの周囲のピクセルとの空間的な関係性によって定義されます。画像をフラットな一次元ベクトルに変換すると、この重要な空間的関係性が失われてしまいます。
- 位置・スケール・回転への不変性: 同じ「猫」の画像でも、猫の位置が少しずれたり、サイズが変わったり、少し回転したりするだけで、ピクセル値は大きく変化します。従来のモデルでは、これらを全て異なる画像として認識してしまう可能性があります。
1.2. CNNの必要性
CNNは、これらの課題を克服するために特別に設計されたニューラルネットワークです。画像の空間的特徴を効率的に抽出し、位置やスケール、回転の変化にもある程度頑健な(不変性を持つ)特徴を学習する能力を持っています。
2. CNNの主要な構成要素
CNNは主に以下の3種類の層(レイヤー)で構成されます。
2.1. 畳み込み層 (Convolutional Layer)
CNNの核心となる層です。入力画像に対して「フィルター(カーネル)」と呼ばれる小さな行列をスライドさせながら、特徴を抽出します。
- フィルター(カーネル): 特定のパターン(例:エッジ、コーナー、特定のテクスチャ)を検出するための小さな数値の行列です。
- 畳み込み演算: フィルターを画像上でスライドさせながら、フィルターの数値と画像の部分的なピクセル値を掛け合わせて足し合わせる演算です。これにより、「特徴マップ(Feature Map)」または「活性化マップ(Activation Map)」と呼ばれる新しい画像が生成されます。
- 特徴抽出: 畳み込み層を重ねることで、初期の層では単純なエッジや線などの低レベルな特徴を抽出し、深い層になるにつれて、目、鼻、車輪など、より複雑な高レベルな特徴を抽出するようになります。
- 重み共有: 同じフィルターが画像全体にわたって適用されるため、モデルが学習すべきパラメータの数を大幅に削減できます。これがCNNの効率性の大きな理由の一つです。
# 畳み込み演算のイメージ(Pythonでの簡易的な手動計算)
import numpy as np
# 3x3の入力画像 (簡略化のためグレースケール)
image = np.array([
[10, 20, 30, 40],
[50, 60, 70, 80],
[90, 100, 110, 120],
[130, 140, 150, 160]
])
# 2x2のフィルター (エッジ検出用フィルターの例)
# このフィルターは、右下がり対角線方向のエッジに反応しやすい
filter_kernel = np.array([
[1, 0],
[0, -1]
])
# 出力特徴マップのサイズ計算 (簡単な場合)
# (入力サイズ - フィルターサイズ + 1)
# (4 - 2 + 1) = 3 -> 3x3の出力になる
output_feature_map = np.zeros((3, 3))
# 畳み込み演算の実行 (手動ループ)
for i in range(image.shape[0] - filter_kernel.shape[0] + 1):
for j in range(image.shape[1] - filter_kernel.shape[1] + 1):
# 画像の部分を抽出
patch = image[i:i+filter_kernel.shape[0], j:j+filter_kernel.shape[1]]
# 畳み込み演算 (要素ごとの積の合計)
output_feature_map[i, j] = np.sum(patch * filter_kernel)
print("--- 入力画像 ---")
print(image)
print("\n--- フィルター ---")
print(filter_kernel)
print("\n--- 出力特徴マップ (手動計算) ---")
print(output_feature_map)
この手動計算はあくまでイメージですが、実際のディープラーニングフレームワーク(TensorFlowやPyTorch)では、この演算が非常に高速に実行されます。
2.2. プーリング層 (Pooling Layer)
畳み込み層の後によく配置されます。特徴マップの空間的なサイズを削減(ダウンサンプリング)し、計算量を減らし、位置のわずかなずれに対する不変性を高めます。
- 最大プーリング (Max Pooling): フィルター領域内の最も大きい値を選択します。
- 平均プーリング (Average Pooling): フィルター領域内の値の平均を取ります。
例えば、2x2の最大プーリングは、2x2の領域内で最大値だけを残し、特徴マップのサイズを半分にします。
# 最大プーリングのイメージ (2x2フィルター、ストライド2)
feature_map_input = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
])
# 2x2プーリング、ストライド2 (overlapなしで2マスずつ進む)
pooled_output = np.array([
[max(feature_map_input[0:2, 0:2].flatten()), max(feature_map_input[0:2, 2:4].flatten())],
[max(feature_map_input[2:4, 0:2].flatten()), max(feature_map_input[2:4, 2:4].flatten())]
])
print("\n--- プーリング層への入力特徴マップ ---")
print(feature_map_input)
print("\n--- 最大プーリング後の出力 ---")
print(pooled_output)
プーリング層は、特徴マップの最も重要な情報(最も強く活性化した部分)を保持しつつ、ノイズや細かい位置のずれを吸収する役割を果たします。
2.3. 全結合層 (Fully Connected Layer)
プーリング層で抽出された高レベルな特徴は、最終的に全結合層に渡されます。ここでは、画像認識タスク(例:画像分類)の最終的な予測が行われます。
- 特徴マップの平坦化 (Flatten): 畳み込み層とプーリング層によって生成された多次元の特徴マップは、全結合層に入力するために一次元のベクトルに変換されます。
- 分類/回帰: 全結合層は、入力された特徴ベクトルに基づいて、最終的な分類結果(例:猫か犬か)や回帰値を出力します。出力層には、分類の場合はSoftmax活性化関数が、回帰の場合は線形活性化関数がよく用いられます。
3. CNNの学習プロセス(大まかな流れ)
- 入力: 画像がCNNに入力されます。
- 特徴抽出: 複数の畳み込み層とプーリング層が交互に配置され、画像から階層的な特徴(エッジ、テクスチャ、部品、オブジェクトなど)を自動的に学習・抽出します。
- 平坦化: 抽出された多次元の特徴マップが一次元ベクトルに変換されます。
- 分類/予測: 平坦化されたベクトルが一つ以上の全結合層に渡され、最終的な分類(または回帰)の予測が行われます。
- 損失計算: モデルの予測と実際の正解(ラベル)との間にどれくらいの誤差があるか(損失)を計算します。
- 最適化: 損失を最小化するように、モデル内の各層のフィルターや重み(パラメータ)を微調整(勾配降下法など)します。このプロセスが「学習」です。
4. PythonでのCNN実装の概要(Keras/TensorFlow)
実際にCNNを構築するには、TensorFlowやPyTorchのようなディープラーニングフレームワークを使用します。ここでは、Keras(TensorFlowのAPIとして統合されている)を使ったシンプルなCNNの骨格を見てみましょう。
import tensorflow as tf
from tensorflow.keras import layers, models
import matplotlib.pyplot as plt
# 例としてMNISTデータセットを使用 (手書き数字の画像データセット)
# 画像認識のHello World!
(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()
# 画像データを正規化 (0-255のピクセル値を0-1に変換)
train_images = train_images.reshape((60000, 28, 28, 1)).astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1)).astype('float32') / 255
# モデルの構築
model = models.Sequential()
# 1. 畳み込み層 + ReLU活性化関数
# filters: フィルターの数, kernel_size: フィルターのサイズ
# activation='relu': 活性化関数 (非線形性を導入)
# input_shape: 入力画像の形状 (28x28ピクセル、1チャネル=グレースケール)
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
# 2. 最大プーリング層
# pool_size: プーリングの領域サイズ (2x2)
model.add(layers.MaxPooling2D((2, 2)))
# 3. 2つ目の畳み込み層 + ReLU
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
# 4. 2つ目の最大プーリング層
model.add(layers.MaxPooling2D((2, 2)))
# 5. 3つ目の畳み込み層 + ReLU (オプション)
# より深い特徴を抽出
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
# 6. 平坦化 (全結合層への入力のために多次元データを一次元に変換)
model.add(layers.Flatten())
# 7. 全結合層 + ReLU
model.add(layers.Dense(64, activation='relu'))
# 8. 出力層 (数字10クラス分類なので10個のニューロン、Softmaxで確率出力)
model.add(layers.Dense(10, activation='softmax'))
# モデルのサマリ表示
model.summary()
# モデルのコンパイル (損失関数、オプティマイザ、評価指標の設定)
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy', # ラベルが整数値なのでこれを使う
metrics=['accuracy'])
# モデルの学習
history = model.fit(train_images, train_labels, epochs=5,
validation_data=(test_images, test_labels))
# 学習履歴の可視化
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(history.history['accuracy'], label='Training Accuracy')
plt.plot(history.history['val_accuracy'], label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(history.history['loss'], label='Training Loss')
plt.plot(history.history['val_loss'], label='Validation Loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()
上記のコードは、MNISTデータセット(手書き数字認識)をCNNで学習する例です。モデルの層の定義、コンパイル、そして学習の基本的な流れを理解できるでしょう。学習の過程で訓練データと検証データの精度や損失がどのように変化するかをグラフで確認することは、過学習の兆候を捉える上で非常に重要です。
5. まとめと次へのステップ
本日は、AI学習ロードマップの9日目として、画像認識の強力な武器である CNN(畳み込みニューラルネットワーク) の基礎を学びました。
- 高次元性や空間的関係性といった画像データの特性に対応するためにCNNが開発されたことを理解しました。
- CNNは主に畳み込み層(特徴抽出)、プーリング層(特徴の要約とサイズ削減)、全結合層(最終予測)から構成されることを学びました。
- 重み共有の概念が、CNNの効率性と性能の鍵であることを理解しました。
- Keras/TensorFlowを使った簡単なCNNの実装例を通じて、具体的なコードのイメージを掴みました。
CNNは、現代のAI技術において非常に重要な位置を占めています。自動運転、顔認証、医療画像診断など、その応用範囲は計り知れません。
明日は、時系列データを扱うためのニューラルネットワークである「RNN(リカレントニューラルネットワーク)」の基礎について学びます。音声認識や自然言語処理の扉を開く、また異なる種類のモデルについて理解を深めていきましょう。
それでは、また明日!