ロゴ ロゴ

誤差逆伝播とクリッピング

環境

python: 3.8.2
numpy:1.184
gym:0.17.2
中間層の活性化関数:ReLU
出力層の活性化関数:tanh
損失関数:二乗和誤差
バッチサイズ:10
中間層のニューロン数:50

概要

OpenAIのgymの中のBipedalWalker-v3で深層強化学習を勉強していたところある問題にぶち当たりました。

#出力結果
[[-0.2171489  -0.07575708  0.03611224 -0.0570887 ]]
[[-0.22095748 -0.08336085  0.02989673 -0.0658941 ]]
[[-0.2300945  -0.10509693  0.01564181 -0.0523298 ]]
[[-0.23026615 -0.10547072  0.01240033 -0.05218243]]
[[-0.22897101 -0.09123651  0.00565358 -0.05411297]]
[[-0.23049711 -0.09215829  0.00581974 -0.055781  ]]
[[-0.27269665 -0.14704135 -0.01896072 -0.12839232]]
[[-0.30093505 -0.1822165  -0.05609594 -0.15977812]]
[[-0.31389829 -0.19667872 -0.06472401 -0.15977014]]
[[-0.32370385 -0.20976409 -0.07568633 -0.16447643]]
[[-1. -1. -1. -1.]]
[[-1. -1. -1. -1.]]
[[-1. -1. -1. -1.]]
[[-1. -1. -1. -1.]]
[[-1. -1. -1. -1.]]
[[-1. -1. -1. -1.]]
[[-1. -1. -1. -1.]]
[[-1. -1. -1. -1.]]

ニューラルネットワークからの出力結果が何故か上の様に-1が連続してしまう…
自分のコードを確認したところ、重みの勾配を求めている最中にnanが出力されていた。さらに出力を遡るとどうやら行列の積の計算を繰り返したことで勾配爆発が起きてしまいnanが出力されてしまっているらしい。

対策

どうやらクリッピングというのをすれば良いらしい。計算式はgrad = \frac{M}{||grad||}grad(Mは勾配の最大値、gradは勾配)でこれを各層からの入力に対する重みとバイアスに適用させてやれば良いらしい…?

実装

import numpy as np

def clip_grad(grads):
        grad_norm = []
        rate = []
        for grad in grads:
            grad_norm.append(np.sum(grad ** 2))
            rate.append(None)
        grad_norm = np.sqrt(grad_norm)
        for index in range(len(grad_norm)):
            if grad_norm[index] != 0:
                rate[index] = MAX_GRAD / total_norm[index]
                if rate[index] < 1:
                    grads[index] *= rate[index]
        return grads

上記のコードを重みとバイアスに適用後に各パラメータを更新することで勾配の更新量を制限しました。

感想

もう少しクリッピングの効率の良いやり方がありそう、特にfor文を二回使用してしまっているので自分としては気に食わない最後に、このクリッピングを実装後によくよく調べてみたら、クリッピングより正則化をして重みに制限を加える方が一般的らしい( ;∀;)

コメント入力

関連サイト