CNNは画像認識の分野で驚異的な精度を誇るディープラーニングのアルゴリズムのひとつであるものの、ぱっと見がとても複雑な構造をしているため、実装するのも大変そうです。
実際、ネットや文献上で見られる多くのCNNの実装は、Theano (pythonのライブラリ)の自動微分機能を使っていたり、MATLABの組み込み関数を使っているものがほとんどです。
そのためか、きちんと forward propagation & backpropagation を数式で書き下している文献はないように思いました。(もちろん、楽に実装できるならばそれはそれで素晴らしいことです。)
そこで、どうすれば CNN を実装するための数式を書き下せるのか、レイヤーごとに分けて導出していきたいと思います。

まず、CNN がどんな層に分解できるのかについて。これは、下記の3つで表せるでしょう。

  • Convolution Layer: 畳み込みレイヤー
  • Activation Layer: 活性化レイヤー
  • MaxPooling Layer: プーリングレイヤー
この3層をひとつのセットとして、ディープラーニングの場合はこのセットを積み重ねていくのが基本形です。(プーリングレイヤーは、Max PoolingがデファクトスタンダードなのでMaxPooling Layerと書いています。また、モデルの最後に付く通常の多層パーセプトロン層はここでは省いています。)
しかし、この順番は特に決まっているわけではなく、研究によっては Convolution → MaxPooling → Activation で実験していたり、Convolution → Convolution → Activation → MaxPooling で実験していたりと様々です。
複雑に見えるCNNですが、この3つのレイヤーの feed-forward と backpropagation を数式で表してしまいさえすれば、どんな言語でも実装が可能になるはずです。

では、順番に見ていきましょう。まずは畳み込みレイヤーから。
まずはと言いつつ、この層はカーネルの学習があったり、複数チャネルの場合があったりと一番煩雑になるため、簡単のために最初は画像が1チャネル(グレースケール)の場合にどうなるかを考えてみます。



・Convolutional Layer (1 channel)


さて、理解しやすくするために、下の図に則って数式を表わしていくことにします。


image

\( M, N \) はインプット画像のサイズ、\( m, n \) はカーネル(畳み込みフィルター)のサイズ、 \( k \) はカーネルのインデックス番号を表しており、 また、 \( x \) は入力画像データ、 \( a^{\,(k)} \) は、畳み込みレイヤーを通した後の2次元データを表しています。理解のしやすさのために、 カーネル \( \, k \, \) に対応しているんだぞ、ということを明記しています。

すると、畳み込みは以下の式で表すことができます:

\[ a_{ij}^{(k)} = \sum_{s = 0}^{m - 1} \sum_{t = 0}^{n - 1} w_{s\,t}^{(k)} \,x_{(i + s)(j + t)} + b^{(k)} \]

ここで、\(w^{(k)}\) はカーネルを、\(b^{(k)}\)はバイアスを表しています。これで、畳み込みレイヤーの feed-forward は書き下すことができました。
続いて、backward (backpropagation) について考えてみます。学習すべきモデルのパラメータは \( w^{(k)} \)および\( b^{(k)} \)であるため、 誤差関数を\( E \) で表したとすると、それぞれの勾配は次のように書くことができます:

\[ \begin{align} \frac{\partial E}{\partial w_{s\,t}^{(k)}} &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \frac{\partial E}{\partial a_{ij}^{(k)}} \frac{\partial a_{ij}^{(k)}}{\partial w_{s\,t}^{(k)}} \nonumber \\\ \nonumber \\\ &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \frac{\partial E}{\partial a_{ij}^{(k)}} \, x_{(i+s)(j+t)} \nonumber \\\ \nonumber \\\ \nonumber \\\ \frac{\partial E}{\partial b^{(k)}} &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \frac{\partial E}{\partial a_{ij}^{(k)}} \frac{\partial a_{ij}^{(k)}}{\partial b^{(k)}} \nonumber \\\ \nonumber \\\ &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \frac{\partial E}{\partial a_{ij}^{(k)}} \end{align} \]

ここで、バックプロパゲーションの誤差

\[ \delta_{ij}^{(k)} := \frac{\partial E}{\partial a_{ij}^{(k)}} \]

は、前のレイヤー(feed-forwardでは次のレイヤー)から逆伝播してきているはずなので、上式を用いてモデルパラメータを更新することができることになります。

Convolution Layer から更に逆伝播が必要なとき(すなわちディープな層になっているとき)は、 畳み込み層の誤差

\[ \frac{\partial E}{\partial x_{ij}{(k)}} \]

を求める必要があるので、こちらも書き下してみましょう。次のように表すことができます:

\[ \begin{align} \frac{\partial E}{\partial x_{ij}} &= \sum_{s = 0}^{m - 1} \sum_{t = 0}^{n - 1} \frac{\partial E}{\partial a_{(i-s)(j-t)}^{(k)}} \frac{\partial a_{(i-s)(j-t)}^{(k)}}{\partial x_{ij}} \nonumber \\\ \nonumber \\\ &= \sum_{s = 0}^{m - 1} \sum_{t = 0}^{n - 1} \frac{\partial E}{\partial a_{(i-s)(j-t)}^{(k)}} \, w_{s\,t}^{(k)} \end{align} \]

ここで注意すべきなのは、\( i - s < 0 \) または \( j - t < 0 \) となり得る場合があるということです。このときは

\[ \frac{\partial E}{\partial a_{(i-s)(j-t)}^{(k)}} = 0 \]

