"BOKU"のITな日常

BOKUが勉強したり、考えたことを頭の整理を兼ねてまとめてます。

全結合層(Affine)について数式なしでざっくり整理/ディープラーニング

f:id:arakan_no_boku:20190330090249j:plain

ディープラーニングの「全結合層」について、ざっくり整理します。

目次

全結合層はAffineレイヤー

Neural Network Consoleの場合は「Affine」レイヤーが「全結合層」にあたります。

Affineは、Neural Network Consoleレイヤーリファレンスには、こう説明されてます。

全ての入力値から、OutShapeプロパティで指定する全ての出力ニューロンへの結合を持つ全結合層です。

o = Wi+b

(iは入力、oは出力、Wは重み、bはバイアス項を示す)

なるほど。

Affineってのは、「o = Wi+b」という計算をした結果を、出力する役目で、その計算は「全ての入力値」から「全ての出力」への結合をもつ・・ということはわかります。 

この説明だけで

o = Wi+b (iは入力、oは出力、Wは重み、bはバイアス項を示す)

という計算式が「全ての入力値から全ての出力への結合」になることがわかる方は、この記事を続けて読む必要はないです(笑)

わからない方だけ、続きをどうぞ。  

利用するのは行列データ

上記の「o = Wi+b」の式に登場する「o」も「W」も「i」も行列データです。

行列データってこんな感じのやつです。

見やすくするため「6×6」のサイズの例にしてます。 

f:id:arakan_no_boku:20181029194959j:plain

行列の内積計算について

上記計算式の「Wi」は、この行列どうしの掛け算を意味しますが、単純な「W × i」ではありません。

「W」と「i」の内積(ドット積)という特殊な方法で計算します。

行列の内積(ドット積)計算の仕方を説明します。

計算する過程を、さらに見やすくするため、2×2の行列の例にしてます。

f:id:arakan_no_boku:20181029195357j:plain

左側の行(横)と右側の列(縦)を掛け合わせて足して、結果の数値をもとめるのが、行列の内積計算です。

例えば、上の例だと。

左上の「19」は、「(1 × 5) + (2 × 7)」。

左下の「43」は、「(3 × 5) + (4 × 7)」

右上の「22」は、「(1 × 6) + (2 × 8)」

右下の「50」は、「(3 × 6) + (4 × 8)」

と計算してるわけです。

ちなみに、全結合層の「+b」は「Wi」の結果に固定値の行列データ「b」を足します。

この「b」は、Wiの結果が極端に小さな値でエラーになるとかを防ぐための保険みたいなものと考えるとわかりやすいです。

こういう行列同士の「Wi+b 」みたいな演算のことを「Affine変換」というみたいで、

だからレイヤーの名前が「Affine」なのです。 

全結合にするには一次元への変換が必要 

上記計算例は「2 × 2」の行列で計算しました。

それを見て「全結合じゃねーじゃないか」と思った人、当たりです。

上記例だと、左側の上2つ、下2つずつしか一度に計算してませんから、「全結合」いにはなってません。

全結合にするには1次元にして計算する必要があります。

例えば、(28,28,1)=幅28×高さ28×1チャンネルのデータなら、一旦 28×28×1=>784のサイズの1次元・・以下のようなイメージに変換するわけです。

f:id:arakan_no_boku:20181029005046j:plain

1次元にすると全結合になるイメージをつかむため、超シンプルなモデルで計算してみます。(784とかで書くと、とても見づらいので)

用意したのは、以下の2×3の小さな行列2つです。

f:id:arakan_no_boku:20190330094540j:plain

これを、一次元に変換すると、以下のようになります。

f:id:arakan_no_boku:20181029225407j:plain

これにウエイトを掛け合わせて出力するサイズは「3」にします。

かけあわせるW(ウエイト)は、単純化のため全部0.5にしてやっていますが、別に全部が同じ数字である必要などありません。

まず、入力①のほう。

f:id:arakan_no_boku:20181029231038j:plain

結果の128は「0*0.5+255*0.5+0*0.5+0*0.5+0*0.5+0*0.5」で計算して求めています。横×縦の計算ですから、1次元だと一度の計算で左側の全部が対象になるため「入力すべてと、ウエイトを掛け合わせる」=「全結合」になるわけです。

これがマニュアルに書いてある「全ての入力値から、OutShapeプロパティで指定する全ての出力ニューロンへの結合を持つ」のイメージです。 

続けて、入力②の方も計算してみます。

f:id:arakan_no_boku:20181029231152j:plain

こちらは、「0*0.5+0*0.5+0*0.5+0*0.5+255*0.5+0*0.5」で128です。

全結合で計算すると何がいいのか

じゃあ。

こうすることで何がうれしいのかというと、2つのデータを単純比較するのではなく、「特徴」を抽出して似ている=ほぼ同じという比較ができるようになることです。

具体例でいうと、1次元に変換する前は、上記の入力①と入力②こんな感じなので、単純比較すると、まったく違うデータになります。

f:id:arakan_no_boku:20190330094540j:plain

でも上記のように、1次元展開して全結合で計算をすることで、入力①②にあった位置関係のずれによる誤差を吸収して「0が5つと255がひとつで構成されている」という共通の特徴を際立たせて「似ている=ほぼ同じ」判定に結び付ける「ひとつの方法論」になりえるのは、イメージできると思います。

もちろん、上記は極端で何ら実用性のない例で、あくまでイメージでしかありませんけど、数学的な説明でモヤモヤしてた部分が、こういうイメージを持てたことで、すっと腹に落ちたという経験をふまえて、あえて、こんな整理の仕方をしてみました。

邪道?かもしれませんけど。 

行列計算の基本的なルールを補足 

さて、ここからは、行列計算の基本的なルールについて補足しときます。

上にでてきた

f:id:arakan_no_boku:20181029231152j:plain

この図で、なんで1行6列の入力、1行3列の出力に対して、真ん中が6行3列になっているのか?について、行列演算の基本ルールをふまえて説明しておきます。

すでに知っている方はスキップしてください。

基本ルールは2つです。

  • 行列の内積計算は、左側が「2行3列」なら「3行2列」みたいに内側の数が一致してないと計算できません。
  • 出力サイズは、「2行3列」×「3行2列」なら「2行2列」になるみたいに、外側の数で決まります。

これを、図にするとこんな感じになります。f:id:arakan_no_boku:20181030000816j:plain

なので。

入力が「6」。

出力を「3」。

そう決めた時点で、掛け合わせる相手(真ん中)は「6行3列」に決まるわけです。

f:id:arakan_no_boku:20181030001119j:plain

今回は、こんなところで。

ではでは。