hirohirohirohirosのブログ

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

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

hirohirohirohiros.hatenablog.com

ステップ18

推論モードの切り替え

 今まで学習モード(逆伝播をする必要がある)と推論モード(逆伝播をする必要が無くメモリ使用量を減らせる)の切り替えはConfigクラスの属性でTrue,Falseを切り替えることで行っていました.これでは少々不便なのでwith文でインデントを囲い,その中でのみ推論モードに変え,with文を抜けると自動に学習モードに切り替えられるシステムを作ります.
 with文は,withブロックに入るときの前処理とwithブロックを抜けるときの後処理を自動で行うことが出来ます.よって,前処理として推論モードに切り替える,後処理として学習モードに切り替えるような関数を作り,with文に入れます.
 @contextlib.contextmanagerというデコレータを付ける事で,yieldの前に前処理,yieldの後に後処理を適切に書くことが出来ます.

@contextlib.contextmanager
def using_config(name, value):
    old_value = getattr(Config, name) #前処理
    setattr(Config, name, value) #前処理
    try:
        yield
    finally:
        setattr(Config, name, old_value) #後処理

 getattr(Config, name)とすることで,Configクラスのname属性を取得します.今回の場合これが,学習モードと推論モードを切り替えるenable_backdropとなり,この段階では学習モードなのでold_value=Trueになります.
 そして,setattr(Config, name, value)でConfigクラスのname属性をvalueに変えます.今回の場合,nameがenable_backdropでvalueがFalseなので推論モードに切り替えられます.
 そしてtry文に入りyieldとなって返されます.そしてwith文の中に入り,推論をして,全ての処理が終わったら,finallyに入ってsetattrを使いenable_backdropをTrueに変えます.(この辺りの処理の流れを正しく理解出来ている自信がありません……)
 このようなenable_backdropを切り替えを,with文とcontextlibを使う事で自動で行うことが出来ます.
 最後に,これを使ってモードの切り替えを行う処理はusing_config("enable_backdrop", False)となり長ったらしいので,関数でくくり,

def no_grad():
    return using_config("enable_backdrop", False)

として,

with no_grad():
    x = Variable(np.array(2.0))
    ... #推論するコード

とwith文の中に推論するコードを書けば,自動で推論モードに切り替わり,with文を抜けると自動で学習モードに切り替わります

ステップ19

@property

 def shape(self):の前に@propertyを付ける事でshapeメゾットがインスタンス変数としてアクセスできます.
 本書ではデータの形状や次元,型などを示すためにこれを使っています.確かに,データの形状を得るためにいちいちx.data.shapeと書くのは煩わしいです.
 さらに@propertyを付けるメリットとしてその値を隠蔽し,その値を書き換えることが出来なくなります.

class test:
    def __init__(self, x):
        self._x = x

    @property
    def x(self):
        return self._x

 隠蔽したい変数は慣例的に先頭に_を付けます.そして,それを参照したいときは@propertyをつけた関数を用意します.そうすると,

a = test(1)
print(a.x)
>>1

と参照は出来ますが,

a = test(1)
a.x = 10
>>File "step1.py", line 203, in <module>
>>    a.x = 10
>>AttributeError: can't set attribute

と,書き換えようとするとエラーが出ます.
 ただし,慣例的に_を付けた変数は,文法的に制限されているわけではないので

a = test(1)
a._x = 10
print(a.x)
>>10

と書き換えが出来てしまいます.ただ,先頭に_がつくことで,これは書き換えてはいけない値だと気付くことが出来ます.
 これが@propertyを付けてインスタンス変数としてアクセスすることのメリットです(と理解しています……).

ステップ20

演算子オーバーロード

 普段pythonで+や*といった演算子が使えるのは__add__や__mul__といった特殊メゾットが定義されているからです.つまり,自作のクラスでもこれらの特殊メゾットをオーバーロードすることによって+や*を使う事が出来るようになります.
 また,__add__や__mul__だけでなく,__radd__や__rmul__という特殊メゾットもあります.これはa+bというコードが処理されたとき,aに__add__メゾッドが無ければ,bの__radd__メソッドを呼ぶという仕組みになっています.addやmulは入れ替えても同じであるので,__add__と__radd__は同じ事を書いても構いません.逆に,自作のクラスでは__radd__などもちゃんと記述しないと,問題が起こる可能性があります.(実際次のステップ21でその問題を解決します)