【理論とイメージ】CNNの誤差逆伝播とDeconvolutionまとめ

# 自己紹介
関東圏の大学の文系学部に通うものです。最近よくよく考えると誤差逆伝播(数式の変形を追っただけ)やDeconvolutionなどの原理をよく知らないということを悟り、調べたらみんなあまり詳しく解説していないような気がしたので、改めてまとめようと思いここに書きました。

#どのような人向けの記事か
・全結合層ネットワークでの誤差逆伝播をある程度理解している人
・CNNの順伝播(畳み込みの計算など)をある程度理解している人
・CNNの逆伝播もしくはDeconvolutionの仕組みを知りたい人

(「Decovolutionの計算はCNNの誤差逆伝播とほぼ同じだよ」といってすぐに理解できた人は、最後の補足だけみてくだされば大丈夫だと思います。)

# なぜこのタイミングでCNNの誤差逆伝播の説明？
これを読んでくださっている皆さん、いまの時代chainer, TensorFlow, Keras, PyTorchなど、どのフレームを使っているかは個人の自由ですが、基本的に抽象化されたフレームワークを使用していると思います。そうなるときっと１から誤差逆伝播を記述する機会は少なくなっているのではないでしょうか。(と言いますか、僕は恥ずかしながら書いたことありませんでした。)
chainer風に記述すれば、Lossの計算をしてLoss.backward()すれば誤差逆伝播できるのでそこまで気にする必要もないかもしれません。しかし、このようにこれから便利になっていくことが予期されるからこそ、一回ちゃんと原理を抑えようと思い、この記事を書きました。

