Pythonのたたみ込みネットワーク。 パート2.モデルトレーニングの式の導出







前回の記事では、将来のモデルを構成するすべてのレイヤーと機能を概念的に検討しました。 今日は、このモデルのトレーニングを担当する公式を導き出します。 損失関数から始めて畳み込み層で終わる、逆順で層を解析します。 数式を理解するのが困難な場合は、エラーの逆伝播法の詳細な説明 (写真)を理解し、 複雑な関数の微分規則を覚えておくことをお勧めします。



損失関数を介したエラーの逆伝播の公式の導出



これは、損失関数の偏微分です。 E モデル出力。





 beginarrayrcl dfrac partialE partialyli= dfrac partial frac12 sumni=0ytruthiyli2 partialyli= dfrac partial frac12ytruth0yl02+ ldots+ytruthiyli2+ ldots+ytruthnyln2 partialyli= dfrac partial frac12ytruthiyli2\パyli= frac12 cdot2 cdotytruthiyli21 cdot frac partialytruthiyli partialyli=ytruthiyli cdot1=yliyi endarray







デリバティブ  partial frac12yiyli2 分子では、複素関数の導関数として扱います。 un=nun1 cdotu 。 ここで、ところで、人はどのように見ることができます  frac12 そして 2 、そして最初に式に追加した理由が明らかになります  frac12



最初は標準偏差を使用しましたが、分類問題にはクロスエントロピーを使用する方が適切です(説明付きのリンク )。 以下は、backpropの式です。可能な限り詳細な式の出力を記述しようとしました。





 beginarrayrcl dfrac partialE partialyli= dfrac partial sumni=0ytruthi cdotlnyli partialyli= dfrac partialytruth0lnyl0+ ldots+ytruthilnyli+ ldots+ytruthnlnyln partialyli= dfrac partialytruthilnyli partialyli= dfracytruthiyli endarray







覚えておいて  largelnx= frac1x



アクティベーション関数を介したbackprop式の導出

... ReLU経由







f '_ {ReLU} = \ frac {\ mathrm {\ partial} y ^ l_i} {\ mathrm {\ partial} x ^ l_i} = \ left \ {\ begin {matrix} 1、&if \ enspace x ^ l_i> 0 \\ 0、その他の場合\\ \ end {matrix} \ right。

f '_ {ReLU} = \ frac {\ mathrm {\ partial} y ^ l_i} {\ mathrm {\ partial} x ^ l_i} = \ left \ {\ begin {matrix} 1、&if \ enspace x ^ l_i> 0 \\ 0、その他の場合\\ \ end {matrix} \ right。







どこで  large frac partialyli partialxli -アクティベーション機能によるバックプロップの指定。



つまり、アクティベーション関数を直接通過する際に最大で選択された要素にエラーを渡し(前のレイヤーからのエラーを1倍します)、選択されなかったため結果に影響を与えなかった要素にはパスしません(乗算します)前のレイヤーからゼロへのエラー)。



... シグモイドを通して







 beginarrayrclfsigmoid= dfrac mathrm partial mathrm partialxli left dfrac11+exli right= dfrac mathrm partial mathrm partialxli1+exli1=1+exli2exli= dfracexli1+exli2= dfrac11+exli cdot dfracexli1+exli= dfrac11+exli cdot dfrac1+exli11+exli= dfrac11+exli cdot left dfrac1+exli1+exli dfrac11+exli\右= dfrac11+exli cdot left1 dfrac11+exli right=fsigmoid cdot1fsigmoid\終







ここで覚えておく必要があります eux=eux cdotux

同時に  largefsigmoid= frac11+exli シグモイド式です