として計算をすることになります。 誤差の式を見ると、カーネル \( w \) を flip した(行・列ともにひっくり返した、すなわち180°回転した)フィルターとの畳み込みになっていることがわかるでしょう。
畳み込みを逆伝播するのでflipの畳み込みになっている、という様に解釈できるでしょうか。これで、次のレイヤー(feed-forwardでは前のレイヤー)に伝播すべき誤差を求めることができます。



・Activation Layer


続いて、活性化レイヤーです。最近のトレンドとして、sigmoid関数やtanh関数を使うよりも、ReLU (Rectified Linear Unit) を使うことが多いので、ReLUでの式を書いてみます。 これは全く難しくありません。まずは feed-forward から:

\[ a_{ij} = {\rm ReLU}\,(x_{ij}) = {\rm max} \,(0, x_{ij}) \]

また、 backward は次のように表せます:

\[ \begin{eqnarray} \frac{\partial E}{\partial x_{ij}} = \begin{cases} \frac{\partial E}{\partial a_{ij}} & \, if \,\, a_{ij} \geq 0 \nonumber \\\ \, & \, \nonumber \\\ 0 & \, otherwise \end{cases} \end{eqnarray} \]


・MaxPooling Layer


Max Pooling については、フィルターが正方形でなくても適用はできるものの、 正方形にしてもしなくても大差はないので、計算が簡単になる正方形で表すのが普通です。 この層も、学習すべきパラメータはないので、feed-forward, backward ともに 前後のレイヤーにつなげる式を書くだけで終わりです。
feed-forward は次のようになります:

\[ a_{ij} = {\rm max}\, ( x_{(i+s)(j+t)} ) \,\, {\scriptstyle where \,\, s \in [0, \,k], \,\, t \in [0, \,k] } \]

ここで、 \( k \) はフィルタのサイズを表しています。 また、backward (backpropagation) は次のようになります:

\[ \begin{eqnarray} \frac{\partial E}{\partial x_{(i+s)(j+t)}} = \begin{cases} \frac{\partial E}{\partial a_{ij}} & \, if \,\, a_{ij} = x_{(i+s)(j+t)} \nonumber \\\ \, & \, \nonumber \\\ 0 & \, otherwise \end{cases} \end{eqnarray} \]


これで3つの層の feed-forward, backpropagation の導出ができました。
しかし、カラーの画像やディープラーニングに対応するためには、畳み込みレイヤーで複数チャネルの場合を考えなくてはなりません。 こちらについても考えてみましょう。



・Convolutional Layer (multi-channel)


複数チャネルの場合を図にすると、下のように表すことができます。


image

1チャネルのときに比べ、新しくチャネル\(c\) というパラメータが増えました。 そのため、伝播の式もそれぞれチャネルの分、ループが増えることになります。 feed-forwardの式を書いてみると次のようになります:

\[ \begin{align} a_{ij}^{(k)} &= \sum_{c} a_{ij}^{(k, c)} \nonumber \\\ \nonumber \\\ &= \sum_{c} \, \sum_{s = 0}^{m - 1} \sum_{t = 0}^{n - 1} w_{s\,t}^{(k)} \,x_{(i + s)(j + t)}^{(\nonumber c)} + b^{(k)} \end{align} \]

ここで、カーネル(およびバイアス)は各チャネルで共有されるため、\(w^{(k)}\) および \(b^{(k)}\) は \( c \) に依存しないことに注意したい。
すると、モデルパラメータの更新式は下記のように表すことができます:

\[ \begin{align} \frac{\partial E}{\partial w_{s\,t}^{(k)}} &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \, \sum_{c} \frac{\partial E}{\partial a_{ij}^{(k, c)}} \frac{\partial a_{ij}^{(k, c)}}{\partial w_{s\,t}^{(k)}} \nonumber \\\ \nonumber \\\ &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \, \sum_{c} \frac{\partial E}{\partial a_{ij}^{(k, c)}} \, x_{(i+s)(j+t)}^{(\nonumber c)} \nonumber \\\ \nonumber \\\ \nonumber \\\ \frac{\partial E}{\partial b^{(k)}} &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \, \sum_{c} \frac{\partial E}{\partial a_{ij}^{(k, c)}} \frac{\partial a_{ij}^{(k, c)}}{\partial b^{(k)}} \nonumber \\\ \nonumber \\\ &= \sum_{i = 0}^{M-m} \sum_{j = 0}^{N-n} \, \sum_{c} \frac{\partial E}{\partial a_{ij}^{(k, c)}} \end{align} \]

見た目はだいぶごちゃついていますが、基本的にはチャネルごとの和をとる項が増えたにすぎません。 同様にバックプロパゲーションも下記のように導出することができます:

\[ \frac{\partial E}{\partial x_{ij}^{(\nonumber c)}} = \sum_{s = 0}^{m - 1} \sum_{t = 0}^{n - 1} \frac{\partial E}{\partial a_{(i-s)(j-t)}^{(k, c)}} \, w_{s\,t}^{(k)} \]

これで、複数チャネルの場合にも対応することができるようになりました。
\( \sum \)が多いことからも、実装するとforループが何重にもなることがわかります。 マシンの処理能力が高くないとCNNを走らせるのは難しいでしょう。 CNNの実装はまだしていませんが、できたらまた公開する予定です。

数式に誤りがありましたらご連絡ください。

Me

Yusuke Sugomori / 巣籠 悠輔

Creative Technologist

Planner / Engineer / Designer

View Portfolio