- AI ⚙️
CNNとは|畳み込みニューラルネットワークの仕組みをnumpyで自作し実測
結論
CNN(Convolutional Neural Network、畳み込みニューラルネットワーク)とは、小さなフィルタを画像の上で少しずつずらしながら掛け算と足し算をする「畳み込み層」と、近くの値をまとめる「プーリング層」を重ねた、画像に強いニューラルネットワークです。同じフィルタを画像のどこでも使い回すので、全結合だけのネットワークよりパラメータが少なく、ものの位置が少しずれても同じ特徴を拾えます。
この記事でわかること
- 畳み込み・ストライド・パディング・プーリングの仕組みと出力サイズの式を、手書き数字に手作りのフィルタをかけた数値で確かめる方法
- numpy で順伝播と逆伝播を自作し、SciPy の結果との一致(誤差 5e-15)と数値微分による勾配の確認(相対誤差 1e-8 前後)を出す手順と、確認でつまずく2つの落とし穴
- load_digits(8×8)で学習した小さな CNN(1,370 パラメータ)と、全結合のネットワーク・ロジスティック回帰の精度・パラメータ数・ずらしへの強さの実測
この記事で実際に動かしたもの:numpy だけで書いた畳み込み層(im2col)・最大/平均プーリング・全結合層・softmax と、その逆伝播。SciPy の correlate2d との一致の確認、数値微分による勾配の確認、scikit-learn 同梱の手書き数字(load_digits)での学習と評価です。PyTorch・TensorFlow・GPU は使っていません。
flowchart TD
A[入力 8x8 の手書き数字] --> B[畳み込み 3x3 のフィルタ 8枚]
B --> C[ReLU 負の値を0にする]
C --> D[最大プーリング 2x2 で 4x4 に]
D --> E[平らにする 8x4x4 = 128個]
E --> F[全結合 128 から 10]
F --> G[softmax で 0〜9 の確率]
よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| CNN とは | 畳み込み層とプーリング層を重ね、最後に全結合層で分類する、画像に強いニューラルネットワーク | CNNとは |
| 畳み込み ニューラルネットワーク の仕組み | 小さなフィルタ(例 3×3)を画像の上でずらし、重なった画素とフィルタの積の和を1つの値にする。フィルタは学習で決まる | 畳み込みの仕組み |
| 畳み込みの出力サイズ | (入力 + 2×パディング − フィルタ)÷ ストライド + 1(切り捨て)。8×8・3×3・パディング1・ストライド1 なら 8×8 | 畳み込みの仕組み |
| プーリング とは | 2×2 などの範囲を最大値(最大プーリング)や平均(平均プーリング)の1つにまとめ、縦横を半分にする処理。学習するパラメータは無い | プーリングとは |
| CNN が画像に強い理由 | 局所的な結合・重みの共有・ずらしに対する等変性の3つ。手元では1画素ずらした数字で、CNN 59.6%・全結合 45.1% | ずらしに強い理由を実測 |
| CNN のパラメータ数の計算 | 畳み込み層は(フィルタ縦×横×入力チャンネル+1)×フィルタ数。画像の大きさに依らない | CNNの構造とパラメータ数 |
| CNN を python で自作できるか | numpy で順伝播・逆伝播とも書ける。この記事のコードで load_digits の正解率 98.5% | numpyでCNNを自作する |
| CNN と RNN・Transformer の違い | CNN は近くの画素の関係、RNN は順番、Transformer は全体の組み合わせ(注意)を得意にする | CNNの歴史と使われ方 |
確認環境:Ubuntu 24.04.5 LTS(Intel Xeon 2.10GHz・2コア・メモリ8GB の共有のマシン)で、Python 3.13.18・numpy 2.5.3(OpenBLAS 0.3.34、OPENBLAS_NUM_THREADS=1)・SciPy 1.18.1・scikit-learn 1.9.1 を使いました。データは scikit-learn 同梱の load_digits(8×8・1,797枚、値は0〜16)だけで、ネットからは何も取得していません。訓練1,347枚・テスト450枚に層化して分け(random_state=0)、numpy のモデルは乱数の種3通りの平均と標準偏差です。学習の合計は118秒、プロセスの最大メモリは165MBでした。畳み込みの定義と出力サイズの式は PyTorch 2.15・SciPy 1.18 の公式ドキュメント、考え方は『Deep Learning』9章と CS231n の講義ノート、歴史は原論文で確かめました。図は mermaid で描画を確かめています(確認日 2026年11月5日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CNNとは|畳み込みニューラルネットワークの仕組みをnumpyで自作し実測」
CNNとは(畳み込みニューラルネットワークを1行で)
この節では、CNN という言葉の意味と、ふだんの言葉との違い、ほかのニューラルネットワークとの関係が分かります。
CNN は Convolutional Neural Network の頭文字で、日本語では畳み込みニューラルネットワークと呼びます。ニューラルネットワーク(入力に重みを掛けて足し、非線形の関数を通す層を重ねたモデル)の一種で、ディープラーニングの代表的な形の1つです。画像の分類・物体の検出・医用画像の解析・製品の外観検査などで長く使われてきました。
CNN・畳み込み・プーリングは、どれもふだんの言葉と同じ字を使うため、検索するとまったく別の意味の結果が混じります。最初に並べておきます。
| 言葉 | ふだんの意味 | 機械学習(IT)での意味 |
|---|---|---|
| CNN | 米国のニュース放送局の名前 | 畳み込みニューラルネットワーク(Convolutional Neural Network) |
| 畳み込み | 布団や紙を折りたたむこと | 小さなフィルタを入力の上でずらしながら、重なった部分の積の和をとる計算(convolution) |
| フィルタ(カーネル) | コーヒーや空気をこす道具 | 畳み込みで使う小さな重みの表(例 3×3 の9個の数)。学習で値が決まる |
| プーリング | お金や車を共同で持ち寄ること(相乗りなど) | 近くの値を最大値や平均にまとめて、縦横を小さくする処理(pooling) |
| チャンネル | テレビの局の番号 | 画像の色の層(RGB の3つ)や、フィルタごとの出力の枚数 |
| ストライド | 大またで歩く一歩の幅 | フィルタを1回にずらす画素の数 |
CNN が画像に強い理由は、次の3つの考え方にまとめられます(Goodfellow ほか『Deep Learning』9.2節の整理)。この記事では、3つとも手元の数値で確かめます。
1局所的な結合
出力の1つは、入力の小さな範囲(3×3 なら9画素)だけを見る。線・角・点といった近くの画素の関係を取り出しやすい。
2重みの共有
同じフィルタを画像のどこでも使う。パラメータは画像の大きさに依らず、3×3 のフィルタ1枚なら重み9個とバイアス1個だけ。
3ずらしに対する等変性
入力を1画素ずらすと、畳み込みの出力もそのまま1画素ずれる。プーリングを重ねると、小さなずれには値そのものが変わりにくくなる。
ニューラルネットワーク全体の仕組み(重み・活性化関数・誤差逆伝播・Attention)は、関連記事「ニューラルネットワークとは|仕組み・学習・AttentionをNumPyで実演」で扱っています。この記事は CNN の層の中身・学習・比較の実測に絞ります。
最初の5分:畳み込みとプーリングを動かしてみる
この節では、numpy と SciPy だけで、手書き数字に畳み込みとプーリングをかける最小の手順と、その結果の数値が分かります。
scikit-learn と SciPy が入っていれば、次の10行がそのまま動きます。load_digits().images[7] は数字の「7」の 8×8 の画像で、値は0(白)〜16(黒)です。sobel_x は、左と右の明るさの差に反応する(縦の線を見つける)有名なフィルタです。
import numpy as np
from scipy.signal import correlate2d
from sklearn.datasets import load_digits
img = load_digits().images[7] # 8x8、値は0〜16。ラベルは7
sobel_x = np.array([[-1, 0, 1],
[-2, 0, 2],
[-1, 0, 1]]) # 縦の線(左右の明るさの差)に反応
out = correlate2d(img, sobel_x, mode="same") # CNN の「畳み込み」はこれ
relu = np.maximum(out, 0)
pool = relu.reshape(4, 2, 4, 2).max(axis=(1, 3)) # 2x2 最大プーリング
print(out.astype(int)); print(pool.astype(int))
実行した出力です(上が畳み込みの結果 8×8、下が ReLU と最大プーリングのあとの 4×4)。
[[ 0 21 23 9 20 12 -41 -42]
[ 0 21 22 8 29 11 -50 -40]
[ 4 15 11 20 37 -15 -52 -20]
[ 10 27 21 26 16 -40 -47 -13]
[ 8 30 46 20 -31 -44 -23 -6]
[ 2 20 60 6 -58 -26 -4 0]
[ 0 31 51 -24 -51 -7 0 0]
[ 0 35 25 -34 -25 -1 0 0]]
[[21 23 29 0]
[27 26 37 0]
[30 60 0 0]
[35 51 0 0]]
正の大きな値(60・51 など)は「左が白く右が黒い」縦の境目、負の値はその逆で、「7」の斜めの線の左側に正、右側に負が並びます。プーリングのあとは左下に大きな値が残りました。CNN は、このフィルタの9個の数を人が決めるのではなく、正解のラベルに合うよう学習で決めます。ここから先は、この計算を1つずつ分解し、学習まで自作します。
畳み込みの仕組み(フィルタ・ストライド・パディング・出力サイズ)
この節では、畳み込みの1回の計算、ストライドとパディングの意味、出力サイズの式、CNN の畳み込みが数学の畳み込みと少し違う点が分かります。
5×5 の入力に 3×3 のフィルタを、パディング無し・ストライド1でかける例で、1つの出力ができるまでを追います。
フィルタを入力の左上に重ねる
入力の左上 3×3 は [[1,2,0],[0,1,3],[2,1,0]]、フィルタは縦の境目用の [[1,0,−1],[1,0,−1],[1,0,−1]]。
同じ位置どうしを掛けて全部足す
(1+0+2) − (0+3+0) = 0。これが出力の左上の1つ。バイアスがあれば最後に足す。
ストライドの分だけ右へずらして繰り返す
ストライド1なら1画素ずつ。右端まで行ったら1行下の左端へ戻る。
必要なら活性化関数を通す
CNN では ReLU(負を0にする)がよく使われる。出力の 3×3 の表を「特徴マップ」と呼ぶ。
CNN の「畳み込み」は、数学では相互相関(cross-correlation)と呼ばれる計算です。数学の畳み込みはフィルタを上下左右に反転してから重ねます。SciPy の2つの関数で同じ入力を計算すると、結果の符号がちょうど反対になりました(このフィルタは左右を反転すると符号が変わるため)。
自作 conv valid:
[[ 0. 0. -1.]
[-1. -4. 1.]
[ 1. -2. -2.]]
scipy correlate2d:
[[ 0. 0. -1.]
[-1. -4. 1.]
[ 1. -2. -2.]]
scipy convolve2d (数学の畳み込み、フィルタ反転):
[[ 0. 0. 1.]
[ 1. 4. -1.]
[-1. 2. 2.]]
PyTorch の Conv2d の公式ドキュメントも、この演算を「valid 2D cross-correlation」と説明しています。『Deep Learning』9章も、多くのライブラリが反転を省いた相互相関を畳み込みと呼んでいると書いています。フィルタは学習で決まるため、反転の有無は学習結果の向きが変わるだけで、性能の差にはなりません。自作するときは、SciPy の correlate2d と比べるのが正しい確かめ方です。
ストライドとパディング、出力サイズの式
パディングは入力の外側を0などで囲むことで、端の画素もフィルタの中心に来るようにし、出力を小さくしすぎないために使います。ストライドはフィルタをずらす幅で、2にすると出力の縦横がほぼ半分になります。出力の縦(横も同じ)は次の式で決まります。
出力 = floor((入力 + 2 × パディング − フィルタ) ÷ ストライド) + 1
PyTorch 2.15 の公式ドキュメントの式は、これにダイレーション(フィルタの間を空ける幅)の項が入った形です(ダイレーション1のとき同じ)。自作の畳み込みに0の配列を通し、実際の出力の形と式を比べました。8通りすべて一致しています。
| 入力 | フィルタ | ストライド | パディング | 出力(実測=式) |
|---|---|---|---|---|
| 5×5 | 3×3 | 1 | 0 | 3×3 |
| 5×5 | 3×3 | 1 | 1 | 5×5(入力と同じ大きさ。same と呼ぶ) |
| 8×8 | 3×3 | 1 | 1 | 8×8(この記事の CNN) |
| 8×8 | 3×3 | 2 | 1 | 4×4 |
| 28×28 | 5×5 | 1 | 0 | 24×24 |
| 32×32 | 3×3 | 2 | 1 | 16×16 |
| 224×224 | 7×7 | 2 | 3 | 112×112(ResNet の最初の層の形) |
| 7×7 | 2×2 | 2 | 0 | 3×3(端の1列は切り捨て) |
3×3・パディング1・ストライド1 では大きさが変わらないため、多くの CNN はこれを基本にし、小さくする役をプーリングかストライド2の畳み込みに任せます。
手作りのフィルタで手書き数字がどう変わるか
この節では、エッジ検出やぼかしのフィルタを同じ「7」にかけたとき、特徴マップの数値がどう変わるかと、CNN が何を学習しているのかの手がかりが分かります。
元の画像(load_digits の7番、ラベル7)の値です。0 が白、16 が最も濃い黒です。
[[ 0 0 7 8 13 16 15 1]
[ 0 0 7 7 4 11 12 0]
[ 0 0 0 0 8 13 1 0]
[ 0 4 8 8 15 15 6 0]
[ 0 2 11 15 15 4 0 0]
[ 0 0 0 16 5 0 0 0]
[ 0 0 9 15 1 0 0 0]
[ 0 0 13 5 0 0 0 0]]
この画像に、4つの手作りのフィルタをパディング1でかけ、値の範囲・正になった画素の数・ReLU のあとの合計を比べました。
| フィルタ(3×3) | 反応するもの | 最小〜最大 | 正の画素(64中) | ReLU 後の合計 |
|---|---|---|---|---|
| 縦エッジ(Sobel x)[[−1,0,1],[−2,0,2],[−1,0,1]] | 左右の明るさの差(縦の線) | −58〜60 | 32 | 697 |
| 横エッジ(Sobel y)[[−1,−2,−1],[0,0,0],[1,2,1]] | 上下の明るさの差(横の線) | −46〜48 | 20 | 390 |
| 斜め [[0,1,2],[−1,0,1],[−2,−1,0]] | 右上がりの境目 | −49〜48 | 27 | 512 |
| ぼかし(9個とも1/9) | 近くの平均(細部を消す) | 0〜10.3 | 55 | 264 |
横エッジのフィルタの結果では、1行目(7 の上の横棒の上側)に 21〜38 の正の値が並び、すぐ下の2行目に −22〜−32 の負の値が並びました。横棒の「上の境目」と「下の境目」が、別の符号の帯として分かれて見えます。最大プーリングのあとの 4×4 は次のとおりで、上半分に値が集まり、下半分の3行目は0でした。
横エッジ → ReLU → 最大プーリング
[[ 7 25 38 35]
[15 48 27 0]
[ 0 0 0 0]
[13 15 0 0]]
縦エッジの合計(697)が横エッジ(390)より大きいのは、「7」の斜めの線の左右の境目が縦エッジにも反応するためです。CNN の最初の層では、このような線・境目・明暗の向きに反応するフィルタが学習でできることが多く、後半の節で学習したフィルタの中身を実際に見ます。
プーリングとは(最大プーリング・平均プーリング・全体プーリング)
この節では、プーリングの計算、最大と平均の違い、学習するパラメータが無いこと、全体プーリング(グローバルプーリング)が分かります。
プーリングは、特徴マップを小さな範囲(多くは 2×2・ストライド2)に区切り、それぞれを1つの値にまとめる処理です。縦横が半分になり、値の数は4分の1になります。チャンネル(フィルタの枚数)は変わりません。4×4 の例で計算しました。
入力 4x4
[[1 3 2 0]
[4 2 1 1]
[0 1 5 6]
[2 2 7 1]]
max pool: avg pool:
[[4. 2.] [[2.5 1. ]
[2. 7.]] [1.25 4.75]]
最大プーリング(max pooling)
範囲の中で最も大きい値を残す
「この辺りに特徴があったか」を残し、正確な位置は捨てる
逆伝播では、最大だった1か所にだけ勾配を流す
手元の学習では正解率 98.5%
平均プーリング(average pooling)
範囲の平均を残す
弱い反応も薄めて残す。ノイズもならされる
逆伝播では、4か所に4分の1ずつ勾配を流す
手元の学習では正解率 97.2%
プーリングに学習するパラメータはなく、役目は計算量を減らすことと、小さなずれで出力を変わりにくくすることです。CS231n の講義ノートは 2×2・ストライド2 が最も一般的としています。
全体プーリング(グローバル平均プーリング・グローバル最大プーリング)は、特徴マップ全体を1つの値にまとめるものです。チャンネル数だけの値が残り、そのまま全結合層に渡します。ResNet などの近年の CNN は、最後の全結合層の前にグローバル平均プーリングを置き、パラメータを大きく減らしています。後の節で、全体最大プーリングにすると1画素のずれに対して特徴がまったく変わらなかった実測を示します。
CNNの構造とパラメータ数(全結合だけとの比較)
この節では、CNN の層の並びと各層での配列の形、パラメータ数の計算方法、全結合だけのネットワークとの差が分かります。
この記事で学習する CNN は、冒頭の図のとおり「畳み込み(3×3 のフィルタ8枚・パディング1)→ ReLU → 最大プーリング 2×2 → 平らにする → 全結合 → softmax」です。2枚の画像を入れたときの配列の形を、自作のコードで順に出しました。形は(枚数, チャンネル, 縦, 横)です。
形 (2, 1, 8, 8) → (2, 8, 8, 8) → (2, 8, 4, 4) → (2, 128) → (2, 10)
カラー画像のように入力が複数のチャンネルを持つ場合、フィルタも入力のチャンネル数だけの厚みを持ちます。RGB の画像に 3×3 のフィルタをかけると、1枚のフィルタは 3×3×3=27個の重みを持ち、3つのチャンネルの積の和を合わせて1枚の特徴マップを出します。PyTorch の Conv2d の重みの形が(出力チャンネル, 入力チャンネル, 縦, 横)になっているのはこのためです。自作の畳み込みでも、3チャンネル・5枚のフィルタの結果を SciPy の correlate2d をチャンネルごとに足したものと比べ、最大の差は 5.3e-15(浮動小数点の丸めの範囲)でした。
パラメータ数の計算
畳み込み層のパラメータ数は (フィルタ縦 × 横 × 入力チャンネル + 1)× フィルタ数、全結合層は (入力の数 + 1)× 出力の数 です(+1 はバイアス)。式で計算した値です。
| 層 | 計算 | パラメータ数 |
|---|---|---|
| この記事の畳み込み(1→8、3×3) | (9×1+1)×8 | 80 |
| この記事の全結合(128→10) | (128+1)×10 | 1,290(CNN 全体で1,370) |
| 比べる全結合のネットワーク(64→20→10) | (64+1)×20+(20+1)×10 | 1,510 |
| 28×28 の画像 → 全結合128 | (784+1)×128 | 100,480 |
| 28×28 の画像 → 3×3 の畳み込み32枚 | (9×1+1)×32 | 320(画像の大きさに依らない) |
| 224×224×3 の画像 → 全結合1,000 | (150,528+1)×1,000 | 150,529,000 |
| 224×224×3 の画像 → 3×3 の畳み込み64枚 | (27+1)×64 | 1,792 |
| 3×3、64→128 チャンネル | (9×64+1)×128 | 73,856 |
| 5×5、64→128 チャンネル | (25×64+1)×128 | 204,928 |
224×224 のカラー画像を全結合の1,000個に直接つなぐと約1.5億個のパラメータになりますが、畳み込みなら1,792個です。CS231n の講義ノートも、AlexNet の最初の層で重みを共有しなければ約1億600万個になるところが、共有で34,944個になる例を挙げています。ただし計算の回数は画素の数に比例するため、「パラメータが少ない=計算が軽い」ではありません。
3×3 を重ねる
3×3 を2層重ねると 5×5、3層で 7×7 の範囲を見る。5×5 を1層使うより、パラメータが少なく、間に ReLU が入る分だけ表現力が上がる。
VGG 以降の定番の設計
1×1 の畳み込み
縦横には広がらず、チャンネルどうしだけを混ぜる。256→64 チャンネルの1×1 は16,448個のパラメータで、チャンネル数を減らす役に使う。
ResNet のボトルネックなど
受容野
出力の1つが見ている入力の範囲。逆伝播で勾配が0でない範囲を数えると、3×3 を1・2・3層で 3×3・5×5・7×7、間に 2×2 プーリングを挟むと2層で 8×8。
深いほど広い範囲を見る
numpyでCNNを自作する(im2colで畳み込みを行列の積にする)
この節では、畳み込みを numpy で速く書く im2col の考え方と、この記事で使った順伝播のコード、素朴なループとの速さの差が分かります。
畳み込みを4重のループで書くと、Python では遅くなります。im2col は、フィルタが重なる各位置の 3×3 の窓を1行ずつに並べた行列を作り、フィルタとの積の和を1回の行列の積で計算する方法です。numpy の sliding_window_view を使うと、窓の行列をコピー無しで作れます。この記事のすべての実験は、次のコードで動かしました。
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
def im2col(x, k, s=1, p=0):
N, C, H, W = x.shape
xp = np.pad(x, ((0, 0), (0, 0), (p, p), (p, p)))
Ho = (H + 2 * p - k) // s + 1
Wo = (W + 2 * p - k) // s + 1
win = sliding_window_view(xp, (k, k), axis=(2, 3))[:, :, ::s, ::s][:, :, :Ho, :Wo]
cols = win.transpose(0, 2, 3, 1, 4, 5).reshape(N * Ho * Wo, C * k * k)
return cols, Ho, Wo
def conv_forward(x, Wt, b, s=1, p=0):
F, C, k, _ = Wt.shape
cols, Ho, Wo = im2col(x, k, s, p)
out = cols @ Wt.reshape(F, -1).T + b
out = out.reshape(x.shape[0], Ho, Wo, F).transpose(0, 3, 1, 2)
return out, (x.shape, cols, Ho, Wo, s, p)
def pool_forward(x, mode="max"):
N, C, H, W = x.shape
r = x.reshape(N, C, H // 2, 2, W // 2, 2).transpose(0, 1, 2, 4, 3, 5).reshape(N, C, H // 2, W // 2, 4)
if mode == "avg":
return r.mean(-1), (x.shape, None)
idx = r.argmax(-1)
out = np.take_along_axis(r, idx[..., None], -1)[..., 0]
return out, (x.shape, idx)
最大プーリングで argmax を使い、最大だった位置を1つだけ覚えているのは、逆伝播のためです。値が同じ(同点)の位置が2つ以上あるときの扱いが、後の勾配の確認で問題になります。
200枚の 8×8 の画像に8枚のフィルタをかける計算で、4重ループと im2col を比べました。
im2col は入力の約9倍(3×3 のとき)のメモリを使うため、大きな画像では問題になることがあり、深層学習のライブラリは別の計算方法(Winograd・FFT など)も使い分けています。配列の reshape・transpose・ブロードキャストの基本は、関連記事「NumPyの使い方 基本一覧|配列の作成・演算・形状変更」で扱っています。
逆伝播を自作し、数値微分で勾配を確かめる
この節では、畳み込み層とプーリング層の逆伝播のコードと、数値微分による勾配の確認の方法、確認で実際につまずいた2つの落とし穴(ReLU の折れ目と最大プーリングの同点)が分かります。
学習では、損失(予測と正解のずれ)を各重みで微分した勾配を求め、重みを少しずつ動かします。畳み込み層の逆伝播は、順伝播が行列の積なので、行列の積の微分と同じ形になります。
- 重みの勾配:出力の勾配(位置ごとに並べたもの)の転置と im2col の行列の積。同じフィルタを全位置で使っているので、全位置の分が足し合わされる
- バイアスの勾配:出力の勾配を全位置・全画像で足したもの
- 入力の勾配:出力の勾配とフィルタの積を、窓の位置に戻して重なる分を足す(col2im)
def col2im(dcols, xshape, k, s, p, Ho, Wo):
N, C, H, W = xshape
d = dcols.reshape(N, Ho, Wo, C, k, k)
dxp = np.zeros((N, C, H + 2 * p, W + 2 * p))
for i in range(k):
for j in range(k):
dxp[:, :, i:i + s * Ho:s, j:j + s * Wo:s] += d[:, :, :, :, i, j].transpose(0, 3, 1, 2)
return dxp[:, :, p:p + H, p:p + W]
def conv_backward(dout, Wt, cache):
xshape, cols, Ho, Wo, s, p = cache
F, C, k, _ = Wt.shape
dr = dout.transpose(0, 2, 3, 1).reshape(-1, F)
dW = (dr.T @ cols).reshape(Wt.shape)
db = dr.sum(0)
dx = col2im(dr @ Wt.reshape(F, -1), xshape, k, s, p, Ho, Wo)
return dx, dW, db
def pool_backward(dout, cache, mode="max"):
xshape, idx = cache
N, C, H, W = xshape
if mode == "avg":
g = np.repeat(dout[..., None], 4, -1) / 4
else:
g = np.zeros(dout.shape + (4,))
np.put_along_axis(g, idx[..., None], dout[..., None], -1)
return g.reshape(N, C, H // 2, W // 2, 2, 2).transpose(0, 1, 2, 4, 3, 5).reshape(xshape)
数値微分による勾配の確認(勾配チェック)は、重みを1つだけ ±eps 動かして損失の差を測り、(L(w+eps) − L(w−eps)) ÷ (2×eps) を逆伝播の値と比べる方法です。比べる尺度は相対誤差 |数値 − 解析| ÷ (|数値| + |解析|) を使い、float64 で 1e-7 より小さければ正しいと判断するのが目安です。フィルタ4枚の CNN に16枚の画像を入れ、各パラメータと入力から最大30個を選んで確かめました。
eps = 1e-5
for t, i in enumerate(picks):
o = flat[i]
flat[i] = o + eps; Lp, _ = m.loss(xb, yb)
flat[i] = o - eps; Lm, _ = m.loss(xb, yb)
flat[i] = o
num[t] = (Lp - Lm) / (2 * eps)
rel = np.abs(num - ana) / np.maximum(1e-8, np.abs(num) + np.abs(ana))
最初は、バイアスを0で始めた状態で確かめ、バイアスの勾配が合いませんでした。実行した出力の原文です。
[max b1=0.0] loss 2.300557
ちょうど0の a1 の個数 516 / 4096
W1 個数 30 最大相対誤差 4.65e-09 中央値 1.56e-10 例 解析-0.014056 数値-0.014056
b1 個数 4 最大相対誤差 4.24e-01 中央値 1.30e-01 例 解析+0.030049 数値+0.041200
W2 個数 30 最大相対誤差 1.76e-08 中央値 6.50e-10 例 解析-0.008261 数値-0.008261
b2 個数 10 最大相対誤差 3.20e-09 中央値 2.10e-10 例 解析-0.042102 数値-0.042102
x 個数 30 最大相対誤差 1.00e+00 中央値 1.05e-09 例 解析+0.003849 数値+0.003849
[max b1=0.05] loss 2.312775
ちょうど0の a1 の個数 0 / 4096
W1 個数 30 最大相対誤差 2.11e-08 中央値 1.17e-10 例 解析-0.014447 数値-0.014447
b1 個数 4 最大相対誤差 7.19e-11 中央値 2.42e-11 例 解析+0.058775 数値+0.058775
W2 個数 30 最大相対誤差 9.46e-09 中央値 6.62e-10 例 解析-0.011196 数値-0.011196
b2 個数 10 最大相対誤差 4.45e-09 中央値 1.54e-10 例 解析-0.043231 数値-0.043231
x 個数 30 最大相対誤差 1.00e+00 中央値 1.25e-09 例 解析+0.000817 数値+0.000817
[avg b1=0.05] loss 2.299516
ちょうど0の a1 の個数 0 / 4096
W1 個数 30 最大相対誤差 7.58e-09 中央値 2.70e-10 例 解析-0.005173 数値-0.005173
b1 個数 4 最大相対誤差 2.99e-10 中央値 1.40e-10 例 解析+0.040527 数値+0.040527
W2 個数 30 最大相対誤差 1.75e-08 中央値 1.49e-09 例 解析+0.015157 数値+0.015157
b2 個数 10 最大相対誤差 1.43e-09 中央値 2.38e-10 例 解析-0.028190 数値-0.028190
x 個数 30 最大相対誤差 1.31e-07 中央値 2.76e-09 例 解析-0.000355 数値-0.000355
落とし穴1:ReLU の折れ目。手書き数字は白(0)の画素が多く、0ばかりの 3×3 の窓では、バイアス0のとき畳み込みの出力がちょうど0になります(4,096個中516個)。ReLU は0で折れ曲がっているため、バイアスを +eps 動かすとその516か所が一斉に正になり、−eps では0のままです。数値微分は左右の傾きの平均を出すので、逆伝播の値と合いません。コードではなく確かめる点の選び方の問題で、バイアスを0.05で始めると b1 の相対誤差は 7.19e-11 になりました。
落とし穴2:最大プーリングの同点。バイアスを変えても、最大プーリングでは入力 x の勾配がまだ合いませんでした。0ばかりの窓の出力はどこも同じ値(バイアスと同じ)になり、2×2 の中に最大値が2つ以上ある窓が1,024個中185個ありました。入力を少し動かすと最大の位置が入れ替わり、傾きが左右で変わります。全1,024個の入力のうち219個で相対誤差が 1e-5 を超え、最悪の1つは右側の差分 +0.002364・左側の差分 −0.006678 と向きまで違いました。同点の出ない乱数の入力では、1,024個すべてが 4.2e-07 以下でした。平均プーリングは同点が無く、手書き数字のままで合っています。
勾配チェックの入力の選び方
手元では、同点の出ない入力ですべて 1e-7 以下になった
間違いを見つけられるかの確認
180度回した勾配は最大相対誤差 1.00、中央値 0.495 で、はっきり不一致と出た
eps の大きさと float32
eps は大きすぎると曲がり具合(2次の項)の誤差が、小さすぎると引き算の桁落ちの誤差が出ます。同じ1つの重みで eps を変えた結果です。
| eps | 数値微分 | 逆伝播 | 相対誤差 |
|---|---|---|---|
| 1e-1 | +0.0247032299 | +0.0209457527 | 8.2e-02 |
| 1e-3 | +0.0209457545 | +0.0209457527 | 4.2e-08 |
| 1e-5 | +0.0209457527 | +0.0209457527 | 1.4e-10 |
| 1e-7 | +0.0209457518 | +0.0209457527 | 2.1e-08 |
| 1e-9 | +0.0209454676 | +0.0209457527 | 6.8e-06 |
| 1e-11 | +0.0209388062 | +0.0209457527 | 1.7e-04 |
float64 では eps=1e-5 前後が最もよく合いました。同じ確認を float32 で行うと、eps=1e-3 で 8.4e-04、1e-5 では 6.5e-02 と、正しいコードでも大きくずれます。勾配チェックは float64 で行うのが確実です。
手書き数字(load_digits)で学習:CNN・全結合・ロジスティック回帰を比べる
この節では、自作の CNN を手書き数字で学習した結果と、全結合だけのネットワーク・ロジスティック回帰との精度・パラメータ数・学習時間の比較が分かります。
モデルの損失と勾配は次のコードです。重みの更新は Adam(学習率0.01・ミニバッチ32枚・30エポック)を自作し、全結合のネットワークも同じ条件で学習しました。入力は 0〜16 を16で割って 0〜1 にしています。
class CNN:
"""入力 1x8x8 → 畳み込み(F枚,3x3,pad1) → ReLU → 最大プーリング2x2 → 全結合10"""
def __init__(self, F=8, seed=0, pool="max"):
g = np.random.default_rng(seed)
self.pool = pool
self.p = {
"W1": g.normal(0, np.sqrt(2 / 9), (F, 1, 3, 3)), "b1": np.zeros(F),
"W2": g.normal(0, np.sqrt(1 / (F * 16)), (F * 16, 10)), "b2": np.zeros(10),
}
def loss(self, x, y, need_dx=False):
P = self.p
a1, c1 = conv_forward(x, P["W1"], P["b1"], 1, 1)
h1 = np.maximum(a1, 0)
p1, cp = pool_forward(h1, self.pool)
f = p1.reshape(len(x), -1)
z = f @ P["W2"] + P["b2"]
L, dz = softmax_ce(z, y)
g = {"W2": f.T @ dz, "b2": dz.sum(0)}
dp = (dz @ P["W2"].T).reshape(p1.shape)
dh = pool_backward(dp, cp, self.pool)
da = dh * (a1 > 0)
dx, g["W1"], g["b1"] = conv_backward(da, P["W1"], c1)
if need_dx: g["x"] = dx
return L, g
テスト450枚での結果です(numpy と sklearn の MLP は乱数の種3通りの平均±標準偏差。ロジスティック回帰は乱数に依らない)。「ずらし」の列は、テスト画像を上下左右に1画素(または2画素)ずらし、空いたところを0で埋めた4通りの平均です。
モデル パラメータ 元の精度 1pxずらし 2pxずらし 1回の学習秒
sklearn ロジスティック回帰 650 96.9±0.0 42.7±0.0 8.4±0.0 0.15
sklearn MLP 隠れ20 1510 97.2±0.4 45.1±1.8 9.8±1.3 2.52
numpy MLP 隠れ20 1510 96.2±0.8 45.1±2.1 11.5±0.8 0.45
numpy MLP 隠れ100 7510 97.6±0.6 47.9±0.2 11.3±0.4 0.65
numpy CNN 4枚 690 96.8±0.9 54.8±5.8 15.5±4.3 2.59
numpy CNN 8枚 1370 98.5±0.4 59.6±3.9 14.7±0.9 4.54
numpy CNN 8枚 平均プーリング 1370 97.2±0.1 50.9±2.1 14.2±1.0 3.61
numpy MLP 隠れ20+ずらし拡張 1510 94.3±1.1 92.7±0.9 32.3±1.8 2.53
numpy CNN 8枚+ずらし拡張 1370 98.4±0.4 95.4±0.2 41.7±1.2 22.30
読み取れることは4つです。
- 同じくらいのパラメータ数(約1,400〜1,500)で、CNN は全結合より1〜2ポイント高い。全結合を5倍の7,510個に増やしても 97.6%で、CNN 8枚の 98.5%に届きませんでした
- 8×8 の小さな画像では、どのモデルも96%以上になり、差は大きくありません。ロジスティック回帰(650個)でも96.9%です。CNN の利点は、画像が大きく位置がばらつくほど大きくなります
- 自作の numpy の MLP は sklearn の MLPClassifier と近い精度(96.2%と97.2%、1画素ずらしは同じ45.1%)で、自作の学習が大きく外れていない確認になります(sklearn は既定で L2 正則化が入るため一致はしません)
- 学習時間は CNN が長い。全結合 0.45 秒に対し CNN は 4.5 秒でした
訓練データの損失(交差エントロピー)は、30エポックで CNN 8枚が 1.282→0.010、平均プーリングの CNN が 1.295→0.033、MLP 隠れ100 が 0.839→0.001 まで下がりました(種0)。MLP 隠れ100 は訓練の損失が最も小さいのにテストの精度は CNN より低く、過学習に近い状態と読めます。
学習したフィルタの中身
学習後の CNN 8枚(種0)の 3×3 のフィルタを10倍して丸めた値の一部です。
1 [[-6, -3, 0], [-11, -1, -6], [-3, -3, -1]] b -0.06
3 [[-11, -11, 3], [-20, -2, 7], [20, 16, -1]] b 0.02
5 [[0, 12, 9], [19, 11, 9], [-11, -8, 1]] b -0.13
7 [[3, 3, -15], [12, 24, 8], [-24, 10, 10]] b -0.01
フィルタ3は、上の行が負・下の行が正で、横エッジ(上が白く下が黒い境目)に近い形です。フィルタ5は逆に上が正・下が負です。人が作った Sobel のようにきれいな形ではありませんが、境目の向きに反応するフィルタが学習でできていました。
フィルタ1は9個の重みがすべて0以下で、バイアスも −0.06 です。入力の画素は0以上なので、出力は常に負になり、ReLU のあとは全画像・全位置で0でした(正になった割合 0.0%。ほかの7枚は 53〜72%)。このフィルタは学習の途中で「死んで」おり、8枚のうち実際に働いているのは7枚です。ReLU の出力が常に0になると勾配も0になり、二度と戻りません。フィルタ4枚の CNN で種によるばらつき(1画素ずらしで±5.8)が大きかったのも、少ない枚数のうち何枚が働くかで結果が変わるためと考えられます。Leaky ReLU にする・学習率を下げる・バイアスを小さな正の値で始める、が一般的な対策です。
ずらしに強い理由を実測(等変性とプーリング)
この節では、「CNN は位置のずれに強い」と言われる理由を、畳み込みとプーリングのそれぞれで数値として確かめた結果と、その限界が分かります。
テスト画像450枚を右へ1画素ずらし、学習済みの CNN(種0)の各段階の特徴が、ずらす前とどれだけ似ているかをコサイン類似度(1なら同じ向き)で測りました。
右端の列の差 1.6572030684773726
畳み込み+ReLU:右端の1列を除いた max|conv(ずらす(x)) - ずらす(conv(x))| = 0.0
画素そのまま cos 0.661
畳み込み+ReLU 8x8x8 cos 0.715 (位置をそろえれば 1.000)
最大プーリング後4x4x8 cos 0.861
平均プーリング後4x4x8 cos 0.845
全体最大プーリング 8 cos 1.000
1画素ずらし: 最大プーリング後の値がずらす前の値と一致する割合(位置を0マスずらして比較) 0.362
2画素ずらし: 最大プーリング後の値がずらす前の値と一致する割合(位置を1マスずらして比較) 0.935
| 段階 | 1画素ずらしたときの似かた | 読み方 |
|---|---|---|
| 画素そのまま(全結合の入力) | 0.661 | 全結合は同じ位置の画素どうしを比べるので、別の画像に見える |
| 畳み込み+ReLU | 0.715(位置をそろえれば 1.000) | 等変性:出力もそのまま1画素ずれるだけ。端の1列以外の差は0.0 |
| 最大プーリング 2×2 のあと | 0.861 | 同じ窓の中のずれは吸収される。値の36.2%がまったく同じ |
| 平均プーリング 2×2 のあと | 0.845 | 最大プーリングより少し弱い |
| 全体最大プーリング | 1.000 | 位置の情報を全部捨てるので、ずれに対して変わらない(不変) |
畳み込みは、ずらした入力に対して、出力もぴったり同じだけずれます(等変性)。差が出たのは、画像の外に押し出された右端の列だけでした。プーリングは小さなずれを吸収しますが、2×2 の窓の境目をまたぐずれでは値が変わります。2画素ずらしでは、プーリング後の位置を1マスそろえると93.5%が同じ値になり、「窓の幅の倍数のずれには強く、半端なずれには弱い」という性質が見えます。
ただし、前の節の表のとおり、1画素ずらすと CNN でも正解率は 98.5%から59.6%に下がりました。8×8 の画像の1画素は幅の8分の1にあたる大きなずれで、層の浅い CNN では吸収しきれません。CNN は「ずれに強い作り」ですが、どんなずれにも強いわけではありません。ずらした画像を訓練データに足す(データ拡張)と、CNN は1画素ずらしで95.4%、全結合でも92.7%まで上がりました。実務の画像認識で、データ拡張(ずらし・反転・回転・拡大縮小)をほぼ必ず使うのはこのためです。なお、全結合は拡張で元の画像の精度が96.2%から94.3%に下がり、CNN は98.4%でほぼ保ちました。
CNNの歴史と使われ方(LeNetからResNet・ViTまで)
この節では、CNN が生まれてから今までの主なモデルと、画像以外も含めた使われ方、RNN・Transformer との違いが分かります。
- 11980年 ネオコグニトロン(福島邦彦)局所的な特徴を取り出す層と、ずれを吸収する層を重ねる考え方。CNN の原型とされる
- 21998年 LeNet-5(LeCun ほか)畳み込みとサブサンプリング(プーリング)を誤差逆伝播で学習。手書き文字の認識に使われた
- 32012年 AlexNet(Krizhevsky ほか)GPU・ReLU・ドロップアウトで ImageNet の分類の誤りを大きく減らした
- 42014年 VGG(Simonyan・Zisserman)3×3 の畳み込みだけを深く重ねる単純な設計
- 52015年 ResNet(He ほか)入力をそのまま足す「スキップ接続」で、100層を超える深さでも学習できるようにした
- 62020年 Vision Transformer(Dosovitskiy ほか)画像を区画に分け、注意(Attention)で扱う。大量のデータで CNN に並ぶ
- 72022年 ConvNeXt(Liu ほか)Transformer の工夫を取り入れた純粋な CNN
使われ方は、画像の分類・物体の検出・領域の分割(画素ごとの分類)・顔の認証・医用画像の診断の補助・外観検査・自動運転のカメラの認識などが代表的です。音声をスペクトログラム(時間×周波数の表)にして CNN にかける方法や、時系列に1次元の畳み込み(Conv1d)を使う方法もあり、画像の生成モデル(拡散モデルの U-Net など)の中にも畳み込み層があります。画像を読み込んでリサイズ・切り抜きする前処理は、関連記事「Python Pillowで画像処理|リサイズ・トリミング・形式変換のコード集」で扱っています。
RNN・LSTM は順番、Transformer は離れた要素どうしの組み合わせ(注意)を得意にし、CNN は近くの値どうしの関係を得意にします。Transformer は構造の仮定が少ない分、少ないデータでは CNN より学習しにくいことが ViT の原論文でも示されています。データが少ない・画像が中心・計算の資源が限られる場面では、今も CNN が第一の候補になります。LLM の仕組みは関連記事「生成AI(LLM)の仕組み8選|ハルシネーション・トークン」で扱っています。
CNNでよくあるエラーとつまずき
この節では、CNN を自作したり使ったりするときに出るエラーの原文と、原因・直し方が分かります。原文はすべて手元で実際に出したものです。
| 症状(エラーの原文) | 原因 | 直し方 |
|---|---|---|
| ValueError: cannot reshape array of size 49 into shape (3,2,3,2) | 7×7 のような奇数の大きさを reshape で 2×2 プーリングしようとした | パディングで偶数にする、端を切り捨てる(7→6)、出力サイズの式で事前に確かめる |
| ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 128 is different from 512) | プーリングを飛ばした・ストライドを変えたなどで、平らにした数(512)と全結合の重み(128)が合わない | 各層の出力の形を print し、全結合の入力数を「チャンネル×縦×横」で計算し直す |
| ValueError: matmul: … (size 9 is different from 27) | 入力は3チャンネルなのに、フィルタの入力チャンネルが1 | フィルタの形を(フィルタ数, 入力チャンネル, 縦, 横)にそろえる |
| ValueError: not enough values to unpack (expected 4, got 2) | 1枚の画像を (8, 8) のまま渡した。チャンネルと枚数の軸が無い | img[None, None] で (1, 1, 8, 8) にする |
| ValueError: correlate2d inputs must both be 2-D arrays | SciPy の correlate2d に3次元(チャンネル付き)の配列を渡した | チャンネルごとに計算して足すか、自作・ライブラリの多チャンネルの畳み込みを使う |
| ValueError: window shape cannot be larger than input array shape | sliding_window_view で、入力(2×2)よりフィルタ(3×3)が大きい | パディングを足す。層を重ねて小さくなりすぎていないか出力サイズを確かめる |
| RuntimeWarning: overflow encountered in exp と invalid value encountered in divide、結果が [[nan 0. 0.]] | softmax で最大値を引かずに exp(1000) を計算した | 各行の最大値を引いてから exp をとる |
最も多いのは形(shape)の食い違いで、学習の前にダミーの入力を1枚通して各層の形を出すと、多くはその場で見つかります。
CNNを使うか決める判断表
この節では、手元の課題に CNN を使うべきか、ほかの手法にするべきかの判断の目安と、始める前の確認項目が分かります。
| 状況 | 向いている手法 | 理由・手元の結果 |
|---|---|---|
| 表のデータ(売上・属性など) | 勾配ブースティング・ロジスティック回帰・MLP | 列の並びに「近い・遠い」の意味が無く、畳み込みの前提が成り立たない |
| 小さな画像・位置がそろっている | ロジスティック回帰や MLP でも足りることが多い | 8×8 の数字でロジスティック回帰 96.9%、CNN 98.5%。まず軽い手法を基準にする |
| 画像の中で位置や大きさがばらつく | CNN+データ拡張 | 1画素ずらしで CNN 59.6%→拡張あり95.4% |
| 大きな画像・データが少ない | 学習済みの CNN を使った転移学習 | 最初の層のフィルタ(線・模様)は課題をまたいで使い回せる |
| 大きな画像・大量のデータと計算資源 | CNN と ViT を両方比べる | ViT はデータが多いほど伸びる(原論文) |
| 音声・センサーの時系列 | 1次元の CNN、またはスペクトログラム+2次元の CNN | 近い時刻の関係を局所的な特徴として取り出せる |
| 仕組みを学びたい | numpy で自作して、ライブラリの結果と比べる | 畳み込みは SciPy と 5e-15 で一致、勾配は数値微分と 1e-8 前後で一致 |
実務で大きな画像の CNN を GPU で学習する場合、必要な GPU のメモリの見積もり方は関連記事「VRAMとは|確認方法・必要量の目安とLLMの計算式をPythonで解説」で扱っています。
よくある質問
この節では、CNN について検索されることの多い質問への答えが分かります。
Q. CNN とディープラーニングはどう違いますか。
ディープラーニングは、層を深く重ねたニューラルネットワーク全体を指す言葉です。CNN はその中の1つの形で、畳み込み層とプーリング層を使うものです。RNN や Transformer もディープラーニングの別の形です。機械学習・ディープラーニングとの関係の全体は、本サイトの「機械学習とは|種類・ディープラーニングとの違いを実行結果で解説」でも扱う予定です。
Q. なぜ CNN は画像認識に強いのですか。
画像は近くの画素どうしの関係(線・角・模様)に意味があり、同じ模様がどこに出ても同じ意味を持つからです。CNN は局所的な結合と重みの共有でこの性質を組み込んでいます。手元では、同じくらいのパラメータ数の全結合より、元の画像で1〜2ポイント、1画素ずらした画像で約15ポイント高い正解率でした。
Q. フィルタの大きさと枚数はどう決めますか。
大きさは 3×3 が基本です。枚数は最初の層で16〜64枚程度から始め、プーリングで縦横を半分にするたびに2倍にする設計が多く使われます。手元の 8×8 の数字では、4枚で96.8%、8枚で98.5%でした。最後は検証用のデータで比べて決めます。
Q. パディングは必ず要りますか。
必須ではありません。パディング無しでは層ごとに縦横が小さくなり(3×3 なら2ずつ)、端の画素がフィルタの中心に来ないため、端の情報が弱くなります。層を深く重ねる場合や、入力と同じ大きさの出力が欲しい場合(領域の分割など)は、パディングを入れて大きさを保つのが一般的です。
Q. 「畳み込み」と「相互相関」、どちらが正しい呼び方ですか。
数学の定義では、CNN の計算はフィルタを反転しない相互相関です。ただ、機械学習ではこれを畳み込みと呼ぶのが慣習で、PyTorch の公式ドキュメントも『Deep Learning』の教科書も、その前提で書かれています。フィルタは学習で決まるので、どちらで計算しても性能は変わりません。自作の結果を確かめるときだけ、SciPy の correlate2d(相互相関)と比べる点に注意が要ります。
Q. scikit-learn で CNN は作れますか。
scikit-learn のニューラルネットワーク(MLPClassifier・MLPRegressor)は全結合だけで、畳み込み層はありません。CNN を学習するには PyTorch・TensorFlow(Keras)などの深層学習のライブラリを使うか、この記事のように numpy で自作します。
Q. CNN の学習に GPU は必須ですか。
小さな画像と小さなモデルなら CPU で学習できます。手元の 8×8・1,347枚・30エポックは、CPU 1スレッドで1回4.5秒でした。224×224 のような画像を数万枚以上学習する場合は、畳み込みの計算が非常に多くなるため GPU がほぼ必須です。
Q. カラー画像ではどう計算しますか。
フィルタが RGB の3チャンネル分の厚み(3×3×3)を持ち、3つのチャンネルの積の和を1つの値にします。出力のチャンネル数はフィルタの枚数です。
まとめ
この節では、記事全体の要点が分かります。
- CNN は、畳み込み層とプーリング層を重ねた、画像に強いニューラルネットワーク。強さの理由は、局所的な結合・重みの共有・ずらしに対する等変性の3つ。
- 畳み込みは小さなフィルタの積の和をずらしながら計算する処理で、数学では相互相関。出力サイズは floor((入力+2×パディング−フィルタ)÷ストライド)+1。自作の結果は SciPy と 5e-15 以下で一致した。
- 逆伝播は数値微分で確かめられる。float64・eps=1e-5 で相対誤差 1e-8 前後。ただし ReLU の入力がちょうど0の点と、最大プーリングの同点では、正しいコードでも合わないため、乱数の入力と小さな正のバイアスで確かめる。
- load_digits では CNN 8枚(1,370 パラメータ)が 98.5%で、5.5倍のパラメータの全結合(97.6%)とロジスティック回帰(96.9%)を上回った。ただし小さな画像では差は小さく、学習時間は CNN が約10倍長い。
- ずれに強いが、限界がある。1画素ずらしで CNN 59.6%・全結合 45.1%。データ拡張を足すと CNN 95.4%。死んだフィルタ(8枚中1枚)のように、学習後の中身を数えて確かめることも大切。
参考・出典
この節では、この記事で根拠にした公式ドキュメント・教科書・論文が分かります(確認日 2026年11月5日)。
- PyTorch 2.15 公式ドキュメント「Conv2d」:https://docs.pytorch.org/docs/2.15/generated/torch.nn.Conv2d.html
- SciPy 1.18 公式ドキュメント「scipy.signal.correlate2d」:https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.correlate2d.html
- scikit-learn 1.9.1 公式ドキュメント「load_digits」:https://scikit-learn.org/stable/modules/generated/sklearn.datasets.load_digits.html
- numpy 公式ドキュメント「numpy.lib.stride_tricks.sliding_window_view」:https://numpy.org/doc/stable/reference/generated/numpy.lib.stride_tricks.sliding_window_view.html
- Ian Goodfellow, Yoshua Bengio, Aaron Courville『Deep Learning』9章 Convolutional Networks:https://www.deeplearningbook.org/contents/convnets.html
- Stanford CS231n「Convolutional Neural Networks (CNNs / ConvNets)」:https://cs231n.github.io/convolutional-networks/
- Y. LeCun, L. Bottou, Y. Bengio, P. Haffner「Gradient-Based Learning Applied to Document Recognition」Proceedings of the IEEE, 1998
- A. Krizhevsky, I. Sutskever, G. Hinton「ImageNet Classification with Deep Convolutional Neural Networks」NeurIPS 2012
- K. Simonyan, A. Zisserman「Very Deep Convolutional Networks for Large-Scale Image Recognition」2014:https://arxiv.org/abs/1409.1556
- K. He, X. Zhang, S. Ren, J. Sun「Deep Residual Learning for Image Recognition」2015:https://arxiv.org/abs/1512.03385
- A. Dosovitskiy ほか「An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale」2020:https://arxiv.org/abs/2010.11929
- Z. Liu ほか「A ConvNet for the 2020s」2022:https://arxiv.org/abs/2201.03545
- K. Fukushima「Neocognitron: A self-organizing neural network model for a mechanism of pattern recognition unaffected by shift in position」Biological Cybernetics, 1980
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「CNNとは|畳み込みニューラルネットワークの仕組みをnumpyで自作し実測」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。