さらに示す  large frac partialE partialxli どうやって \大\デli (どこ  large frac partialE partialxli= frac partialE partialyli frac partialyli partialxli



...また、softmax経由 (またはこちら



i番目の出力のsoftmax関数はその計算だけに依存しないため、これらの計算はもう少し複雑に思えました。 xli 他のすべてからも xlj\エ forallij in0...n 、その合計は、ネットワークを直接通過する公式の分母にあります。 したがって、backpropの式は2つに「分割」されます。 xli そして xlj





 beginarrayrcl dfrac partialyli partialxli= dfrac partial partialxli left dfracexli sumnk=0exlk\右= dfracexli cdot sumnk=0exlkexli cdotexli left sumnk=0exlk\右2= dfracexli cdot left sumnk=0exlkexli\右 sumnk=0exlk cdot sumnk=0exlk=yli cdot dfrac sumnk=0exlkexli sumnk=0exlk=yli cdot left dfrac sumnk=0exlk sumnk=0exlk dfracexli sumnk=0exlk\右=yli cdot1yli endarray







式を適用します \大\左 fracuv\右= fracuvuvv2 どこで u=exli そして  largev= sumnk=0exlk

同時に  large frac partial partialxli sumnk=0exlk= frac partialexl0+ ldots+exli+ ldots+exln partialxli= frac partialexli partialxli=exli



そして、の偏微分 xlj





 beginarrayrcl dfrac partialyli partialxlj= dfrac partial partialxlj left dfracexli sumnk=0exlk\右= dfrac0 cdot sumnk=0exlkexli cdotexlj left sumnk=0exlk right2 \&= dfracexli cdotexlj sumnk=0exlk cdot sumnk=0exlk=yli cdotylj endarray







上記の式に基づいて、エラーが逆方向に伝播するときに関数が(コードで)返す必要のあるニュアンスがあります  large fracylxl この場合、1つを計算するため、softmaxで yli すべて使用されています xl または、言い換えれば、それぞれ xli すべてに影響する yl







ソフトマックスの場合  large frac partialE partialxli 等しくなります  large sumnk=0 frac partialE partialylk frac partialylk partialxli (金額が登場しました!)、つまり:





 frac partialE partialxli= frac partialE partialyl0 frac partialyl0 partialxli+...+ frac partialE partialyl1 frac partialyl1 partialxli+...+ frac partialE partialyln frac partialyln partialxli qquad foralli in0...n







この場合、値  large frac partialE partialylk すべてのために k 損失関数を介してこのバックプロップがあります。 見つけることが残っている  large frac partialylk partialxli すべてのために k そしてすべて i -つまり、マトリックスです。 展開された形式の行列乗算の下  large frac partialylk partialxli -行列と行列の乗算はどこから来ますか。





 beginbmatrix frac partialE partialxl0 frac partialE partialxl1.. frac partialE partialxln endbmatrix== scriptsize beginbmatrix frac partialE\部yl0 frac partialyl0 partialxl0+ frac partialE partialyl1 frac partialyl1 partialxl0+ ldots+ frac partialE\部yln frac partialyln partialxl0 frac partialE partialyl0 frac partialyl0 partialxl1+ frac partialE\部yl1 frac partialyl1 partialxl1+ ldots+ frac partialE partialyln frac partialyln partialxl1... frac\部E\部yl0 frac\部yl0\部xln+ frac\部E\部yl1 frac\部yl1\部xln+ ldots+ frac\部E\部yln frac\部yln\部xln endbmatrix= beginbmatrix frac partialE partialyl0 frac partialE partialyl1... frac partialE partialyln endbmatrix beginbmatrix frac partialyl0 partialxl0 frac partialyl0 partialxl1... frac partialyl0 partialxln frac partialyl1 partialxl0 frac partialyl1 partialxl1... frac partialyl1 partialxln............ frac partialyln partialxl0 frac partialyln partialxl1... frac\部yln\部xln endbmatrix







それは分解のこの最後の行列についてでした-  large frac partialyl partialxl 。 行列を乗算する方法をご覧ください  large frac partialE partialyl そして  large frac partialyl partialxl 私たちは得る  large frac partialE partialxl 。 したがって、softmaxのbackprop関数(コード内)の出力は行列でなければなりません  large frac partialyl partialxl 、その時点で既に計算されているものを掛けたとき  large frac partialE partialyl 取得します  large frac partialE partialxl



完全に接続されたネットワークを介したバックプロップ









重み行列を更新するためのbackprop式の出力 wl fcネットワーク







\ begin {array} {rcl} \ dfrac {\ partial E} {\ partial w ^ l_ {ki}}&=&\ dfrac {\ partial E} {\ partial y ^ l_i} \ dfrac {\ partial y ^ l_i} {\ partial x ^ l_i} \ dfrac {\ partial x ^ l_i} {\ partial w ^ l_ {ki}} = \ delta ^ l_i \ cdot \ dfrac {\ partial x ^ l_i} {\ partial w ^ l_ {ki}} = \ delta ^ l_i \ cdot \ dfrac {\ partial \ left(\ sum ^ m_ {k '= 0} w ^ l_ {k'i} y ^ {l-1} _ {k'} + b ^ l_i \ right)} {\ partial w ^ l_ {ki}} \\&=&\ delta ^ l_i \ cdot \ dfrac {\ partial \ left(w ^ l_ {0i} y ^ {l-1} _ {0} + \ ldots + w ^ l_ {ki} y ^ {l-1} _ {k} + ... w ^ l_ {mi} y ^ {l-1} _ {m} + b ^ l_i \右)} {\ partial w ^ l_ {ki}} = \ delta ^ l_i \ cdot y ^ {l-1} _k \\ && \ forall i \ in(0、...、n)\ enspace \ forall k \ in(0、...、m)\ end {array}

\ begin {array} {rcl} \ dfrac {\ partial E} {\ partial w ^ l_ {ki}}&=&\ dfrac {\ partial E} {\ partial y ^ l_i} \ dfrac {\ partial y ^ l_i} {\ partial x ^ l_i} \ dfrac {\ partial x ^ l_i} {\ partial w ^ l_ {ki}} = \ delta ^ l_i \ cdot \ dfrac {\ partial x ^ l_i} {\ partial w ^ l_ {ki}} = \ delta ^ l_i \ cdot \ dfrac {\ partial \ left(\ sum ^ m_ {k '= 0} w ^ l_ {k'i} y ^ {l-1} _ {k'} + b ^ l_i \ right)} {\ partial w ^ l_ {ki}} \\&=&\ delta ^ l_i \ cdot \ dfrac {\ partial \ left(w ^ l_ {0i} y ^ {l-1} _ {0} + \ ldots + w ^ l_ {ki} y ^ {l-1} _ {k} + ... w ^ l_ {mi} y ^ {l-1} _ {m} + b ^ l_i \右)} {\ partial w ^ l_ {ki}} = \ delta ^ l_i \ cdot y ^ {l-1} _k \\ && \ forall i \ in(0、...、n)\ enspace \ forall k \ in(0、...、m)\ end {array}







分子の合計を展開し、すべての偏微分がゼロに等しいことを取得します  large frac partialwlkiyl1k partialwlki それは等しい yl1k 。 このケースは次の場合に発生します k=k 。 バーは、ここで「内部」サイクルを示します k 、つまり、これはまったく関連しないイテレータです k から  large frac partialE partialwlki



そのため、マトリックス形式で表示されます。





 frac partialE partialwl= leftyl1 rightT cdot deltal tinym timesn enspace\エ\エm\回1\エ\エ1\回n







マトリックス次元 yl1 等しい 1\回m 、および行列乗算を生成するには、行列を転置する必要があります。 以下では、マトリックスを完全に「拡張された」形式で表示し、計算がより明確に見えるようにします。





 beginbmatrix frac partialE partialwl00 frac partialE partialwl01.. frac partialE partialwl0n frac partialE partialwl10 frac partialE partialwl11... frac partialE partialwl1n........... frac partialE partialwlm0 frac partialE partialwlm1... frac partialE partialwlmn endbmatrix= beginbmatrixyl10 deltal0yl10 deltal1...yl10 deltalnyl11 deltal0yl11 deltal1...yl11 deltaln...........yl1m deltal0yl1m deltal1..yl1m deltaln endbmatrix qquad qquad qquad qquad qquad qquad= beginbmatrixyl10yl11...yl1m endbmatrix beginbmatrix deltal0 deltal1... deltaln endbmatrix









マトリックスを更新するためのbackprop式の出力 bl



バイアスについては、すべての計算は前の段落と非常に似ています。





\ begin {array} {rcl} \ dfrac {\ partial E} {\ partial b ^ l_ {i}}&=&\ dfrac {\ partial E} {\ partial y ^ l_i} \ dfrac {\ partial y ^ l_i} {\ partial x ^ l_i} \ dfrac {\ partial x ^ l_i} {\ partial b ^ l_ {i}} = \ delta ^ l_i \ cdot \ dfrac {\ partial x ^ l_i} {\ partial b ^ l_ {i}} \\&=&\ delta ^ l_i \ cdot \ dfrac {\ partial \ left(\ sum ^ m_ {k '= 0} w ^ l_ {k'i} y ^ {l-1} _ { k '} + b ^ l_i \ right)} {\ partial b ^ l_ {i}} = \ delta ^ l_i \\ && \ forall i \ in(0、...、n)\ end {array}

\ begin {array} {rcl} \ dfrac {\ partial E} {\ partial b ^ l_ {i}}&=&\ dfrac {\ partial E} {\ partial y ^ l_i} \ dfrac {\ partial y ^ l_i} {\ partial x ^ l_i} \ dfrac {\ partial x ^ l_i} {\ partial b ^ l_ {i}} = \ delta ^ l_i \ cdot \ dfrac {\ partial x ^ l_i} {\ partial b ^ l_ {i}} \\&=&\ delta ^ l_i \ cdot \ dfrac {\ partial \ left(\ sum ^ m_ {k '= 0} w ^ l_ {k'i} y ^ {l-1} _ { k '} + b ^ l_i \ right)} {\ partial b ^ l_ {i}} = \ delta ^ l_i \\ && \ forall i \ in(0、...、n)\ end {array}







それは明らかです  large frac partial left summk=0wlkiyl1k+bli right\部bli=1



マトリックス形式では、すべてが非常に単純です。





 frac partialE partialbl= deltal tiny1 timesn enspace enspace1 timesn







を介した逆プロップ式の導出 yl1



次の式では、 i 各という事実から生じる yl1k それぞれに接続されている xli (レイヤーは完全に接続されていると呼ばれることに注意してください





\ begin {array} {rcl} \ dfrac {\ partial E} {\ partial y ^ {l-1} _ {k}}&=&\ sum_ {i = 0} ^ {n} \ delta ^ l_i \ cdot \ dfrac {\ partial x ^ l_i} {\ partial y ^ {l-1} _ {k}} = \ sum_ {i = 0} ^ {n} \ delta ^ l_i \ cdot \ dfrac {\ partial \ left (\ sum ^ m_ {k '= 0} w ^ l_ {k'i} y ^ {l-1} _ {k'} + b ^ l_i \ right)} {\ partial y ^ {l-1} _ {k}} \\&=&\ sum_ {i = 0} ^ {n} \ delta ^ l_i \ cdot \ dfrac {\ partial \ left(w ^ l_ {0i} y ^ {l-1} _ {0 } + \ ldots + w ^ l_ {ki} y ^ {l-1} _ {k} + ... w ^ l_ {mi} y ^ {l-1} _ {m} + b ^ l_i \右) } {\ partial y ^ {l-1} _ {k}} \\&=&\ sum_ {i = 0} ^ {n} \ delta ^ l_i \ cdot w ^ l_ {ki} \\ && \ forall i \ in(0、...、n)\ enspace \ forall k \ in(0、...、m)\ end {array}







分子を分解すると、すべての偏微分がゼロに等しいことがわかります。ただし、 k=k





 frac partial leftwl0iyl10+ ldots+wlkiyl1k+...wlmiyl1m+bli right partialyl1k= frac partialwlkiyl1k partialyl1k=wlki







そして、マトリックス形式で:





 frac partialE partialyl1= deltal cdotwlT tiny1 timesm enspace enspace\エ1\回n\エn\回m







さらに、「オープン」形式のマトリックス。 私は意図的に、転置する前の形で最新の行列のインデックスを残したので、転置後にどの要素がどこに行ったのかを確認した方がよいことに注意してください。





 beginbmatrix frac partialE partialyl10 frac partialE partialyl11... frac partialE partialyl1m endbmatrix= scriptsize beginbmatrix deltal1wl00+ deltal2wl01+ ldots+ deltalnwl0n deltal1wl10+ deltal2wl11+ ldots+ deltalnwl1n.. deltal1wlm0+ deltal2wlm1+ ldots+ deltalnwlmn endbmatrix= enspace enspace= beginbmatrix deltal0 deltal1... deltaln endbmatrix beginbmatrixwl00wl01...wl0nwl10wl11...wl1n............wlm0wlm1...wlmn endbmatrixT= beginbmatrix deltal0 deltal1... deltaln endbmatrix beginbmatrixwl00wl10...wlm0wl01wl11...wlm1............wl0nwl1n...wlmn endbmatrix







さらに示す  large frac partialE partialyl1k どうやって  deltal1k 、および完全に接続されたネットワークの後続のレイヤーを介したエラーの逆伝播のすべての式は、同様の方法で計算されます。



maxpoolingによるバックプロップ



エラーは、maxpoolingステップで最大として選択された元のマトリックスの値のみを「通過」します。 マトリックスの残りのエラー値はゼロになります(これらの要素の値は、ネットワークを直接通過する際にmaxpooling関数によって選択されなかったため、最終結果に影響しなかったため、論理的です)。







Pythonのmaxpoolingの実装は次のとおりです。



code_demo_maxpool.py
gitリンク

import numpy as np y_l = np.array([ [1,0,2,3], [4,6,6,8], [3,1,1,0], [1,2,2,4]]) other_parameters={ 'convolution':False, 'stride':2, 'center_window':(0,0), 'window_shape':(2,2) } def maxpool(y_l, conv_params): indexes_a, indexes_b = create_indexes(size_axis=conv_params['window_shape'], center_w_l=conv_params['center_window']) stride = conv_params['stride'] #          y_l_mp = np.zeros((1,1)) #  y_l    y_l_mp_to_y_l = np.zeros((1,1), dtype='<U32') #   backprop    (    ) #          if conv_params['convolution']: g = 1 #   else: g = -1 #   #   i  j   y_l  ,        for i in range(y_l.shape[0]): for j in range(y_l.shape[1]): result = -np.inf element_exists = False for a in indexes_a: for b in indexes_b: # ,        if i*stride - g*a >= 0 and j*stride - g*b >= 0 \ and i*stride - g*a < y_l.shape[0] and j*stride - g*b < y_l.shape[1]: if y_l[i*stride - g*a][j*stride - g*b] > result: result = y_l[i*stride - g*a][j*stride - g*b] i_back = i*stride - g*a j_back = j*stride - g*b element_exists = True #       ,    i  j    if element_exists: if i >= y_l_mp.shape[0]: #  ,    y_l_mp = np.vstack((y_l_mp, np.zeros(y_l_mp.shape[1]))) #  y_l_mp_to_y_l    y_l_mp y_l_mp_to_y_l = np.vstack((y_l_mp_to_y_l, np.zeros(y_l_mp_to_y_l.shape[1]))) if j >= y_l_mp.shape[1]: #  ,    y_l_mp = np.hstack((y_l_mp, np.zeros((y_l_mp.shape[0],1)))) y_l_mp_to_y_l = np.hstack((y_l_mp_to_y_l, np.zeros((y_l_mp_to_y_l.shape[0],1)))) y_l_mp[i][j] = result #   y_l_mp_to_y_l   , #          y_l y_l_mp_to_y_l[i][j] = str(i_back) + ',' + str(j_back) return y_l_mp, y_l_mp_to_y_l def create_axis_indexes(size_axis, center_w_l): coordinates = [] for i in range(-center_w_l, size_axis-center_w_l): coordinates.append(i) return coordinates def create_indexes(size_axis, center_w_l): #              coordinates_a = create_axis_indexes(size_axis=size_axis[0], center_w_l=center_w_l[0]) coordinates_b = create_axis_indexes(size_axis=size_axis[1], center_w_l=center_w_l[1]) return coordinates_a, coordinates_b out_maxpooling = maxpool(y_l, other_parameters) print(' :', '\n', out_maxpooling[0]) print('\n', '    backprop:', '\n', out_maxpooling[1])
      
      





スクリプト出力の例








関数が返す2番目の行列は、maxpooling操作中に元の行列から選択された要素の座標です。



畳み込みネットワークを介したバックプロップ









畳み込みカーネルを更新するためのbackprop式の出力







\ begin {array} {rcl} \ dfrac {\ partial E} {\ partial w ^ l_ {ab}}&=&\ sum_ {i} \ sum_ {j} \ dfrac {\ partial E} {\ partial y ^ l_ {ij}} \ dfrac {\ partial y ^ l_ {ij}} {\ partial x ^ l_ {ij}} \ dfrac {\ partial x ^ l_ {ij}} {\ partial w ^ l_ {ab}} \\&=&^ {(1)} \ sum_ {i} \ sum_ {j} \ dfrac {\ partial y} l_ {ij}} \ dfrac {\ partial y ^ l_ {ij}} {\ partial x ^ l_ {ij}} \ cdot \ dfrac {\ partial \ left(\ sum_ {a '=-\ infty} ^ {+ \ infty} \ sum_ {b' =-\ infty} ^ {+ \ infty} w ^ l_ {a'b '} \ cdot y ^ {l-1} _ {(is-a')(js-b ')} + b ^ l \ right)} {\ partial w ^ l_ { ab}} \\&=&^ {(2)} \ sum_ {i} \ sum_ {j} \ dfrac {\ partial E} {\ partial y ^ l_ {ij}} \ dfrac {\ partial y ^ l_ { ij}} {\ partial x ^ l_ {ij}} \ cdot y ^ {l-1} _ {(is-a)(js-b)} \\ && \ forall a \ in(-\ infty、.. 。、+ \ infty)\ enspace \ forall b \ in(-\ infty、...、+ \ infty)\ end {array}







(1)ここで、単に式を xlij なでる a そして b 別のイテレータであることを意味します。

(2)ここで、分子の量を次のようにレイアウトします。 a そして b





 small sumi sumj frac partialE partialylij frac partialylij partialxlij frac partial leftwl infty infty cdotyl1is+ inftyjs+ infty+ ldots+wlab cdotyl1isajsb+ ldots+wl infty infty cdotyl1is inftyjs infty+bl right partialwlab







つまり、分子内のすべての偏微分。ただし、 a=ab=b ゼロに等しくなります。 同時に  large frac partialwlab cdotyl1isajsb partialwlab 等しい yl1isajsb



上記のすべてが畳み込みに適用されます。 相互相関のバックプロップ式は、次の場合の符号の変更を除いて似ています a そして b





 frac partialE partialwlab= sumi sumj frac partialE partialylij frac\部ylij\部xlij cdotyl1is+aj+b







ここで重要なのは、畳み込みカーネル自体が最終式に含まれていないことです。 一種の畳み込み演算がありますが、すでに参加しています  large frac partialE partialxlij そして yl1 、およびコアとして機能します  large frac partialE partialxlij 、それでも、特にステップ値が1より大きい場合、畳み込みのようには見えません。  large frac partialE partialxlij 「破る」 yl1 、これは通常の畳み込みに完全には似ていません。 この「崩壊」は、パラメーターが i そして j 数式のループ内で繰り返します。 デモコードを使用すると、これがすべてどのように見えるかを確認できます。



code_demo_convolution_back_dEdw_l.py
gitリンク

 import numpy as np w_l_shape = (2,2) #  stride = 1 dEdx_l = np.array([ [1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16]]) #  stride = 2  'convolution':False (   - x_l   ) # dEdx_l = np.array([ # [1,2], # [3,4]]) #  stride = 2  'convolution':True # dEdx_l = np.array([ # [1,2,3], # [4,5,6], # [7,8,9]]) y_l_minus_1 = np.zeros((4,4)) other_parameters={ 'convolution':True, 'stride':1, 'center_w_l':(0,0) } def convolution_back_dEdw_l(y_l_minus_1, w_l_shape, dEdx_l, conv_params): indexes_a, indexes_b = create_indexes(size_axis=w_l_shape, center_w_l=conv_params['center_w_l']) stride = conv_params['stride'] dEdw_l = np.zeros((w_l_shape[0], w_l_shape[1])) #          if conv_params['convolution']: g = 1 #   else: g = -1 #   #   a  b   for a in indexes_a: for b in indexes_b: #        y_l,         (  stride>1)  x_l demo = np.zeros([y_l_minus_1.shape[0], y_l_minus_1.shape[1]]) result = 0 for i in range(dEdx_l.shape[0]): for j in range(dEdx_l.shape[1]): # ,        if i*stride - g*a >= 0 and j*stride - g*b >= 0 \ and i*stride - g*a < y_l_minus_1.shape[0] and j*stride - g*b < y_l_minus_1.shape[1]: result += y_l_minus_1[i*stride - g*a][j*stride - g*b] * dEdx_l[i][j] demo[i*stride - g*a][j*stride - g*b] = dEdx_l[i][j] dEdw_l[indexes_a.index(a)][indexes_b.index(b)] = result #    ""      w_l #   demo     print('a=' + str(a) + '; b=' + str(b) + '\n', demo) return dEdw_l def create_axis_indexes(size_axis, center_w_l): coordinates = [] for i in range(-center_w_l, size_axis-center_w_l): coordinates.append(i) return coordinates def create_indexes(size_axis, center_w_l): #              coordinates_a = create_axis_indexes(size_axis=size_axis[0], center_w_l=center_w_l[0]) coordinates_b = create_axis_indexes(size_axis=size_axis[1], center_w_l=center_w_l[1]) return coordinates_a, coordinates_b print(convolution_back_dEdw_l(y_l_minus_1, w_l_shape, dEdx_l, other_parameters))
      
      



スクリプト出力の例










バイアスの重みを更新するためのバックプロップ式の出力



前の段落と同様に、置換のみ wlabbl 。 1つの機能マップに1つのバイアスを使用します。





 beginarrayrcl dfrac partialE partialbl= sumi sumj dfrac partialE partialylij dfrac partialylij partialxlij dfrac partialxlij partialbl= small sumi sumj dfrac partialE partialylij dfrac partialylij partialxlij cdot dfrac partial left sum+ inftya= infty sum+ inftyb= inftywlab cdotyl1isajsb+bl right partialbl= sumi sumj dfrac partialE partialylij dfrac partialylij partialxlij endarray







つまり、合計をすべてに展開すると i そして j に関して、すべての偏微分 \部bl 1に等しくなります。





 frac partial left sum+ inftya= infty sum+ inftyb= inftywlab cdotyl1isajsb+bl right partialbl=1







1枚の標識のカードについては、このカードのすべての要素と「接続」されている1つのバイアスのみ。 したがって、バイアス値を調整するときは、エラーの逆伝播中に取得したマップのすべての値を考慮する必要があります。 別の方法として、このマップ内の要素と同じ数の個別の機能マップにバイアスをかけることができますが、この場合、たたみ込みカーネル自体のパラメーターよりも多くのバイアスパラメーターがあります。 2番目の場合、導関数の計算も簡単です。  large frac partialE partialblij (バイアスには既に下付き文字があります ij )それぞれに等しくなります  large frac partialE partialxlij



畳み込み層を介してバックプロップ式を導出する



ここでは、すべてが以前の結論に似ています。





\ begin {array} {rcl} \ dfrac {\ partial E} {\ partial y ^ {l-1} _ {ij}}&=&\ sum_ {i '} \ sum_ {j'} \ dfrac {\部分E} {\部分y ^ l_ {i'j '}} \ dfrac {\部分y ^ l_ {i'j'}} {\部分x ^ l_ {i'j '}} \ dfrac {\部分x ^ l_ {i'j '}} {\ partial y ^ {l-1} _ {ij}} \\&=&\ sum_ {i'} \ sum_ {j '} \ dfrac {\ partial E} {\パーシャルy ^ l_ {i'j '}} \ dfrac {\パーシャルy ^ l_ {i'j'}} {\ partial x ^ l_ {i'j '}} \ cdot \ dfrac {\ partial \ left(\ sum_ {a =-\ infty} ^ {+ \ infty} \ sum_ {b =-\ infty} ^ {+ \ infty} w ^ l_ {ab} \ cdot y ^ {l-1} _ {(i's-a )(j's-b)} + b ^ l \ right)} {\ partial y ^ {l-1} _ {ij}} \\&=&\ sum_ {i '} \ sum_ {j'} \ dfrac { \部分E} {\部分y ^ l_ {i'j '}} \ dfrac {\部分y ^ l_ {i'j'}} {\部分x ^ l_ {i'j '}} \ cdot w ^ { l} _ {(i's-i)(j's-j)} \\ && \ forall i、j \次元\エンスペースの行列\エンスペースy ^ {l-1} \ end {array}







分子の量を次のようにレイアウトする a そして b 、すべての偏微分がゼロに等しいことを取得します。ただし、 isa=i そして jsb=j 、それに応じて、 a=isib=jsj 。 これは畳み込みにのみ当てはまり、相互相関は is+a=i そして js+b=j それに応じて a=ii そして b=jj 。 そして、相互相関の場合の最終的な式は次のようになります。





 frac partialE partialyl1ij= sumi sumj frac partialE partialylij frac partialylij partialxlij cdotwliisjjs







結果の式は同じ畳み込み演算で、カーネルはおなじみのカーネルです wl 。 しかし、真実、すべてが通常の畳み込みのように見えますが、ストライドが1に等しい場合のみ、別のステップの場合、他のものが既に取得されています(畳み込みカーネルを更新するためのbackpropの場合と同様) wl マトリックス全体で「破壊」し始める  large frac partialE partialxl さまざまな部分をキャプチャします(これも、インデックスが i そして jwl 数式のループ内で繰り返されます)。



ここで、コードを確認してテストできます。



code_demo_convolution_back_dEdy_l_minus_1.py
gitリンク
 import numpy as np w_l = np.array([ [1,2], [3,4]]) #  stride = 1 dEdx_l = np.zeros((3,3)) #  stride = 2  'convolution':False (   - x_l    ) # dEdx_l = np.zeros((2,2)) #  stride = 2  'convolution':True # dEdx_l = np.zeros((2,2)) y_l_minus_1_shape = (3,3) other_parameters={ 'convolution':True, 'stride':1, 'center_w_l':(0,0) } def convolution_back_dEdy_l_minus_1(dEdx_l, w_l, y_l_minus_1_shape, conv_params): indexes_a, indexes_b = create_indexes(size_axis=w_l.shape, center_w_l=conv_params['center_w_l']) stride = conv_params['stride'] dEdy_l_minus_1 = np.zeros((y_l_minus_1_shape[0], y_l_minus_1_shape[1])) #          if conv_params['convolution']: g = 1 #   else: g = -1 #   for i in range(dEdy_l_minus_1.shape[0]): for j in range(dEdy_l_minus_1.shape[1]): result = 0 #     demo = np.zeros([dEdx_l.shape[0], dEdx_l.shape[1]]) for i_x_l in range(dEdx_l.shape[0]): for j_x_l in range(dEdx_l.shape[1]): #    ""      w_l a = g*i_x_l*stride - g*i b = g*j_x_l*stride - g*j #         if a in indexes_a and b in indexes_b: a = indexes_a.index(a) b = indexes_b.index(b) result += dEdx_l[i_x_l][j_x_l] * w_l[a][b] demo[i_x_l][j_x_l] = w_l[a][b] dEdy_l_minus_1[i][j] = result #   demo     print('i=' + str(i) + '; j=' + str(j) + '\n', demo) return dEdy_l_minus_1 def create_axis_indexes(size_axis, center_w_l): coordinates = [] for i in range(-center_w_l, size_axis-center_w_l): coordinates.append(i) return coordinates def create_indexes(size_axis, center_w_l): #              coordinates_a = create_axis_indexes(size_axis=size_axis[0], center_w_l=center_w_l[0]) coordinates_b = create_axis_indexes(size_axis=size_axis[1], center_w_l=center_w_l[1]) return coordinates_a, coordinates_b print(convolution_back_dEdy_l_minus_1(dEdx_l, w_l, y_l_minus_1_shape, other_parameters))
      
      



スクリプト出力の例








興味深いことに、相互相関を実行する場合、ネットワークを直接通過する段階で、畳み込みコアは反転せず、畳み込み層を介してエラーが逆方向に伝搬するときに反転します。 畳み込み式を適用すると、すべてがまったく逆になります。



この記事では、エラーの逆伝播のすべての式、つまり、将来のモデルが学習できる式を導き出し、詳細に調べました。 次の記事では、これをすべて畳み込みネットワークと呼ばれる単一のコードに結合し、このネットワークをトレーニングして実際のデータセットのクラスを予測します。 また、テンソルフロー機械学習ライブラリと比較して、すべての計算が正しいことを確認します。



All Articles