はじめに
畳み込みニューラルネットワーク(Convolutional Neural Network、以下 CNN)は、初見では複雑に感じられる手法です。実際、内部の仕組みは決して単純ではありませんが、TensorFlow を用いることで、比較的容易にニューラルネットワークを構築し、自分のデータで学習させ、独自のタスクを実行できます。
TensorFlow は NPM パッケージとしても提供されていますが、本記事のサンプルでは Python と pip3 を使用します。実際に手元で試す場合は、事前にインストールしておいてください。
この記事では、以下のノウハウ取得できます!
- CNN が何をしているのか、どのように動作するのか
- 注意すべき点やデメリット
- TensorFlow がこれらをどのように実現しているか
- 画像から猫と犬を判別する CNN を実際に作成する
CNN が何をしているのか、どのように動作するのか
ニューラルネットワークは、人間の脳の神経回路を模した仕組みで、「ニューロン」と呼ばれる層を重ねることでパターン認識などを行います。CNN(畳み込みニューラルネットワーク)は、**畳み込み層(Convolutional Layer)やプーリング層(Pooling Layer)**などを組み合わせた特殊なアーキテクチャで、画像内の特徴を効率的に検出できるように設計されています。
CNN の仕組みを考える前に、人間がどのように画像を認識しているかを考えてみましょう。次の猫の画像を見てください。
一目見ただけで、私たちはほぼ瞬時に「猫だ」と判断できます。その思考の流れを大まかに書くと、次のようになります。
- 頭の上に三角形が二つある形(耳)に気づく。経験的に、猫は尖った耳を持つため、この時点で仮説が立つ
- ひげ、毛の質感、全体の形状に気づく
- これらの特徴を過去の知識と組み合わせ、「これは猫だ」と結論づける
CNN も概念的にはこれと似た動きをします。複数の層がそれぞれ異なる特徴を処理し、最終的に学習済みデータと照らし合わせて判断を行います。
初期の層では単純なパターンを検出し、より深い層ではそれらを組み合わせて、より複雑な特徴を捉えていきます。
最初の畳み込み層の仕組み
最初の畳み込み層では、学習された複数のフィルタを生のピクセル値に適用します。
各フィルタは画像の小さな領域(例:3×3 や 5×5)をスライドしながら走査し、その位置ごとに「そのパターンをどれだけ強く検出したか」を表す値を出力します。
その結果として得られるのが 特徴マップ(Feature Map) です。人間が見るとグレースケール画像のように可視化されることが多いですが、内部的には単なる数値の集合(活性値)です。
例えば、あるフィルタはエッジ(輪郭)に強く反応し、別のフィルタは特定の質感に反応します。
猫の耳の輪郭に対して強い反応が出た場合、その空間位置に対応する特徴マップの値は大きくなります。
より深い層で何が起きているか
後続の畳み込み層では、前の層で生成された特徴マップに対して新しいフィルタを適用します。これにより、より低レベルな特徴を組み合わせて、より高レベルな特徴を表現できるようになります。
例としては以下のような階層構造になります。
- 初期層:エッジや色のグラデーション
- 中間層:形状、角、単純なテクスチャ
- 後半層:物体の一部、特定のクラスに特徴的なパターン
このような階層的な構造によって、CNN は画像全体を認識するために必要な情報を段階的に抽出していきます。
最終的には、特定の特徴の組み合わせが「猫」というクラスの学習済みパターンと高い一致度を持つかどうかを判断できるようになります。
注意点とデメリット
CNN を設計するうえで、適切な層の数を選ぶことは非常に重要で、バランスが求められます。
層が少なすぎる場合(アンダーフィッティング)
モデルが単純すぎて、有意なパターンを学習できません。その結果、猫と犬の違いを十分に捉えられず、微妙な特徴を検出できない可能性があります。
層が多すぎる場合(オーバーフィッティング)
モデルが学習データに過剰に適合し、ノイズやデータ固有の癖まで学習してしまいます。例えば、学習データ中の猫がすべて尖った両耳を持っている場合、片耳が欠けていたり垂れていたりする猫を「猫ではない」と判断してしまうかもしれません。
このような関係は バイアス・バリアンストレードオフ と呼ばれます。本記事の後半では、TensorFlow のツールを使って CNN を評価し、層が多すぎるのか少なすぎるのかを判断する方法を見ていきます。
実装(Python)
最初のステップは、画像データを読み込み、それらを学習用データまたはテスト用データとしてモデルに渡すことです。
すべてのデータがリポジトリに配置できたら、次はモデルを作成します。TensorFlow がすでに import されている前提で、基本となるモデルは次のように定義します。
model = tf.keras.models.Sequential([])
Sequential の配列内では、使用したいレイヤーを順番に定義していくだけでモデルを構築できます。
TensorFlow をサポートするレイヤー用ライブラリはいくつかありますが、その中でも Keras は特に初心者に扱いやすいライブラリです。レイヤー名が直感的で、用途に応じて選びやすいのが特徴です。
レイヤーの詳細な一覧は 公式ドキュメント に記載されていますが、ここではよく使われる代表的なレイヤーを紹介します。
-
Input
– 入力データの形状(例:画像サイズ)を定義します。通常、Sequentialモデルの最初に配置します。 -
Activation
– ReLU、Sigmoid、Softmax などの非線形関数を適用し、ネットワークが複雑なパターンを学習できるようにします。 -
Dense(全結合層)
– 各ニューロンが次の層のすべてのニューロンと接続されます。CNN では主に最終的な分類部分で使用されます。 -
Conv2D(畳み込み層)
– フィルタ(カーネル)を画像全体に適用して特徴を抽出します。CNN の中核となるレイヤーです。 -
MaxPooling2D / AveragePooling2D(プーリング層)
– 特徴マップの空間サイズ(縦・横)を縮小し、計算量の削減や過学習の防止に役立ちます。 -
Dropout
– 学習中に一定割合のニューロンをランダムに無効化し、過学習を防いで汎化性能を向上させます。 -
Flatten
– 多次元データ(特徴マップなど)を 1 次元ベクトルに変換し、Denseレイヤーに渡します。 -
BatchNormalization
– レイヤーへの入力を正規化し、学習を安定させつつ高速化します。
例えば Conv2D(畳み込み層)を使用する場合、レイヤーは次のように定義します。
tf.keras.layers.Conv2D
次に、画像に適用する フィルタ数 と カーネルサイズ を指定します。
フィルタ数が多いほど表現力は高くなりますが、その分オーバーフィッティングのリスクも大きくなります。適切な値を見つけるには試行錯誤が必要で、この後のセクションで検証方法を紹介します。
先ほど追加したレイヤーを続けると、次のように指定します。
tf.keras.layers.Conv2D({number of filters}, ({kernal}, {kernal}))
e.g.
tf.keras.layers.Conv2D(32, (3, 3))
次に、活性化関数の種類を指定します。代表的なものは以下のとおりです。
-
'relu'
レイヤーの活性化関数を ReLU(Rectified Linear Unit)に設定します。入力が正の値の場合はそのまま出力し、負の値の場合は 0 を出力します。学習を高速化し、勾配消失などの問題を起こしにくいため、最も一般的に使われます。 -
'sigmoid'
出力を 0〜1 の範囲に収めます。2 クラス分類に適しています。 -
'tanh'
出力を -1〜1 の範囲に収めます。一部の隠れ層で使用されます。 -
'softmax'
出力を合計 1 となる確率に変換します。多クラス分類で、主に出力層に使われます。 -
'linear'
活性化関数を適用せず、出力は入力と同じになります。
tf.keras.layers.Conv2D(32, (3, 3) activation='relu')
最後に、最初のレイヤーである場合は、入力画像のサイズを定義します。
tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(IMG_WIDTH, IMG_HEIGHT, 3))
IMG_WIDTH と IMG_HEIGHT には、使用したい画像サイズに応じて任意の値を設定してください。
末尾の 3 はカラーチャンネル数を表しており、RGB(フルカラー画像)を意味します。
いくつかレイヤーを追加しておき、後ほど実際にテストを行って、結果がどのようになるかを確認してみましょう。
データの用意
animals ディレクトリを作成し、その中に cat と dog の 2 つのサブディレクトリを用意します。
それぞれに、猫と犬の画像を学習できるだけの枚数配置してください。
実際に試してみたい場合は、以下のデータセットから十分な量の画像を取得できます。
https://www.kaggle.com/c/dogs-vs-cats/data
次に、ラベルと画像の配列を作成します。本記事では NumPy 配列への読み込み方法そのものを解説することが目的ではないため、ここでは処理内容を確認しつつ、そのままコピー&ペーストして試せる関数のみを掲載します。
def load_data(data_dir):
labels = []
images = []
# カテゴリ名からラベルへのマッピングを作成
category_map = {'cat': 0, 'dog': 1}
# サブディレクトリを反復処理
for subdir in os.listdir(data_dir):
folder_path = os.path.join(data_dir, subdir)
# このカテゴリのラベルを取得
if subdir not in category_map:
continue
label = category_map[subdir]
# サブディレクトリ内の各画像を反復処理
for img in os.listdir(folder_path):
image_path = os.path.join(folder_path, img)
# NumPy配列として画像を読み込む
image_array = cv2.imread(image_path)
# 標準サイズにリサイズ
resized_img = cv2.resize(image_array, (IMG_WIDTH, IMG_HEIGHT))
# 配列に追加
labels.append(label)
images.append(resized_img)
return (images, labels)
学習を開始する前に、エポック数を定義する必要があります。
EPOCHS = 10
エポック(Epoch)とは、モデルが学習データ全体を1回学習することを指します。エポック数が10の場合、モデルはすべての学習データを10回繰り返し学習します。エポック数が少なすぎるとモデルが十分に学習できず、多すぎると過学習(オーバーフィッティング)を引き起こす可能性があります。最初は10程度から始めて、結果を見ながら調整することをお勧めします。
モデルの学習とテスト
いよいよ本題です。実際にデータを使ってモデルを学習させ、結果を評価してみましょう。
まず、x_train と y_train の 2 つのデータセットを用意します。
x_train はモデルの学習に使用するデータで、y_train は人手で用意した正解データです。
x_trainとy_trainを比較し、精度を確認します。
model.fit(x_train, y_train, epochs=EPOCHS, validation_split=0.2)
最後に, 実装して結果を確認する
model.evaluate(x_test, y_test, verbose=2)
Python ファイルを実行すると、テスト結果がターミナルに表示されます。
最初の実行では精度はあまり高くなく、約 67.9%(ターミナル上では 0.6790)でした。しかし、学習を繰り返してデータから学習していくことで、最終的には 98.77% まで精度が向上しました。
精度をさらに向上させる
98.77% という精度は十分に高いですが、さらに改善することも可能です。
過学習や学習不足を避けるために、レイヤーの種類や数の最適な組み合わせを見つけるには試行錯誤が必要です。
さまざまなレイヤー構成や数を試しながら、どこまで精度を高められるか挑戦してみてください。
達成できた精度と、そのために行った工夫をぜひコメントで共有してください。


