ゼロから作るDeep Learning 3 ステップ45~ステップ46 まとめ
hirohirohirohiros.hatenablog.com
ステップ45
レイヤを纏めるレイヤ
ステップ44ではパラメータを纏めるレイヤを作成し,パラメータの管理を一つのクラスで行えるようにしました.今回はこの管理をパラメータだけでなくレイヤも行うようにしています.そうすることで,複数のレイヤを一括に管理する事ができ,何層もあるパーセプトロンを簡単に組むことが出来ます.
dezeroでモデルを組んで,実際に学習させるためのコードはステップ45ではこうなっていました.
class TwoLayerNet(Model): def __init__(self, hidden_size, out_size): super().__init__() self.l1 = L.Linear(hidden_size) self.l2 = L.Linear(out_size) def forward(self, x): y = F.sigmoid_simple(self.l1(x)) y = self.l2(y) return y
そして,pytorchのモデルはこのようになっています.(とあるkaggleコンペの一位解法のコードです)
class ResidualLSTM(nn.Module): def __init__(self, d_model): super(ResidualLSTM, self).__init__() self.LSTM=nn.LSTM(d_model, d_model, num_layers=1, bidirectional=True) self.linear1=nn.Linear(d_model*2, d_model*4) self.linear2=nn.Linear(d_model*4, d_model) def forward(self, x): res=x x, _ = self.LSTM(x) x=F.relu(self.linear1(x)) x=self.linear2(x) x=res+x return x
非常にdezeroがpytorchに近づいていることが分かります!Moduleのような親クラスを継承し,レイヤーをいくつも生成し,forwardを書くことで順伝播と逆伝播を定義する,一連の実装が出来ている事が分かります.
本書p348で”オブジェクト指向によるモデルの定義の作法はChainerが最初に提案し,PytorchやTensorFlowなど他の多くのフレームワークでも一般的に使われるスタイルとなっています”と解説があるように,このような記述の仕方が一般のディープラーニングフレームワークでも一般的であることが分かります.
内容の難易度も上がっていますが,このステップ44,45が一般的なディープラーニングフレームワークになる為に重要なステップであることが分かります.
ステップ46
SGD,Momentum以外のOptimizer
本書ではOptimizerにSGDとMomentumを乗せていました.今回は,それ以外に,ゼロから作るDeeplearning(1)で詳細に説明されていたAdaGradを説明して,実装します.
ニューラルネットワークの学習では,lrで表せられる学習係数の値が重要です.学習係数が小さすぎると学習が一向に進まず,学習係数が大きすぎると発散して正しい学習が行えません.この問題の対処法として,学習係数を減衰させるというテクニックがあります.学習の最初は学習係数を大きく設定し,学習が進むごとに係数を小さくするという手法です.
AdaGradは一つ一つのパラメータに対して,学習係数を小さくします.AdaGradのAdaはAdaptive(適応的)という意味です.パラメータの要素ごとに学習係数の減衰度を決めながら,学習を進めます.
まず,パラメータごとに,減衰度hを決めます.それは
という数式で決められます.Wは対象のパラメータです.つまりパラメータの微分の二乗和をhに加算していきます.そして,パラメータの更新は次の式で行われます.
hの逆数のルートを学習率に掛け合わせています.それ以外はSGDと同じです.
つまり,AdaGradは,学習率にhという係数を追加して掛けるSGDと考える事が出来ます.このhは逆数が掛けられているので,hが大きいほど学習率が小さくなることが分かります.hはパラメータの微分の二乗和なので,パラメータが大きく更新された場合,学習率が小さくなり,更新も小さくなると言うことが分かります.大きく更新されたパラメータの学習係数が小さくなることで,減衰が実装されていることが分かります.
では,これをdezeroに実装します.(コードは公式のgithubにあります)
class AdaGrad(Optimizer): def __init__(self, lr=0.001, eps=1e-8): super().__init__() self.lr = lr self.eps = eps self.hs = {} def update_one(self, param): h_key = id(param) if h_key not in self.hs: self.hs[h_key] = np.zeros_like(param.data) lr = self.lr eps = self.eps grad = param.grad.data h = self.hs[h_key] h += grad * grad param.data -= lr * grad / (np.sqrt(h) + eps)
それぞれのパラメータに対するhをhsというディクショナリで管理します.そして,h += grad*gradとすることで,パラメータの微分の二乗和を計算し,hに加算してます.
そしてパラメータに学習率とhのルートを掛け合わせ,パラメータを更新しています.hが0だったときの0除算を回避するため,ごく小さな値epsを追加で足していることに注意します.