モデル の中身を理解してみる
前回は、CNN を使うと なぜかめちゃくちゃ当たる という結果だけを見ました。
でも 何がどう処理されて、最後に (9,) の確率分布になっていくのか?
そこまでは見ていません。
今回は、CNNのモデルの内部がどんな“変換”をしているのかをできる限り可視化してみようという試みです。
1. 今回の入力画像(ちょっとノイズ入り)
前回使っていた入力がぞうがこれ、

(赤い点線グリッドは答えのエリアをわかりやすくしてるだけで実際のデータには含まれない)
で、これだと結局四角い形したカーネルは簡単に答えを見つけてしまうので、今回背景にノイズを加えて一見どこにターゲットの6x6の四角があるかわかりにくくしてみた。

なかなか人間が見てもパッと見つけづらい。
ちなみにこれで学習させてもTinyCNNはそこそこすぐに学習して見つけれるようになるのだけど。
2.モデル全体像
今モデル全体像は以下のようになってる
1. Input: (1, 32, 32) - グレースケール画像
2. Conv1: 3x3カーネル×16 → (16, 32, 32)
3. ReLU: 負の値を0に
4. MaxPool: 2x2 → (16, 16, 16)
5. Conv2: 3x3カーネル×32 → (32, 16, 16)
6. ReLU: 負の値を0に
7. MaxPool: 2x2 → (32, 8, 8)
8. Flatten: → (2048,)
9. FC1: 2048→64 → (64,)
10. ReLU: 負の値を0に
11. FC2: 64→9 → (9,) ← logits
12. Softmax: → (9,) ← 確率
convもfc層もやってることは線型結合。
-
conv:カーネルサイズという局所空間の要素群ごとに各要素に重みをかけてバイアスを足して一つの値を導く。入力チャンネル数は、入力に用いるチャンネルの数を指定し、出力チャンネル数は重みのパターンを変えて幾つかの出力をするようにしてる。
-
fc:今convで説明したことを全体レベルでやる(空間固定でチャンネルだけやるとかもできるし逆もできるけど)
ここではさらに非線形のReLU, MaxPool, Softmaxがある。これらは条件分岐 みたいなもの 。
-
ReLU:負の値を0にする。
これは if(x<0) x=0 のような「条件的な変換」で、モデルに非線形性を与えて表現力を高める。 -
MaxPool:指定範囲の最大値だけを残す。
if 文ではないが、“最大の値を採用する” という決定的ルールを用いてノイズを減らし重要な構造を強調する。 -
Softmax:最後の9つのスコア(logits)を確率に変換する( 学習時 は CrossEntropyLoss が内部で softmax を行うためモデル内には明示的に書かないのが一般的)。
3. Conv1を見てみる
Conv1では空間解像度は変えずに入力1チャンネルを16チャンネルに拡大してます。カーネルサイズは3なので、自ピクセルから見て周囲に1ピクセル増やした3x3の空間のそれぞれのピクセルにそれぞれ違う重みをかけて足してる感じです。これをこの重みパターンを16個持ってます。つまりそれぞれ異なる3x3パターンで重み付き和=線型結合をしています。

上のgifは16個の重みパターンの育つ様子を表したものです。面白いことにそんなに変化しません。学習率の設定などにもよるとは思いますが、6x6の答え四角を捕まえるには最初のランダムな値でも十分に捕まえれるんだと思います。
ランダムな 3×3 フィルタでも、いくつかは「縦線っぽい」「横線っぽい」「斜めっぽい」「中央が明るい」などの性質を偶然持ちます。CNN はその中から “たまたま四角の特徴を拾えるフィルタ” を強調し、他のフィルタは無視する方向に学習が進むため、Conv1 の変化が小さく見えます。前回の考察で述べた帰納バイアスそのものです。

そしてこれがさっきのフィルタを通したテンソルの変化です。この層ではまだ何かよくわからない感じですね。
4. MaxPool1を見てみる
MaxPool1のウィンドウサイズは2x2なので、Conv1の出力の32x32を2x2のセルに区切ってそのセル内の最大値を次に渡していくって感じです。最大値を取るということは、局所的に「強い特徴だけを残す」処理になります。
ノイズは平均を取ると残りやすいが、最大値だけなら特徴量のスパイクが残るため、意図せずノイズ除去的な働きも生まれます。
ここには学習可能なパラメーターはありませんが、その出力最大値の学習過程での変化が以下のgifです。

まだ捉えれてない感じはありますね。2x2のセルの最大値を渡していくので空間解像度は半分になって16x16を次に渡すことになります。チャンネル数は維持。
5. Conv2を見てみる
こちらのConv2はカーネルサイズ3x3でちゃんねる数を16->32に増やして、空間解像度は16x16で維持です。
テンソルの値は学習過程で以下のように感じで育ちます。

幾つかのチャンネル位置でなんとなく”それ”をつかみ出してます。
6. MaxPool2を見てみる
MaxPool2はウィンドウサイズは2x2なので、空間解像度をさらに小さく8x8にします。チャンネル数は維持。

これは前の層のConv2の結果をより小さい解像度ないで確定させてる感じですね。
7. FC1を見てみる
一つ前の出力で32x8x8=2048の要素があるのでこれを全体として線形和を出していきます。ここでは2048チャンネルを64チャンネルにします。ここではすでに空間の意味合いがなくなってて、2048個の値のどこを強調してみるか、どこを軽視するかみたいな感じで和を出してます。そのパターンが64個ある感じです。
空間の意味合いがなくなるというのは、(C,H,W) の “位置情報” が消え、すべてが 1 次元ベクトルとして扱われる。
ここからは形の情報ではなく「特徴の組み合わせ」だけを見る層になります。この処理自体はこのFC1で行ってるわけではなくてこの一つ前に挟まってるFlattenでやってます。

この入力画像に対しては学習の割と初期にすでに40番目のチャンネルが際立つようになってるのがわかります。ここで少し誤解を生むかもなので補足しますが、これは毎回40番目が際立つのではなくこの入力では40番目が際立ってる。これはここまでこの層を含めて全てのネットで行ってきた処理の結果、この入力 では40番目が際立つ->答えを出そうとしてる動きそのものです。
8. FC2を見てみる
いよいよここでは64個あったチャンネルを最終出力と同じである9にします。これも誤解あるといけないので補足しますが、64個のうち55を脱落させて9個にするのではないです。64この数字の線形和を9パターン作ってます。

index3が途中から安定して立ってきて、何か答えを出そうとしてるのがよくわかります。
9. Softmaxを見てみる
このSoftmaxは実際には(9,)の入力なので、9個を足すと1になるように全てを[0,1]に収めるような数式的計算を行います。この[0,1]という値と足して1になるという性質から、softmaxの値は確率とみなして使われます。“確率らしく見える値”であって、統計学上の確率そのものではない。
また、ここには学習可能パラメーターはありません。この層は実際には学習モデル内にはなく損失計算時の損失関数内にあります。というのも、モデルからはlogits(生の値 (-inf, inf))で出しておいて、それを元に損失を計算した方が、勾配を伝達するときに値が大きいので勾配がヘタリません。

とこの入力に対しててラベル3を導くまでの各値の変化の仕方を学習の時間経過に沿ってみてみました。
モデルを学習させるときにある層から出る値がどのように変化するかを観察するのは学習において非常に重要な知見になりえます。