"BOKU"のITな日常

還暦越えの文系システムエンジニアの”BOKU”は新しいことが大好きです。

全結合層(Affine)の仕組を、数式なしでざっくり整理してみた/ディープラーニング

ディープラーニングで頻繁に登場する「全結合層」は、Neural Network Consoleの場合は「Affine」レイヤーにあたります。

この仕組というか、動作のイメージを頭の整理を兼ねて、ざっくり整理しておこう。

それが今回のテーマです。

f:id:arakan_no_boku:20190330090249j:plain

 

一番基本的なレイヤー「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もWもiも行列データであって、これは「行列計算」であるということ。

かつ、「Wi」の部分は「W × i」ではなくて「W ・ i」・・つまり「内積計算」であるということが、パッとわからないと、結構苦しんだりするわけです。

 

行列データ?内積計算?って人向けに

 

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

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

f:id:arakan_no_boku:20181029194959j:plain

さて、行列のイメージがわかったら、計算です。

行列の内積計算の計算の仕方を簡単に書きます。

計算する過程を、さらに見やすくするため、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つずつしか一度に計算してませんから、「全結合」いにはなってません。

じゃあ・・全結合にするにはどうするのか?というと、実際の全結合層では、平にならして計算するみたいです。

平にする・・とは、例えば、(28,28,1)=幅28×高さ28×1チャンネルのデータなら、一旦 28×28×1=>784のサイズの1次元にするということです。

以下みたいなイメージですね。

f:id:arakan_no_boku:20181029005046j:plain

なんで、平にならすと「全結合」になるのか?

そのイメージをつかむため、超シンプルなモデルにして計算してみます。

(784とかで書くと、とても見づらいので)

 

計算例

 

入力サイズ「6」の小さな2種類のデータにしてみます。

f:id:arakan_no_boku:20181029225407j:plain

これは、元々縦×横の行列だったものを、展開してこうなったとみてください。

出力サイズは「3」にします。

かけあわせるW(ウエイト)は、単純化のため全部0.5にしてやってみます。

まず、入力①のほう。

f:id:arakan_no_boku:20181029231038j:plain

横×縦の計算ですから、1次元だと、一度の計算で左側の全部が対象になります。

上記だと、結果の128は、「0*0.5+255*0.5+0*0.5+0*0.5+0*0.5+0*0.5」で計算して求めているわけです。

入力すべてと、ウエイトを掛け合わせる。

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

さて。

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

f:id:arakan_no_boku:20181029231152j:plain

 

かなり、わざとらしいですが・・(笑)

①と②で、結果の数字が同じになってます。

実は、この結果の数字が同じになる・・ってのが、肝らしいのですよね。

 

なんで全結合をするとよいのか

 

上記の入力①と入力②の展開する前の図はこんな感じになります。

f:id:arakan_no_boku:20190330094540j:plain

単純に比較すると、255の出現位置がずれている全く違う画像です。

これを違うものとして認識するなら、ディープラーニングなんかいりません。

普通に画像を比較するプログラムで十分です。

でも、この2つを「同じもの」として分類できないといけない・・こうなると、急に難しくなるわけです。

単純比較ではなくて「特徴」をつかんで比較しないといけなくなるのです。

でも、上記のようなやり方で一旦平にならして計算すると同じ結果になるということは、入力①②にあった位置関係のずれによる誤差が吸収されて、共通の特徴を認識しやすくなる・・という風に言えると思います。

まあ、実際はこんな風に全部が同じ数値のW(ウエイト)なんて、まず使いませんから、結果が同じになるようなことはないので、イメージでしかないですが。

たぶん、これが「全結合層が必要な理由」・・なんだろうな。

個人的にはそう理解してます。

もっとも。

自分は文系なので、学校で機械学習について、ちゃんと学んだわけでもありません。

ディープラーニングの本を読んでも、こんなことまでは書いてありません。

あくまで、上記は自分が実際に計算して確かめた経験からの話ですから、バリバリの理系の方から「論文と違う」みたいな指摘があるかもしれないのはご容赦ください。 

 

ちょっと補足です

 

今回の記事のテーマについては、前章で終わりです。

でも。

上記の例を見て、なんで真ん中のW(ウエイト)の部分が3列もあるんだ?とわからない人もいると思うので、行列計算の基本的なルールについて補足しときます。

実は。

行列の内積計算は、左側が「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

さて。  

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