hirohirohirohirosのブログ

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

ゼロから作る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を決めます.それは
 h←h+\dfrac{\partial L}{\partial W}\odot \dfrac{\partial L}{\partial W}
という数式で決められます.Wは対象のパラメータです.つまりパラメータの微分の二乗和をhに加算していきます.そして,パラメータの更新は次の式で行われます.
 W←W-lr\frac{1}{\sqrt h}\frac{\partial L}{\partial W}
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を追加で足していることに注意します.