ロゴ ロゴ

2048を強化学習で解きたい

タイトルの通りです。

前回作ったゲームを強化学習でクリアを目指す記事です。

環境

OS: windows10
言語: python3.8.0
Module: Numpy,pygame,opencv-python,pywin32

ニューラルネットワークの設定

中間層の活性化関数:ReLU → hardSigmoid
出力層の活性か関数:softmax
最適化アルゴリズム:確率的勾配降下法
バッチサイズ:50
中間層のニューロン数:50
入力層:1 中間層:3 出力層:1 (中間層の各層にドロップアウト層を追加)

起こった問題

普段フレームワークを使用せず、numpyのみで機械学習を行うとsoftmax関数でオーバーフローまたはアンダーフローが発生し、これの改善のためにsoftmax関数ではオーバーフロー、アンダーフロー対策に以下の式が使用されます。
\Large y = \frac{\mathrm{e}^{x – c}}{\Sigma\mathrm{e}^{x – c}}

def softmax(x):
    #x:入力値 w:重み b:バイアス
    u= np.dot(x,w) + b
    c = np.max( u, axis=1, keepdims=True)
    y = np.exp(u - c) / np.sum( np.exp(u  - c), axis=1, keepdims=True)

この式ではcをxの最大値にしてあげることで、オーバーフローとアンダーフローを対策したものですが、xの値によってはアンダーフローを発生してしまい万能に対策できるものではありません。

import numpy as np

np.seterr(all="raise")
a = np.array([[9999, 12345, 44444,0],[0, 0, 0, 800]])
c = np.max(a,axis=1,keepdims=True)
u = np.exp(a - c) / np.sum(np.exp(a-c),axis=1,keepdims=True)

'''
出力
FloatingPointError: underflow encountered in exp
'''

例えば、上の例では

c = \begin{bmatrix}
4444 \\
800
\end{bmatrix}
,
a = \begin{bmatrix}
9999 & 12345 & 44444& 0 \\
0&0&0&800
\end{bmatrix}

といった行列を宣言していますが、numpyのブロードキャストを考慮して演算を行うと

a – c = \begin{bmatrix}
-95555 & -87654 & 0 & -9999\\
-800 & -800 & -800 & 0
\end{bmatrix}

となりこの値を指数関数のxに代入をするとアンダーフローを起こしてしまいます。
実際に私が書いたコードでも、この現象が起こってしまい十分に学習することができませんでした。

対策

1:学習率を下げる

学習率を下げることで重みの変化を緩やかにすることで、入力値と重みをかけたときの値を小さく保つことを目的として行いました。これに関しては序盤に効果はある程度あるものの、やはり学習を進めていくとアンダーフローが発生してしまい意味をなさなくなってしまいました。

2:出力層で入力値の値の制限をつける

具体的には、活性化関数の入力値に最大最小値をつけてあげることで指数関数をアンダーフローを起こさない範囲に絞ることができるのではと思い以下のコード出力層に追加をしました。

import numpy as np
SOFTMAX_RANGE=709
class Out_layer(Base):
    def forward(self, x):
        self.x = x
        self.u = np.dot(x, self.w) + self.b
        irp_underflow = np.max(self.u, axis=1, keepdims=True)
        #------------------------追加部分--------------------------------------
        self.u = np.clip(self.u - irp_underflow, -SOFTMAX_RANGE, SOFTMAX_RANGE)
        #---------------------------------------------------------------------
        self.y = np.exp(self.u) / np.sum(np.exp(self.u), axis=1, keepdims=True)
        return self.y

    def backward(self, y, t):
        delta = y - t
        self.grad_w = np.dot(self.x.T, delta)
        self.grad_b = np.sum(delta, axis=0)
        self.grad_x = np.dot(delta, self.w.T)

np.clip(クリップしたい値,最小値,最大値)です。これでSOFTMAX_RANGEを超える値はすべてSOFTMAX_RANGEに置き換わります。ですが結局逆伝播中にアンダーフローを起こしこれも意味なし

3:中間層の活性化関数を変える

正直上の対策をとってダメだったので、自分でもどうすればよいのわからなかったのが本音結論から言うとReLu関数をやめました。そもそもsoftmax関数の一つ前の層にReLu関数を使用する時、恒等関数があるのがいけないんじゃね?→上限が決まっている関数がいいなぁ。Hardsigmoidにしよ(やけくそ)

ReLu := \left\{
\begin{array}{ll}
x & (x > 0) \\
0 & (x \leq 0)
\end{array}
\right.
Hardsigmoid := \left\{
\begin{array}{ll}
0 & (x < -2.5) \\
0.2x+0.5 & (-2.5\leq x \leq 2.5)\\
1 & (x > 2.5)
\end{array}
\right.

以下のように実装

class Mid_layer(Base):
    def forward(self, x):
        self.x = x
        self.u = np.dot(x, self.w) + self.b
        #--------------------Hardsigmoid----------------------------------------
        self.y = np.select([self.u < -2.5, (2.5 <= self.u) &
                     (self.u <= 2.5), 2.5 < self.u], [0, 0.2 * self.u + 0.5, 1])
        #-----------------------------------------------------------------------
    def backward(self, grad_y):
        delta = grad_y * np.where((-2.5 <= self.u) & (self.u <= 2.5), 0.2, 0)
        self.grad_w = np.dot(self.x.T, delta)
        self.grad_b = np.sum(delta, axis=0)
        self.grad_x = np.dot(delta, self.w.T)

ただ処理が遅い…もう少し早く処理できるように実装できるかも?
ReLu関数からHardsigmoid関数にすることでアンダーフローしなくなった!ただ勾配消失するかも…中間層全部をHardsigmoidにするのではなく途中にReLu入れてもいいかもしれない。(要検証)

結果

とりあえずアンダーフローは出なくなったからこれで学習させてみる。
以下学習の様子

コメント入力

関連サイト