# 皆さん本当にCNNの誤差逆伝播を理解していますか？(煽りと戒め)
~~僕は今まで[ゼロから作るDeepLearning](https://www.oreilly.co.jp/books/9784873117584/) を読んでわかった気になっていました(全結合層の誤差逆伝播は抑えたし、CNNもどうせ同じようなものだろうと…)。もちろんこの書籍は初学者に対して、非常にわかりやすく記述されており、名著であると個人的に思っています。しかし、皆さんお気づきでしょうか？あの本はあくまで初学者向きであり、誤差逆伝播の計算の代わりに数値微分を用いていていることに…~~

すみません、ここの記述は誤りがありました。[ゼロから作るDeepLearning](https://www.oreilly.co.jp/books/9784873117584/)は、ソースコードの方にBackwardの処理も記述しており、誤差逆伝播で解いていました(大変失礼しました)。

本記事は、先の教科書とは異なり(計算量を気にせず)数式の動きを解説した記事、もしくはDeconvolutionの挙動とConvolutionのBackwardの挙動の比較を書いた記事として読んでいただけると幸いです。(2018/08/22)

# Deconvolutionとの関係
CNNの逆伝播の仕組みを理解することは、Deconvolutionの計算過程を理解することに繋がります。これはどういうことを言っているかと言いますと、chainerの場合は、Convolutionの計算の誤差逆伝播にはDeconvolutionを用いており、逆もまた然りとなっているからです。つまり、Convolution層に返ってきた特徴マップ上の誤差をDeconvolutionすることでもう一つ前の層に誤差を伝えています。しかも、Deconvolutionは教科書にはあまり乗っていないと思いますし、なにをやっているかわかりにくいので、ここで一度把握しておくべきかと考えたからです。ただ大きく異なると言いますか、注意しなければいけないことは、Deconvolutionによる特徴マップの拡大は順伝播の際に行われるものであり、誤差逆伝播で行われる特徴マップの拡大させる処理は逆伝播であるため、活性化関数の扱いが異なるということです。

Deconvolutionの動きなどは、こちらの記事を見ていただけるとわかりやすいと思います。
・[Convolution arithmetic](https://github.com/vdumoulin/conv_arithmetic)

# 数式的な理解とイメージ的な理解 

数式的な展開とイメージ的な理解がつながれば幸いですが、本当に詳しいことが知りたいかたは[画像認識 (機械学習プロフェッショナルシリーズ)](https://www.amazon.co.jp/%E7%94%BB%E5%83%8F%E8%AA%8D%E8%AD%98-%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%8E%9F%E7%94%B0%E9%81%94%E4%B9%9F-ebook/dp/B0756V3TPG)を参考にしてみてください。

## 順伝播
CNNの順伝播の場合の畳み込みの計算は、以下のサイトなどが非常にわかりやすいですし、[ゼロから作るDeepLearning](https://www.oreilly.co.jp/books/9784873117584/) にも乗っているので割愛します。

数式としては、以下のようになります。
![messageImage_1534628504870.jpg](https://qiita-image-store.s3.amazonaws.com/0/222432/a106c248-73e0-9418-e190-0fb89e6a4603.jpeg)

====変数の定義=====
$K^l$:l層のチャンネル数(lは添字)
$M^l,N^l$:l層の縦横の長さ
$P^{l+1},Q^{l+1}$:カーネルの縦横の長さ


$p = ${$1,2,...,P^{l+1}$}
$q = ${$1,2,...,Q^{l+1}$}
$k = ${$1,2,...,K^{l}$}
$k'= ${$1,2,...,K^{l+1}$}

=================

$
a _ { m , n , k ^ { \prime } } ^ { l + 1 } = \sum _ { p , q , k } w _ { p , q , k,k^{'} } ^ { l + 1 } z _ { m + p , n + q , k } ^ { l } + b _ { k ^ { \prime } } ^ { l + 1 }
$

$
z _ { m , n , k ^ { \prime } } ^ { l } = h \left( a _ { m , n , k ^ { \prime } } ^ { l } \right)
$

$a _ { m , n , k ^ { \prime } } ^ { l + 1 } $ ：(l+1)層上のピクセル(m,n)におけるk'番目のチャンネルにおける出力

$w _ { p , q , k,k^{'} } ^ { l + 1 } $ ：(l+1)層の特徴マップを作成するために使用したカーネルのうちk'番目のカーネルであり、kチャンネル目にある(p,q)の位置にある重み

$z _ { m + p , n + q , k } ^ { l }$ :l層上のピクセル(m+p,n+q)におけるkチャンネル目の出力

$b _ { k ^ { \prime } } ^ { l + 1 }$　：各チャンネル毎のバイアス項

$h(x)$　：活性化関数(ReLUなどなど)

以下のサイトなども参考になります。
・[Convolution arithmetic](https://github.com/vdumoulin/conv_arithmetic)
・[Convolutional Neural Networkとは何なのか](https://qiita.com/icoxfog417/items/5fd55fad152231d706c2)
・[Applied Deep Learning - Part 4: Convolutional Neural Networks](https://towardsdatascience.com/applied-deep-learning-part-4-convolutional-neural-networks-584bc134c1e2)

## 逆伝播
逆伝播は、連鎖律という規則を用いて各パラメータでの勾配を計算していきます。CNNのConvolution層でも、もちろんそれは変わりません。ただConvolution層の場合は順伝播で、カーネル(フィルターとも呼ばれる)という重みテンソル(この記事では多次元配列のことをテンソルと呼ぶ)を共有することで、対象にした特徴マップから次の層の特徴マップを作成しています。そしてこの計算過程をもとに逆伝播させていくわけですから、全結合層とは少し勝手が異なります。

このように重みの共有の問題を乗り越え、誤差逆伝播を計算するためには3つ問題点に上がると思われます。

1.誤差逆伝播の計算について
2.どのようにして、特徴マップのサイズ(縦と横の大きさ)を大きくしているのか？
3.どのようにして、特徴マップのチャンネル数の違いを合わせているのか？

## 1.誤差逆伝播の計算について
全結合層での誤差逆伝播の計算はこれらの記事、「[誤差逆伝播法のノート](https://qiita.com/Ugo-Nama/items/04814a13c9ea84978a4c)」、「[誤差逆伝播法をはじめからていねいに](https://qiita.com/43x2/items/50b55623c890564f1893)」を見ていただければわかると思いますので、 ここでは省略します。
イメージ的には、「重み行列(行列)→カーネル(テンソル)」、「中間層(ベクトル)での発火→特徴マップ(テンソル)での発火」に変わります。ただ、全結合ではないので、ある特徴マップでの１点の発火が、次の層の特徴マップに関与している訳ではないことに注意する必要もあります(有効受容野の考えに近いものですね)。


## 2.どのようにして、特徴マップのサイズ(縦と横の大きさ)を大きくしているのか？
順伝播の場合は畳み込みの計算でpaddingサイズやstrideサイズ、dilateなどを指定することで特徴マップのサイズ(縦横の大きさ)を調整しています。ResNetなどのモデルでは特徴マップのサイズが変わりませんが、VGG-16などのモデルをイメージしていただければ、徐々に特徴マップが小さくなっていく様子がわかると思います。ちなみにサイズの調整に関しては、このサイト[【保存版】chainerのconvolutionとdeconvolution周りを理解する](https://www.monthly-hack.com/entry/2017/03/03/171543)がわかりやすいと思います。ただこの記事はあくまで１チャンネルで動かした際のDeconvolutionの計算をわかりやすく、具体的に説明してくれているということに注意する必要があります。そしてこの記事を踏まえた上で今回は解説を行います。

![vgg16.png](https://qiita-image-store.s3.amazonaws.com/0/222432/b490bcbb-5951-2a3b-10e0-99074c9a5022.png)

[VGG in TensorFlow](http://www.cs.toronto.edu/~frossard/post/vgg16/)より引用


まず、逆伝播の計算では特徴マップ上に帰ってきた勾配に対して畳み込みの計算を行います。ただ、通常の順伝播での畳み込み計算ではなく、勾配に対してDeconvolution(逆畳み込み)を行います。しかし、Deconvolution(逆畳み込み)と言っても特別新たな計算を行うという訳ではありません。では何が順伝播(Convolution)と計算過程が異なるかと言いますと、以下の点です。

2.① 重み行列を縦横方向に反転する
2.② １チャンネルごとに計算を行う
(順伝播の畳み込みの計算はカーネルごとにチャンネル方向にも一気に足し合わせてしまうが、逆伝播では各チャンネルごとに全てのカーネルを用いて計算を行うことが違う)
2.③ サイズを調整するためにzero-paddingやdilateなどを駆使する

### 2.①重み行列(使用する重みテンソルのあるチャンネル)を縦横方向に反転する
これはなぜこのようなことをしているかを理解するためには、まずはじめに順伝播で関わったピクセルと重みの位置を意識する必要があります。以下のように4×4の特徴マップから2×2のカーネル(単純のためにチャネル数は１になっている)から3×3の特徴マップの順伝播を考えます。ここでのカーネルの数字は重みの大きさというよりは、位置情報という風に読んでくださると理解が捗るかもしれません。

![conv.png](https://qiita-image-store.s3.amazonaws.com/0/222432/a68c3765-3534-1f16-204d-ff571e4b84dd.png)

すると、各畳み込み処理の結果と、携わった重み行列の要素は以下のようになります。今回はオレンジ色になっているピクセルの計算に関わらない計算は無視してあります。

![backprop.png](https://qiita-image-store.s3.amazonaws.com/0/222432/47111abb-224d-0c8e-1b2f-adc9e6963bb7.png)

つまり、3×3の特徴マップを作るために、カーネルが関わった重みの要素の情報と3×3の特徴マップのピクセルの関係は以下のようになります。

![backprop_2.png](https://qiita-image-store.s3.amazonaws.com/0/222432/b3c9d9c0-a970-2b12-2f44-e7ae8f243f78.png)

順伝播の際に関わった重みの位置は、ちょうどカーネルの縦横を反転させたものと一致します。このようにして、オレンジ色のピクセルが計算に関わった重み要素と特徴マップの箇所に対して逆伝播を行うことができるということになります。じゃあ、l層の(1,1)番目のピクセルの逆伝播はどうするんだ？という疑問を思った方は、2.③をご覧ください。有り体に言うと、辻褄が合うようにpadding等をすればいいと言う話なだけです。

### 2.②どのようにして、特徴マップのサイズ(縦と横の大きさ)を大きくしているのか？
これは順伝播の時と逆伝播の時の数式を見てもらえば一目瞭然なのですが、まず順伝播の時の計算式は以下です(stride=1,padding=0の時)。(デルタの計算の時に戻ってきます。)


$
a _ { m , n , k ^ { \prime } } ^ { l + 1 } = \sum _ { p , q , k } w _ { p , q , k,k^{'} } ^ { l + 1 } z _ { m + p , n + q , k } ^ { l } + b _ { k ^ { \prime } } ^ { l + 1 }
$

$
z _ { m , n , k ^ { \prime } } ^ { l } = h \left( a _ { m , n , k ^ { \prime } } ^ { l } \right)
$


次に逆伝播の時の計算です。
逆伝播を行う目的としては、各層のカーネルの重みを更新することにあります。そこで必要なものは、$\frac { \partial J _ { i } } { \partial w _ { p , q , k , k ^ { \prime } } ^ { l } }$となります。連鎖律を元にこの勾配を計算すると以下のようになります。
$
\frac { \partial J _ { i } } { \partial w _ { p , q , k , k ^ { \prime } } ^ { l } } = \sum _ { m , n } \frac { \partial J _ { i } } { \partial a _ { m , n , k ^ { \prime } } } \frac { \partial a _ { m , n , k ^ { \prime } } ^ { l } } { \partial w _ { p , q , k , k ^ { \prime } } ^ { l } }
$


ここで一度それぞれ、かけられているものを見ると、順伝播の計算から以下のことがすぐに導かれます。

$
\frac { \partial a _ { m , n , k ^ { \prime } } ^ { l } } { \partial w _ { p , q , k , k ^ { \prime } } ^ { l } } = z _ { m + p , n + q , k } ^ { l - 1 }
$

そして問題となってくるのが、$\frac { \partial J _ { i } } { \partial a _ { m , n , k ^ { \prime } } }$の部分ですが、今これを、$\frac { \partial J _ { i } } { \partial a _ { m , n , k ^ { \prime } } ^ { l } } = \delta _ { m , n , k ^ { \prime } } ^ { l }$として表します。$\delta _ { m , n , k ^ { \prime } } ^ { l }$を使ってに表すと、以下のようになります。

$
\frac { \partial J _ { i } } { \partial w _ { p , q , k , k ^ { \prime } } ^ { l } } = \sum _ { m , n } \delta _ { m , n , k ^ { \prime } } ^ { l } \cdot z _ { m + p , n + q , k } ^ { l - 1 }
$
バイアス項に対しても同じように計算しますが、bに関して偏微分したものは１になるので、以下のように表せます。

$
\frac { \partial J _ { i } } { \partial b _ { k ^ { \prime } } ^ { l } } = \sum _ { m , n } \delta _ { m , n , k ^ { \prime } } ^ { l }
$

つまり、$\delta _ { m , n , k ^ { \prime } } ^ { l }$が計算することができれば、重みの更新を行うことができます。ちなみに重み更新の際の計算の動きは以下のようになります。下記の図はk'番目のカーネルのkチャンネル目の(p,q)を更新することを想定したものです。使用する箇所は、順伝播で(l-1)層のkチャンネルで計算された出力と、l層のk'チャンネルまでに計算された$\delta^l$を元に計算しています。わかりにくいですが、図の赤い正方形は、$\delta^l_{:,:,k'}$(l層に帰ったきた誤差のうちk'番目のもの)です。(手書きなのは、力尽きました。許してください…)

![messageImage_1534619429391.jpg](https://qiita-image-store.s3.amazonaws.com/0/222432/cefbd920-b210-e9a6-3900-d03a80f60ab2.jpeg)


では、実際に$\delta _ { m , n , k ^ { \prime } } ^ { l }$の逆伝播の様子についてまとめます。$\delta _ { m , n , k ^ { \prime } } ^ { l }$も連鎖律を用いて表すと以下のようになります。

$
\delta _ { m , n , k } ^ { l } = \frac { \partial J _ { i } } { \partial a _ { m , n , k } ^ { l } } = \sum _ { p , q , k ^ { \prime } } \frac { \partial J _ { i } } { \partial a _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 } } \frac { \partial a _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 } } { \partial a _ { m , n , k } ^ { l } }
$

$\frac { \partial a _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 } } { \partial a _ { m , n , k } ^ { l } }$の計算は、順伝播の時の計算を参照すると、以下のようになります。

$
\frac { \partial a _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 } } { \partial a _ { m , n , k } ^ { l } } = w _ { p , q , k , k ^ { \prime } } ^ { l + 1 } h ^ { \prime } \left( a _ { m , n , k } ^ { l } \right)
$

さらに、$\frac { \partial J _ { i } } { \partial a _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 } }=\delta _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 }$となることに注意すると、最終的に以下のようになります。

$
\delta _ { m , n , k } ^ { l } = h ^ { \prime } ( a _ { m , n , k } ^ { l } )\sum _ { p , q , k ^ { \prime } } \delta _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 } \cdot w _ { p , q , k , k ^ { \prime } } ^ { l + 1 }
$

この数式が意味していることは、各辺の外側に(kernel_size -1)の大きさでpaddingをして、重み行列を縦横反転させたのち(わざわざ対象にするピクセルを(m - p）や(n - q)にしている理由に該当）に、特徴マップ上に帰ってきた勾配に対して、重み行列を縦横反転させたのちに畳み込みの計算をしていることと同じになります。(畳み込み処理をしようとして、特徴マップからはみ出た部分は0として扱うみたいなイメージです。)

もちろん数式的に表しているものとしては、重み行列を反転させていないのですが、畳み込み計算を行う$\delta^{l}$の領域に対して、今まで左上の端から順に畳み込み計算を行なっていた部分を、右下の端から計算するように変えているので、結果的に重み行列の縦横を反転させて畳み込みの計算をしたことと同じになります。


### 2.③サイズを調整するためにzero-paddingや特徴マップへのdilateなどを駆使する
すでに計算でも紹介したように順伝播の時のstrideやpaddingが直接影響してきます。
具体的には、[A guide to convolution arithmetic for deep learning](https://arxiv.org/pdf/1603.07285.pdf)この資料を読んでいただければDeconvolution(Transposed Convolution)に関して自分の得たい情報がすぐに手に入ると思います。ちなみに基本的には、Deconvolutionの出力サイズは、

出力サイズ = stride数 * (inputのサイズ - 1) + カーネルのサイズ - 2*パディング数

で計算されているみたいです。
strideが２以上であった場合の計算もよくわからなかったのですが(と言いますか、これの計算にはめぼしい資料がなかったため)、一応まとめました。気になる人は、下記の注意の部分を参照していただければと思います。後ろの方にchainerの挙動を載せておきました。

※※※※※※※注意※※※※※※※
※の中はchainerの実装コードを読んでも理解できなかったので、もしかしたらあっているかもしれませんし、間違っているかもしれません。(一応chainerでの動作確認はしましたが…)
おそらく順伝播の際のstrideやpaddingの影響は誤差逆伝播やDeconvolutionの時に以下のようになっています。

stride数を元に「stride-1」だけdilateする。paddingとksizeを元に「ksize-padding(順伝播の際にしようしたもの)-1」の大きさでzero-paddingする。畳み込みの際は、重み行列を縦横反転させて、stride数は１に強制する(ここがあっているか不安です)。

ここで一度具体的な例を元に考えてみます。下記のような順伝播を考えます。
5×5の特徴マップから２×２の特徴マップを作成する。(padding=0, stride=2, ksize=3, diliate=1)
![順伝播.png](https://qiita-image-store.s3.amazonaws.com/0/222432/7fbb76ce-8932-276d-c857-db2f22a8ad52.png)

次に考えるものは、上の図で右側(l+1)層に帰ってくる勾配情報を元にl層へ逆伝播する計算過程です。上にも述べましたが、下記のように順伝播の際にしようしたパラメータ(stride数など)を元にzero-paddingを行います。
図は順伝播の時にpadding=0なので、ksize-1の大きさだけ周囲をzero-paddingしています(灰色部分)。次にstride-1だけ特徴マップのピクセルとピクセルの間にzeroを入れてきます。

![deconv.png](https://qiita-image-store.s3.amazonaws.com/0/222432/d4aab33c-44c6-e757-99ea-eef46bc3fb7b.png)

つまり特徴マップを右の特徴マップの用意リサイズするためのパラメータは以下のようになっていると考えられました。つまり、Deconvolutionで言う所のstride数は、特徴マップをどれくらいdiliateするかという値ということです。

![aaa.png](https://qiita-image-store.s3.amazonaws.com/0/222432/94cdf8f7-410f-9c6d-31b6-1e47a1c10bf5.png)

なぜこのように注意喚起をしているかと言いますと、このように考えると辻褄が会うのですが、chainerの実装からそれを読み取ることができなかったからです(誰か強い人確認お願いします…)。一応chainerで簡単な実験をしてみて動作確認をやってあっていたのですが(補足を載せておくのでそちらを参照してください)、厳密にあっているのかわからなかったのであくまで参考にどうぞ…
※※※※※※※注意終了※※※※※※※


## 3.どのようにして、特徴マップのチャンネル数の違いを合わせているのか？
今私がやろうとしていることを図に表すと以下のようになります。

![hukugen.png](https://qiita-image-store.s3.amazonaws.com/0/222432/74ee2156-2244-32d8-f383-ca8116430a3c.png)

また「2.② １チャンネルごとに計算を行う」に関して説明をしている際にも登場した際の数式を再掲すると以下のようになります。

$
\delta _ { m , n , k } ^ { l } = h ^ { \prime } ( a _ { m , n , k } ^ { l } )\sum _ { p , q , k ^ { \prime } } \delta _ { m - p , n - q , k ^ { \prime } } ^ { l + 1 } \cdot w _ { p , q , k , k ^ { \prime } } ^ { l + 1 }
$

ここで注目すべきなことは、$\delta _ { m , n , k } ^ { l }$(l層のkチャンネル目の(m,n)の位置にあるピクセル)を計算するために、和の部分にk(l層のチャンネル数)ではなく、k’((l+1)層のチャンネル数)で足し合わせていることです。しかもの添字に注目すると、kは固定した状態で計算を行なっています。

よって上の数式を元に、改めて一度復元したいチャンネル毎の計算過程をおってみたいと思います。画像には記載されていませんが、(l+1)層に帰ってきた勾配テンソルと順伝播で用いたカーネルを使ってl層の１チャネル目に帰ってくるはずの勾配を計算したいというものです。

はじめに考える作業は図にも書いてあるように、(l+1)層目の１チャンネル目と１個目のカーネルの１チャンネル目に注目した作業です。つまり、$\delta _ {:,:,1}^{l+1}$と$w_{:,:,k, 1}^{l+1}$に注目します。

![1channel.png](https://qiita-image-store.s3.amazonaws.com/0/222432/58ca3bc2-b25d-1ea0-9c70-80e19373ab6e.png)

先ほどの計算(2.①)でも言ったように、重み行列(重みテンソルの１チャンネル目)を縦横の転置(反転)を行います。

![size.png](https://qiita-image-store.s3.amazonaws.com/0/222432/34bd5e2a-e634-2f62-548a-937194279df9.png)

次に、2.③でも触れらたように、zero-paddingや特徴マップへのdilateなどを駆使してサイズを調整します。それが上の図の左側絵になります。そして、このサイズ調整後の特徴マップの勾配に対して、通常の(stride=1 ?)の畳み込み計算を施します。このようにすると、2.①のように、重みが作用した箇所に対して、その重みを用いて逆伝播することができます。ここまでの過程をまとめるとこのようになります。

![1channel_.png](https://qiita-image-store.s3.amazonaws.com/0/222432/b5cbb90d-79cd-338a-4f6b-57d944b6b06d.png)

このようにして、l層の大きさまで、誤差を復元（という言葉が正しいかわかりませんが、逆伝播の方がいいかも？）します。ちなみに２チャンネル目以降の動きは下のslideshareを参考にしてください。(p1~8が参考になると思います)

<iframe src="//www.slideshare.net/slideshow/embed_code/key/soCBIEquwbtvr6" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <a href="//www.slideshare.net/ssuser87f46e/cnndeconvolution" title="CNNの誤差逆伝播/Deconvolutionの計算過程" target="_blank">CNNの誤差逆伝播/Deconvolutionの計算過程</a>

![sum.png](https://qiita-image-store.s3.amazonaws.com/0/222432/9edd8f64-96d7-7f26-ef3b-8d8bd58b43df.png)


次に$K^{l+1} $回各チャンネルでこれを繰り返すことで、上の図のカラフルなテンソルが出来上がります。そしてこれを中央の図のようにチャンネル方向に足し合わせることでようやくl層の１チャンネル目に誤差を逆伝播することができました。

l層の２チャンネル以降の計算は以下のようになります。つまり以下のような計算を行うことを考えます。

![2channel.png](https://qiita-image-store.s3.amazonaws.com/0/222432/9f412fcd-e0a3-d58d-da32-fb07b166057f.png)

今度は、各カーネルの２チャンネル目を用いて同じことを繰り返します。つまり、l層のnチャンネル目の誤差の逆伝播を計算したい場合、各カーネルのn番目のチャンネルを使用するということになります。ちなみにあとは足し合わせるという過程を含めて全て同じです。

![2channel_.png](https://qiita-image-store.s3.amazonaws.com/0/222432/a3e29522-db08-1201-e38c-7d9f852e001b.png)

詳しい動きは、先ほどあげましたslideに乗っていますので、ご覧いただければと思います。
以上で、CNNの誤差逆伝播の計算過程の説明をおわります。

# 補足
環境は以下のようになっています。

``` python_version.py
Python version = 3.6.2
conda version = 4.5.9
chainer.__version__ = 4.3.1
numpy.__version__ = 1.12.1
```

またモジュールの定義は以下のようになっています。

```import.py
import chainer
import numpy as np
import chainer.functions as F
import chainer.links as L
from chainer import Variable
```

chainer での動作確認を行います。
例として、９×９の大きさで要素が全て１となるような入力と、今までの図のように1~9が順番に入っている重みを用意します。(ただしchainerに入れられるようにx.shape=(1,1,9,9),W.shape=(1,1,3,3)としてあります)

```print_x.py
           [[[[ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]
            [ 1.  1.  1.  1.  1.  1.  1.  1.  1.]]]])

```

```print_W.py
           [[[[ 1.  2.  3.]
              [ 4.  5.  6.]
              [ 7.  8.  9.]]]]

```

以上のパラメータで畳み込み処理を行います。わかりやすいようにsride＝３にしてあります。

```convolution.py
with chainer.using_config('train', False):
    x = Variable(np.ones(9**2).astype("f").reshape(9,9)[None,None,:,:])
    W = np.arange(1,10).astype("f").reshape(3,3)[None,None,:,:]
    conv = L.Convolution2D(1, 1, ksize=3,stride=3,nobias=True,dilate=1, initialW=W)
    conv_output_sum = F.sum(conv(x))
    conv.cleargrads()
    conv_output_sum.backward(retain_grad=True)
```

ここでは、詳しい説明は省きたいと思いますが、chainerは出力を逆伝播したいときに、そのまま〇〇.backward()とすると、その値ではなく、勾配が１として計算されるみたいです(conv_outputsum.gradの部分)。ですので、この場合は合計の値が405.0なので、それを逆伝播しているように思われるかもしれませんが、今回δに該当する部分は1となります。


```print_result.py
print(conv_output_sum.data)
405.0
print(conv_output_sum.grad)
1.0
print(x.grad)
[[[[ 1.  2.  3.  1.  2.  3.  1.  2.  3.]
   [ 4.  5.  6.  4.  5.  6.  4.  5.  6.]
   [ 7.  8.  9.  7.  8.  9.  7.  8.  9.]
   [ 1.  2.  3.  1.  2.  3.  1.  2.  3.]
   [ 4.  5.  6.  4.  5.  6.  4.  5.  6.]
   [ 7.  8.  9.  7.  8.  9.  7.  8.  9.]
   [ 1.  2.  3.  1.  2.  3.  1.  2.  3.]
   [ 4.  5.  6.  4.  5.  6.  4.  5.  6.]
   [ 7.  8.  9.  7.  8.  9.  7.  8.  9.]]]]

```
一応、「※※※※※※※注意※※※※※※※」に記載した通りの動きはしているようです。

次にDeconvolutionの動きを追ってみたいと思います。今度はyという3×3の要素が１の行列を入力として(重みは同じです）、Deconvolutionしたいと思います。先ほどと同様にstride=3です。

```print_y.py
          [[[[ 1.  1.  1.]
            [ 1.  1.  1.]
            [ 1.  1.  1.]]]]
```


```Deconvolution.py
with chainer.using_config('train', False):
    y = Variable(np.ones(9).astype("f").reshape(3,3)[None,None,:,:])
    W = np.arange(1,10).astype("f").reshape(3,3)[None,None,:,:]
    deconv = L.Deconvolution2D(1, 1, ksize=3,stride=3,pad=0,nobias=True, initialW=W)
    deconv.cleargrads()
    deconv_output = deconv(y)
```

結果を表示すると、１をDeconvolutionした結果と誤差逆伝播が同じ動きをしていることがわかります。

```print_result_2.py
print(deconv_output.data)
array([[[[ 1.,  2.,  3.,  1.,  2.,  3.,  1.,  2.,  3.],
         [ 4.,  5.,  6.,  4.,  5.,  6.,  4.,  5.,  6.],
         [ 7.,  8.,  9.,  7.,  8.,  9.,  7.,  8.,  9.],
         [ 1.,  2.,  3.,  1.,  2.,  3.,  1.,  2.,  3.],
         [ 4.,  5.,  6.,  4.,  5.,  6.,  4.,  5.,  6.],
         [ 7.,  8.,  9.,  7.,  8.,  9.,  7.,  8.,  9.],
         [ 1.,  2.,  3.,  1.,  2.,  3.,  1.,  2.,  3.],
         [ 4.,  5.,  6.,  4.,  5.,  6.,  4.,  5.,  6.],
         [ 7.,  8.,  9.,  7.,  8.,  9.,  7.,  8.,  9.]]]], dtype=float32)
```

#まとめ
・CNNの逆伝播では、順伝播で使用した重みテンソルを用いるが、その際に縦横の重みは反転させており、各チャンネルごとに計算を行う。
・チャンネル方向の調整は、各カーネルのチャンネルごとに計算を行うことで実現している。
・Convolutionの逆伝播の計算過程で、Deconvolutionを使用している(Chainer限定かも？)
・Stride=2以上の計算は少し不安なので、参考までに…
・Deconvolutionは重みテンソルの縦横を反転させて、色々paddingしたのちに畳み込み処理しただけ(だと思う)。

##　感想
・今回はなるべく数式と図的なイメージを連結できるように解説をしましたが、正直どこまで正確に把握しているのか自分でもわからないところがあるので、自分で勉強するための参考程度にしていただければと思います。
・絶対にテンソルの計算でうまう具合に処理できるような気がしたが、あくまで今回はイメージが湧くようにすることを優先しました（強がり）

・何かわかりにくいところや、間違えているところがあれば言ってくださればなるべく直すようにするので言ってください。

# Reference
・[画像認識 (機械学習プロフェッショナルシリーズ)](https://www.amazon.co.jp/%E7%94%BB%E5%83%8F%E8%AA%8D%E8%AD%98-%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%97%E3%83%AD%E3%83%95%E3%82%A7%E3%83%83%E3%82%B7%E3%83%A7%E3%83%8A%E3%83%AB%E3%82%B7%E3%83%AA%E3%83%BC%E3%82%BA-%E5%8E%9F%E7%94%B0%E9%81%94%E4%B9%9F-ebook/dp/B0756V3TPG)
(この記事の数式部分はほとんど、この書籍を~~丸ぱくり~~参考にしました。)
・[畳み込みニューラルネットワークにおける順伝播・誤差逆伝播計算](http://www.ccn.yamanashi.ac.jp/~tmiyamoto/img/formulization_cnn.pdf)
(ネット上に挙げられていて数式が掲載されている資料です。添字は若干異なります。)
・[数式で書き下す Convolutional Neural Networks (CNN)](http://blog.yusugomori.com/post/129688163130/%E6%95%B0%E5%BC%8F%E3%81%A7%E6%9B%B8%E3%81%8D%E4%B8%8B%E3%81%99-convolutional-neural-networks-cnn)
(すごく参考になったブログ記事です。)
・[A guide to convolution arithmetic for deep learning](https://arxiv.org/pdf/1603.07285.pdf)
(友人が見つけた論文？資料？です。便利そうだったので、パクりました。)
・[Deconvolutional Networks](http://www.matthewzeiler.com/wp-content/uploads/2017/07/cvpr2010.pdf)
(Deconvoluionを提案した論文、教師あり学習)
・[Visualizing and Understanding Convolution Networks 誤差逆伝播](https://cs.nyu.edu/~fergus/papers/zeilerECCV2014.pdf)
(Deconvolutionを用いた可視化、教師なし学習、重み行列を縦横に反転すると記載あり)
・[Convolution arithmetic](https://github.com/vdumoulin/conv_arithmetic)
(@taashi さんより紹介されたGithubの資料です。非常にわかりやすいのでこちらではじめに用語の確認とかをするといいかもしれません。)