hirohirohirohirosのブログ

地方国立大学に通う情報系学部4年

ゼロから作るDeep Learning 3 ステップ1~ステップ4 まとめ

 DLに関する話だけで無く,Pythonやソフトウェア開発に関する事についても学ぶ事が多い本だと感じたのでまとめます.
hirohirohirohiros.hatenablog.com

前書き

インクリメンタル

 incrementalとは増加の,増分の,徐々に増加するなどを意味する単語です.Dezeroはインクリメンタルに開発すると言っています.

 ステップ1では変数クラスを作りますが,それは3行しかありません.ステップ2以降変数クラスを利用していろいろな物を作っていきますが,この段階では変数クラスを使う意味は全く感じられません.それでも変数クラスをステップ1で作ったのは,今後変数クラスに様々な機能を追加することを見越しているからだと思います.

 シンプルな変数クラスを先に作っておき,その後必要になったら新しい機能を追加していくのがインクリメンタルな開発だと言えます.

ステップ2

__call__メゾット

 pythonでクラスを使ったことが少ないため__call__メゾットを初めて見ました.__init__はクラスを書くときによく使いますが__call__はどう使われるのでしょうか.

 本書では"f=Function()としたときf()と書くことで__call__メゾットが呼び出される"とあります.具体的にはこのような感じです.

class Function:
    def __call__(self, x):
        y = x**2
        return y

f = Function()
print(f(5))
>> 25

 fはインスタンスであるのに関数のように書くことが出来ます.ただこれなら以下のように別の関数として書いてもいいのでは?と思います.

class Function:
    def squared(self, x):
        y = x**2
        return y

f = Function()
print(f.squared(5))
>> 25

 関数名を省ける分すっきり見えて良いというのは理解出来ますが,関数名が分からず何をする処理なのか分かりにくいとも思います.__call__を使うメリットをいろいろ調べてみましたが,ピンとくる説明はありませんでした……

raise NotImplementedError

Functionクラスは親クラスとして,具体的な内容を持つ関数の親となるクラスです.よって,Functionクラスを継承した子クラスのforward関数に具体的な内容を書きます.つまりFunctionクラスのforward関数はメゾッドの意味だけを定義した抽象メゾッドです.よってユーザーがこのFunctionクラスのforward関数を使うことは出来ません.
 ユーザーがFunctionクラスのforward関数を使おうとしたらエラー,例外を出す必要があります.この役目を果たすのがraise NotImplementedErrorです.raiseは例外を発生させます.
 実際に例外を発生させてみるとこのようになります.

class Function:
    def __call__(self, input):
        x = input.data
        y = self.forward(x)
        return Variable(y)
    
    def forward(self, x):
        raise NotImplementedError()

f = Function()
print(f.forward(10))

>> Traceback (most recent call last):
>>  File "step1.py", line 33, in <module>
>>     print(f.forward(10))
>>   File "step1.py", line 14, in forward
>>     raise NotImplementedError()
>> NotImplementedError

 NotImplementedErrorという例外が発生していることが分かります.これで抽象クラスを使おうとしてしまったんだという事が分かります.

ステップ4

微分の近似

 微分の定義はf(x+h)-f(x)/hでのh→0の極限の値ですが,プログラムは極限を扱えないので近似します.近似の仕方には前進差分近似と中心差分近似の二つが紹介されました.前進差分近似は定義のようにf(x+h)-f(x)/hで求め,中心差分近似はf(x+h)-f(x-h)/2hで求めます.
 中心差分近似の方がよく近似できるそうです.実際にプログラムでそれを確認してみます.

def advance_numerical_diff(f, x, eps=1e-4):
    x0 = Variable(x.data)
    x1 = Variable(x.data + eps)
    y0 = f(x0)
    y1 = f(x1)
    return (y1.data - y0.data)/(eps)

def center_numerical_diff(f, x, eps=1e-4):
    x0 = Variable(x.data - eps)
    x1 = Variable(x.data + eps)
    y0 = f(x0)
    y1 = f(x1)
    return (y1.data - y0.data)/(2*eps)

if __name__ == "__main__":
    f = Square()
    x = Variable(np.array(2.0))
    dyc = center_numerical_diff(f, x)
    dya = advance_numerical_diff(f, x)
    print("center: ", dyc)
    print("advance: ", dya)

>> center:  4.000000000004
>> advance:  4.0001000000078335

 0.0001も差があることが分かりました.この差が重なると結構大きな誤差になりそうなので,中心差分近似を使う方がよいとよく分かります.