chakokuのブログ(rev4)

テック・コミック・ごくまれにチャリ

【DLfS】よく理解できなかったサンプルコード→この実装はちょっとルール違反では!?

交差エントロピー誤差(P89)等、完全に数式の背景を理解できない部分もあったがなんとかニューラルネットの勾配(4.2.2 P109)まで読み進めた。で、、Pythonの実装で全く理解できない箇所に出くわした。それは、、P111の以下の実装

def f(W):
   return net.loss(x,t)

dW = numerical_gradient(f, net.W)

まず分からないのは、、def f(W)で、引数Wの関数fを定義するのだけど、関数内では変数xとtを使っている(tは教師データで、xは認識対象の入力データ)。普通に実行すると、xとtは不定エラーになるのでは?と思った。この実装の意図は、関数fが実行する時点で関数f外ですでに定義されていることを前提にしており、グローバル変数のx(入力データ)とt(教師データ)を関数の外部に対して参照するというものであった。
この実装だと、関数の実行結果が関数の置かれた場所に依存するため、バグを誘発しやすい脆弱な感じがするけどこれでいいのだろうか。。(本来は関数fの引数にx,tを加えるべきだけど、関数fの引数を増やすと、以下にも書いているように、numerical_gradient内での実行関数の引数の数と一致しなくなるので引数の数は変えられない。。しかたなくグローバル変数への参照で代用しているということか?)

次に分からないのは、引数Wは関数内で使われず、引数の定義が不要なのでは?という疑問。。以下の実装だとだめか?

def f():
   return net.loss(x,t)

dW = numerical_gradient(f, net.W)

もし上記のように関数fを引数無しにすると、(file:common/gradient.py内の)numerical_gradient内で誤差演算のf(x+h),f(x-h)で一つ引数を指定するため型不一致(引数の数が不一致)になってエラーになって誤差演算の式が実行できない。だからf(x)でないとだめ。

次に、f(W)に渡される値とは一体何なのか?それはどう使われるのか?という疑問
GitHubで公開されるソースコードを読んだ結果、渡る値は、数値微分でf(w+h),f(w-h)で用いられるw+h,w-hの値が引数として渡される。が、、net.loss(x,t)に渡されずどうやって使われるのか?? 自分の理解としてはf(w)に値は渡るが、引数として渡ってくる値は使われない(引数はダミー)。

使われなかったらどうやって、f(w+h),f(w-h)を計算できるのか?という疑問がわくが、f(w+h),f(w-h)のwとはWeightでありこれはインスタンスnetのインスタンス変数(net.W)である。関数f(x)の引数として値を渡しているのではなく、関数numerical_gradient内の42行目の下記の式で、x[idx]として値を変更しているが、これはインスタンス変数の実体を直接変更している(と自分は理解しました。ちゃんと裏取ってないので間違ってたらすみません)。

x[idx] = float(tmp_val) + h

すなわち、、インスタンスnetのインスタンス変数として管理されている重み配列Wの値(net.W)を関数numerical_gradient内の配列操作式により書き換えて重みを変えて演算させている。だから、、wを渡さずにf()としても計算は可能なはず(関数引数不一致エラーを引き起こすのは除いて)。

DLfS本ではニューラルネットの勾配の実装についてかなり省略して書かれているけど、ソース公開されている実装ではかなり複雑な処理がなされており、グローバル変数を参照したり、クラス外の関数でインスタンス変数を直接操作したりとかなりトリッキーな実装*1と思えた。

とはいえ、この本(「ゼロから作るdeep Learning」)の説明の分かりやすさについては本当に感謝しており、アルゴリズムはそのまま踏襲しつつ、自分なりの実装で、バックプロパゲーション前の数値微分による俺ニューラルネットを作ってみることにした。

*1:処理の局所性を高めるため、演算に必要な情報(値)は関数の引数として渡し、値の変更による影響と手続きは関数内で完結させるべしと理解しているので