「ゼロから作る Deep Learning 3 フレームワーク編」の読書まとめ。思い出すためのまとめメモです。解説ではないので書籍を読んでいる前提です。第5ステージ、目的は「DeZeroで挑む」です。
コードはこっち
第5ステージ DeZeroで挑む
最終ステップです。ここではGPU対応や、ファイルへの保存、DropOutやCNN,RNNの実装等応用です。概念的には難しい話ではないので、はしょり気味で行きます。
GPU対応
Numpyの互換ライブラリCuPyを切り替えて使う方法に関してのステップです。CUDAが使える環境であればこのCuPyを使うことで高速化が期待できます。
1 2 | import numpy as np import cupy as cp |
この2つがほぼ同じAPIを持っています。両者で配列を行ったり来たり(メインメモリとGPUのメモリを行ったり来たりする)する関数を適宜挟み、その配列の演算に対応したモジュールを判別し使うことでGPUを使った高速演算ができるようになります。
cudaがインストールされていない場合においてもエラーを起こさず、つつがなくnumpyでの計算ができるような実装を行うこと。この実装を各クラスに行っています。
まずは定番のto_cpuとto_gpu関数です。内部で保持しているarray(.data)をそれぞれのメモリへ転送する関数です。Variable,Layer,DataLoaderにそれぞれ実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | class Variable: ... def to_cpu( self ): if self .data is not None : self .data = dezero.cuda.as_numpy( self .data) def to_gpu( self ): if self .data is not None : self .data = dezero.cuda.as_cupy( self .data) class Layer: ... def to_cpu( self ): for param in self .params(): param.to_cpu() def to_gpu( self ): for param in self .params(): param.to_gpu() class DataLoader: ... def __next__( self ): ... xp = cuda.cupy if self .gpu else np x = xp.array([example[ 0 ] for example in batch]) t = xp.array([example[ 1 ] for example in batch]) self .iteration + = 1 return x, t ... def to_cpu( self ): self .gpu = False def to_gpu( self ): self .gpu = True |
Variableに関しては.dataを持っているのでそれに対して、Layerに関しては内部で持っているParameter群に対して、DataLoaderに関しては読み込むデータに対してそれぞれ切り替えを行うための関数です。
最後にnp.となっている記述すべてに対して切り替えに相当する記述を追加していきます。
1 | xp = cuda.get_array_module(x) # xに見合うnp or cpを返す |
モデルの保存と読み込み
せっかく長い時間かけて学習したパラメータをファイルに保存しておく関数です。numpy_arrayはnp.saveとnp.loadという関数があるので、1つのインスタンスを保存、読み込みするのはわけないです。複数のarrayを保存するにはnp.savezを使います。arrayのインスタンスを辞書に登録して引数で一気に渡すことで複数一気に書き込みができます。
Dropoutとテストモード
Dropoutの実装です。Dropoutは言わずもがな学習時にランダムにニューロンを消去しながら学習する手法で、疑似的に似て非なるモデルでの学習をしたかのようなふるまいをしてくれます。ポイントは学習時のみ消去しており、推論時は全部のニューロンが活性化されます。この消去するニューロンの比率をdropout_ratioと呼びます。
ここでポイントとなるのがスケールです。学習時にdropout_ratioだけニューロンが死んでいたら、結局1-dropout_ratioだけ生き残っており、学習時は常に1-dropout_ratioの比率でしか結果が出ていません。なので推論時には全部のニューロンの出力にこの1-dropoutだけ乗じ、結果を減らして推論することでスケールを合わせます。
これは逆も考えられ、学習時にスケールの逆数1/(1-dropout_ratio)を結果に乗じておけば(死んだニューロン分スケールアップさせておけば)、推論時には何もする必要がなくなります。また学習時に動的にdropout_ratioを変化させることも可能になります。この手法はInverted Dropoutと呼ぶそうで、通常この実装がされているようです。
学習時と推論時でモデルの動作を切り替える必要があるので、そのフラグを用意する必要があります。逆伝播ON/OFFのフラグの実装と並列に学習ON/OFFのフラグを実装することで実現させています。
1 2 3 4 5 6 7 8 9 10 11 | def dropout(x, dropout_ratio = 0.5 ): x = as_variable(x) if dezero.Config.train: # 学習時 xp = cuda.get_array_module(x) mask = xp.random.rand( * x.shape) > dropout_ratio scale = xp.array( 1.0 - dropout_ratio).astype(x.dtype) y = x * mask / scale return y else : return x |
この学習ON/OFFのフラグはDropout以外にもBatchNormにも使われることが多いですが、今回BatchNormは実装しないので実質Dropout専用フラグです。
これに関して学習パラメータを考える必要はないです。実装も掛け算をしているだけなので順伝播・逆伝播は掛け算のそれが使われます。使い方としてはModelのforwardの中で
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class VGG16(Model): def __init__( self , pretrained = False ): super ().__init__() ... self .fc6 = L.Linear( 4096 ) self .fc7 = L.Linear( 4096 ) self .fc8 = L.Linear( 1000 ) ... def forward( self , x): x = F.relu( self .conv1_1(x)) ... x = F.relu( self .conv5_3(x)) x = F.pooling(x, 2 , 2 ) x = F.reshape(x, (x.shape[ 0 ], - 1 )) x = F.dropout(F.relu( self .fc6(x))) # dropout x = F.dropout(F.relu( self .fc7(x))) x = self .fc8(x) return x |
このように使ってやります。
CNNの実装
CNNのメカニズムに関してはいったん置いときます。画像に対するフィルタの畳み込み演算です。im2colで行列展開し、その結果に対してフィルタを行列の積で計算することで結果を得る手法で、1巻を読んでいるのでOKとします。
RNNの実装
これも2巻を読んでいるのでよしとします。RNNとLSTMに関して実装を行います。
以上
コメント