畳み込みニューラルネットワーク
畳み込みへの導入
ここまでのニューラルネットワークでは全結合層をメインに用いてきた。全結合層は、入力のすべての成分が出力のすべての成分に寄与する。入力次元 のデータに対して、出力次元 の全結合層を適応する場合、その時間計算量は で、空間計算量は である。これから扱うような画像データのような高次元データに対しては、全結合層は計算量が大きくなりすぎる。そこで、局所受容野と重み共有という工夫を通じて畳み込みという手法を導入する。
局所受容野
まず、空間的に相関を持つような空間データを考える.これまでの特徴量にはデータ内の位置に意味がないものとして扱っていた.つまり、 と には隣である意味はなく、全結合層はそれらの空間的位置を無視して関係を学習していた。しかし、画像データが代表するような空間データでは、隣接する特徴量には相関がある。つまり、 と が隣接していること自体に意味が存在する。そこで、特徴量を局所的に抽出することを考えることできるはずである.空間データの入力ベクトル に対して次のようなニューロン結合を考える。
図では少しわかりにくいが、入力 と が 番目のニューロンに結合している。
全結合層では1つのニューロンは入力すべてと結合するが、局所受容野を持つ層では1つのニューロンは入力の一部(=局所受容野)としか結合しない。あるニューロンの出力 は局所受容野からの入力を受け取り、次のニューロンは別の局所受容野をスライドしてそこからの入力を受け取る。局所受容野の幅を 、局所受容野のスライド幅を とする。入力長を とすると出力数は であり、 が で割り切れる場合は最後の局所受容野が入力の端に一致する。上の図では である。すると
と表せる。つまり、 は局所受容野の から までの入力で計算される。
重み共有
局所受容野を用いて限定的なニューロン結合を行い、全結合層と同じ計算を採用すると次のように出力 を計算することになる。なお、活性化関数とバイアスは省略する。
このとき、重み は単純に 個だけ準備することになる。さらにパラメータ数を減らすために全ての局所受容野で同じ重みを用いることにする。この重みは局所受容野の重みと呼ばれ、局所受容野から特徴量を正しく抽出するように学習される。つまり、重み共有を行うと、出力 は次のように表せる。
重み共有を行うメリットは、パラメータ数が大幅に減ること以外にもデータの空間的なズレに対してロバストになるということだ。重み共有を行なわない層では例えば と のように少し位置がズレるだけで全く異なる重みを学習することになる。つまり、特徴量の内部表現的な意味はその位置に依存している。しかし、画像のような空間データに関してはそのようなズレを許容しないといけない。画像を少しズラしただけで大きく結果が変わってしまうと困るからである。重み共有をおこなうことで、局所受容野内の特徴量から新たな特徴量を抽出する術のみを学習し、局所受容野内の位置には依存こそすれど、空間データ内の位置には依存しないようにすることができる。局所受容野内および重み共有を採用した上のような計算を畳み込みという。また、畳み込みにおける局所受容野をカーネルあるいはフィルタと呼ぶ。
テンソルの導入
畳み込みは、1次元のベクトルに対して行うことを考えてきた。しかし、画像データのような3次元のデータに対して畳み込みを行うことを考えたい。そのために、まずテンソルに話を拡張しよう。
そもそも、画像データとは何次元のデータだろうか。全結合層はバッチを考えなければ、一次元データであるベクトルに対して適応されてきた。特徴量一つが一つの成分を表すベクトルである。すると画像は、ピクセルを縦横に並べた2次元のデータだろうか?一般的にはそうではない。画像は、ピクセルを縦横に並べた2次元の構造に加えて、1ピクセルが色の情報をベクトルで持つ。つまり、画像は3次元のデータである。例えば、RGB画像は、縦横のピクセルに加えて、色の情報を3成分のベクトルで持つ。つまり、画像は画像の横方向、縦方向、そして色方向の3次元である。これをテンソルと呼ぶ。テンソルは行列と同様に大きさが存在し、 と表す。一般的に画像は と表される。 は高さ、 は幅、 はチャンネル数である。RGB画像は で表される。(チャンネル軸を最初に置き と表す流儀もあるが、ここでは と表す。)グレースケール画像は で表される。テンソルは、行列やベクトルを一般化した概念であるが、本書では3次元以上のデータをテンソルと呼ぶことにする。 次元実数テンソル全体は である。
また、テンソルは添え字を指定することで行列やベクトルとして扱うこともできる。3次元テンソル に対して、
となる。特に、 はベクトルの行列と同一視できることは重要である。
畳み込み層
では畳み込みをテンソルに対して適応することを考える。まずは、純粋に拡張してみる。テンソル に対して畳み込みを適応し、出力 を得ることを考える。カーネルの大きさ(=カーネルサイズ)を 、カーネルのスライド幅をそれぞれの軸について とする。すると、カーネルの重みは となる。すると、出力は次のように表せる。
これで純粋な畳み込みが行える。しかし畳み込みニューラルネットワークで一般的に行われている畳み込みは少し異なる。まず、 方向と 方向で扱いが異なる。 方向には、1ピクセルの特徴量が含まれている。つまり、 方向については空間データではない。よって、 方向については畳み込みを行わないことにする。つまり、
とする。また、 は不要である。つまり、 方向をベクトルとして扱えることを思い出せば、
と定式化できる。すると、出力の大きさはどうなるだろうか?カーネルが入力内に完全に収まる位置をストライドごとに数えると、出力の大きさは次のように表せる。
ここで、 方向の大きさが になっていることに着目すると、画像の1ピクセルの特徴量が1つに圧縮されていることがわかる。しかし、これはあまり望ましくない。なぜなら、1ピクセルの表現力が失われてしまうからである。また、この計算は 方向については畳み込みを、 方向についてはすべての成分に対して異なる重みを用いてその総和を計算していることから1ニューロンをもつ全結合層の計算を行っていると解釈できる。つまり、全結合層と同じようにニューロンを増やすことで表現力を増すことができる。そのためには、 方向の各成分について異なる重みを準備し、複数回変換を行う必要がある。よって、異なる 個のカーネル を準備し、個々の出力 を得てみよう。
そして、この個々の出力は 方向が のテンソルであり、これは 次元テンソルつまりは行列として扱える。これを失われた 方向に結合することで、真の出力 を得ることができる。
このような計算により、入力テンソル に対して、出力テンソル を得ることができる。 は何枚のカーネルを用いるかを与えることになり、モデル設計の段階で決定する。ちょうど、全結合層における出力の次元数を決定するのと同じである。なので、ここで改めて入力チャンネル数 と出力チャンネル数 を定義する。また、入力 と重み のストライド による畳み込みを以下のように表すこととする。
さらに、バイアスについて考えてみよう。バイアスは、ニューロン1つが1つの値を持ち、入力を重みでスケールさせたのちに加えるスカラーであった。つまり、先ほどの出力 の全要素に同一のバイアス を加えればよい。改めてその出力は、
である。これが、畳み込み層の順伝播である。下図はそのイメージを図示したものである。
パディング
ここまではカーネルが入力内に完全に収まる位置だけで畳み込みを行った。この場合、入力サイズとストライドの組み合わせによっては端に使われない領域が残り、出力サイズも入力サイズと異なる。まず、パディングを行わない場合の出力の大きさは次のように表せる。
当然、このように出力を決めると、出力の大きさが入力と異なる。これ自体は問題にはならないが、出力のサイズが不安定になってしまう。ニューロンの大きさのように狙った大きさにしたいこともある。また、2の累乗や6の倍数といったわかりやすい大きさにすることが望ましい。そのような調整を可能にするため、パディングを導入する。パディングとは、入力の周りに対称になるように0を追加することである。このパディングを行った入力を用いて畳み込みを行うことで、出力の大きさを調整することができる。 方向のパディングの幅を とすると、 軸それぞれ だけ大きさが増えることになる。つまり、パディング後の入力に畳み込みを適用した出力の大きさは次のようになる。
特に用いられるのがセームパディング(Same Padding)である。セームパディングは、出力の大きさについてフィルタの大きさによるズレを解消するようにパディングをおこなうことである。出力サイズは となるようにする。 はそれぞれ を割り切れる値であることは仮定しておく。このような と を計算してみよう。
このような条件を満たす ならばSame Paddingになるが、ストライドが1でカーネルサイズ が偶数の場合、左右対称な整数の は存在しない。そのため、左右対称なSame Paddingでは奇数のカーネルサイズを使うことが望ましい。 のときは
を使うことが多い。当然、 についても同様の議論が可能である。いくつかのライブラリでは左右非対称なパディングを行ってSame Paddingを実装している。が、ここでは議論しないことにする。
畳み込みの高速化(img2col + GEMM)
先程定義した畳み込みは、すべての受容野に対してフィルタの数 だけ計算を行う必要がある。この場合、深いループが必要なことは明らかである。これを克服するために、img2colという手段を用いてテンソルを行列に変換し、畳み込みの演算を一般行列積(GEMM)として解釈するという手法をここでは議論したい。この議論はやや天下り的に行われるのでその正当化について重点的に議論する。具体的な議論に入る前に、感覚的な説明を行う。畳み込みでは重み共有を行っているという性質について、各受容野についておなじフィルタ演算を行っている。これは行列積においてある成分が演算に複数回用いられていることと対応している。また、各受容野では重みと成分の積の和を計算している。これは行列積においても同様であるのでこれは対応する。どうだろう、なんとなく行列積に変換できそうな気がしてきたのではないだろうか。では、実際にどのような変換を行うかを見てみよう。ここからの議論ではバイアスについて考えずに進める。なぜなら、バイアスは出力に対して加算を行うだけで適応できるからである。
まず、入力 と重み 及び出力 を今までどおり定義する。大きさは以下のように定義する。
ストライドを とし、これらの値は適切なものとする。まず、各受容野---つまりは の一部を次のように改めて定義する。
ここで、 はそれぞれ出力の高さと幅の添え字である。つまり、 は出力の 番目の成分に対応する受容野を表す。成分の書き下しではわかりにくいが、 方向に 回、 方向に 回スライドした受容野内の入力 の成分である。この先、このテンソルの 要素を と表記する。このテンソル内の要素ある順番で行ベクトル に並び替える。ここではこの順番を一般的な順番である の順番で並び替えるとする。これは先に でソートし各 内で についてソートし、最後に各 内で でソートする。要素をたどる方向は の順である。実際にやってみよう。
このように、受容野を要素数 の行ベクトルに変換することができた。 は次元数ではなく要素数であることに注意が必要だ。これを全ての受容野について行うと、 個の行ベクトルが得られる。これらを並べて の行列表現である を得る。なお、並べる順番は先程同様に決められるべきで、今回は の順番で行ベクトルを行列の 方向に追加していくことにする。つまり、
となる。これで入力テンソルを行列に変換することができた。次にフィルタの行列表現を得る。まず、入力の各受容野を行ベクトルに変換したのと同様に、各フィルタ を列ベクトル に変換する。このとき、同じ方法で並び替えることが必要だ。
このようにして、各フィルタを列ベクトルに変換することができた。これを全てのフィルタについて行うと、 個の列ベクトルが得られる。これらを並べて を得る。なお、並べる順番は の順番で列ベクトルを行列の 方向に追加していくことにする。つまり、
である。これでフィルタの行列表現を得ることができた。この行列はすべての 個のフィルタを内包していることに注意されたい。ここで と の行列積を考えよう。2つの行列の大きさが行列積の要件を満たしていることは明らかである。
このとき、 は次のように定義した行ベクトル を用いて書き下せる。
これは行列の定義より明らかである。最後にこれをテンソルに戻す。 を出力テンソル の 成分とすると、以下のような出力が得られる。
ではこの正当化について議論しよう。畳み込みの定義から以下のことが言える。
ここで、 は定義に沿った出力の成分であり、現在議論したいのはこれがimg2colを用いた出力の行列表示 において と一致することである。
さて、先程の式の右辺は、まさに と の標準内積である。または、前者が行ベクトルで後者が列ベクトルであることを思い出せば、行列積とも解釈できる。行ベクトル の定義を思い出せば
以上の議論からimg2colによって得られた行列表示を適切に変換したものが畳み込みの出力であることがわかる。よって、img2colを用いて畳み込みを行うことは正当化される。 ここで、再びバイアスを適応することを考えれば
となるように変えれば良い。すなわち、 を
に変えれば良い。すなわち、
という計算にすることでバイアスを適用することができる。以下に、このアルゴリズムにおけるテンソルと行列の変換を図示する。
定義による畳み込み層の逆伝播
ここまでで、畳み込み層の順伝播を見てきた。畳み込み層も深層学習の一部であるためにはやはり逆伝播が必要である。畳み込み層では学習可能なパラメータとしてフィルターの重み とバイアス がある。加えてより上位の層へ伝播する勾配を考えると、 を求める必要がある。ここでは、img2colの力を借りずに定義に基づいた逆伝播を導出してみよう。
まず、重み の逆伝播を考える。畳み込みの定義から
である。ここで、 の各成分は にのみ寄与し、他の には寄与しない。よって、
である。また、 は任意の について に寄与するので、
が成り立つ。見かけは非常に畳み込みににているが、この処理は純粋な畳み込みとは言えない。下にこの演算の図を示す。
重要なのは入力の、すべての成分ではなくストライド を幅とする飛び飛びの値について抜き出して の対応する(つまり異なる)成分との要素積をとっていることである。この一見奇妙な操作は、Dilating拡張を用いると畳み込み演算に帰着できる。
この演算が奇妙なのは では一つ隣の成分に対応する成分が ではストライドだけ離れた成分だからである。よって、 の各成分間にそれぞれ、 方向なら 個、 方向なら 個の を挿入する。数式的定義は以下のようになる。
なお、この行列の大きさについては と の定義により求められる。このように工夫すると、
が成立する。つまり、入力とDilating拡張を行った勾配のストライド の畳み込みにより、重みの勾配を求められた。以下にその図を示す。
さて、次に入力に対する逆伝播を考えよう。再び畳み込みの定義から始めよう。
ある入力の成分 が寄与する出力の成分は、 が
を満たしている必要がある。そもそも、 なので、 が寄与する出力の成分の範囲は
である。この条件を満たす について
が成り立つ。先ほど示したすべての についてこれが成り立つので
となる。ここで以下のようにあらためて を決める。
すると、
となる。 つぎに、再び を以下のように置き換える。
すると、
と変形できる。ただし、ここで の添字が整数でない場合や範囲外である場合は であるとする。 の成分と対応する の成分との積の和になっている。加えて、 はすべての成分が演算に使われている。よって非常に畳み込みに似ている。
いくつか修正して畳み込み演算できるようにしよう。まず、 の添字が、 を増加させると減少する一方で、 の添字は増加するという違いがある。これは を 方向についてフリップ(180°回転)させたものを使うことで解決できる。
次に、 の添字について修正を行う。 を1ずつ増やしても で割り算を行っているため、全体としては小数のままになっている。つまり、間に を挿入するDilating拡張を行う必要がある。また、画像の端と内側では順伝播の時点で寄与する成分の数が異なっている。なので、添字が範囲外になる可能性が存在している。これに対する解決策はパディングである。具体的には成分間に 方向に 個、 方向に 個の を挿入する。さらに 、 とおき、上側と左側にそれぞれ 、 個、下側と右側にそれぞれ 、 個の を追加する。
このように定義することで
が成り立つ。つまり、Dilating拡張とパディングを行った入力とフリップした重みのストライド の畳み込みにより、入力の勾配を求めることができる。
img2colによる畳み込み層の逆伝播
img2colを用いれば、畳み込みを行列積の計算で行えることを示した。img2colを用いて畳み込みを行う場合、逆伝播も同様の方法で行う方が効率的である。ここでは、逆伝播を行列積で表せることを示すのではなく、img2colのアルゴリズムから逆伝播を導出してみよう。まず、img2colの順伝播は以下の式で表せた。
この式はAffine層の順伝播と同様の計算になっている。よって、同様の議論によって を用いて や を求めることが可能である。一方でAffine層と違うのは、それぞれがデータの「行列表現」となっている部分である。つまり、 から への変換や から への変換が必要である。
なお、 については順伝播や逆伝播においても変換は必要ない。なぜなら、順伝播でも逆伝播でも という形でしか用いず、 というテンソルの形では利用しない。対して、 や は画像を表しているべきで、 は のピクセルを重複して保持しており適さず、 はピクセルの空間的距離が保存されておらず適さない。また、 は上層の畳み込み層の になり得ない。
では、まずは行列積計算の逆伝播を考えよう。Affine層での議論をそのまま行うことで以下が示せる。
次に から の変換を考えよう。これは、順伝播で行った変換の逆を素直に行えば良い。
これは、ベクトルの向きと位置を組み直せば簡単に行える。しかし、 の への変換は成分の入れ替えでは済まない。なぜなら、 は の成分を重複して保存しているからである。逆伝播ではこれをどのように扱えばいいだろうか。順伝播にて重複して保存しているという事象は、元の成分の 倍を異なる成分に寄与させているというように解釈できる。例えば の が の 成分と 成分に重複して保存されているとすると当然
であるので、
となる。つまり、対応する成分のすべての和になる。これを定式化することは非常に難しいので複数ステップに分けて行う。
-
をすべて0で初期化する。
-
から1行取得する。
-
img2colで行った「局所受容野のベクトルへの変換」の逆を行う。
-
対応する の局所受容野に3で求めた値を加える。
-
の次の行を考える。 の局所受容野についてストライドだけずらす。
-
2に戻る。 の次の行がない場合は終了。
つまり、img2colで行っていた変換の逆の操作を行い、重なった部分の和をとる。これにより、 から を求めることができた。