[Python]強化学習ライブラリKeras-RLでポリシーをカスタマイズする

# はじめに
※本記事は、Keras-RLの基本的な使い方を理解している人向けに記載しています

Pythonの強化学習ライブラリであるKeras-RLを使っていると、
細かいところで独自にカスタマイズしたくなることがあります。

ここでは例として強化学習の**ポリシー（方策）**について考えたいと思います。
ポリシーとは、複数の行動の選択肢があった場合に、どの行動を選択するかを決めるルールです。

例えば、

` q_values = [1.3, 0.8, 1.2, 0.1]`

という行動の選択肢に対応するQ値のリストがあったとしましょう。
（行動1のQ値が1.3、行動2のQ値が0.8、・・）

単純に考えれば最大のQ値である行動1を選択するのがよさそうですが、
もしかすると行動3を選んだほうが最終的にはよい報酬が得られるかもしれません。

そこで、たまにランダムに行動を選択したりすることが考えられるわけですが、
このルールがポリシーと呼ばれています。

また、強化学習をある現実的な問題に当てはめたときに、
そのドメインの知識を利用することも効果的と考えられます。
（例えば消費電力や機器の性能の制約により「ある時間内に行動はN回までしか変更できない」など）

そこで、本記事ではKeras-RLで独自のポリシーを作成して実行する手順を紹介していきます。

なお、Keras-RLの各パラメータ設定については以下記事をご参照ください。
[Pythonの強化学習ライブラリKeras-RLのパラメータ設定](https://qiita.com/goodclues/items/9b2b618ac5ba4c3be1c5)

# デフォルトのポリシー
Keras-RLには今のところ以下のポリシーが用意されています。

- LinearAnnealedPolicy
- SoftmaxPolicy
- EpsGreedyQPolicy
- GreedyQPolicy
- BoltzmannQPolicy
- MaxBoltzmannQPolicy
- BoltzmannGumbelQPolicy

よくサンプルで見るのはEpsGreedyQPolicyですが、これは

> 「確率εでランダムな行動を選択し、確率1-εでグリーディに行動を選択する」

というポリシーです。
「グリーディに行動を選択する」というのはQ値のリストが与えられたときに、
単にQ値が最大となる行動を選択するというだけのことです。
最初の例で言うと行動1を選択するということになります。

それほど長くないコードなので、実際に見てみましょう。
https://github.com/keras-rl/keras-rl/blob/master/rl/policy.py

以下、コードにコメントをはさみながら解説していきます。

```python
class EpsGreedyQPolicy(Policy):
    # カスタムポリシーを作るには、Policyクラスを継承すればよい
    """Implement the epsilon greedy policy
    Eps Greedy policy either:
    - takes a random action with probability epsilon
    - takes current best action with prob (1 - epsilon)
    """
    def __init__(self, eps=.1):
        super(EpsGreedyQPolicy, self).__init__()
        self.eps = eps
        # ここで必要なパラメータを初期化する

    def select_action(self, q_values):
        # q_valuesにQ値のリストがセットされている
        """Return the selected action
        # Arguments
            q_values (np.ndarray): List of the estimations of Q for each action
        # Returns
            Selection action
        """
        assert q_values.ndim == 1
        nb_actions = q_values.shape[0] # 行動の選択肢の数

        if np.random.uniform() < self.eps: # epsの確率で
            action = np.random.randint(0, nb_actions) # ランダムに行動を選択
        else: # 1- epsの確率で
            action = np.argmax(q_values)  # Q値が最大となる行動を選択
        return action  # 行動（のインデックス）を返却する
```

# カスタムポリシーを作成する
一から作るのも手間なので、EpsGreedyQPolicyをベースに作ってみます。
ここでは簡単な例として、
「確率εでランダムに行動を選択する。ただし、その場合は、Q値が閾値q_thを超えるものから選択する」
というポリシー（EpsGreedyCustomQPolicy）を実装します。

```mypolicy.py
from rl.policy import Policy
import numpy as np
import random

class EpsGreedyCustomQPolicy(Policy):
    def __init__(self, eps=.1, q_th=0):
        super(EpsGreedyCustomQPolicy, self).__init__()
        self.eps = eps
        self.q_th = q_th # パラメータを初期化

    def select_action(self, q_values):
        assert q_values.ndim == 1

        if np.random.uniform() < self.eps:
            # リストから条件（q_thよりもQ値が大きいこと）を満たすアクションをランダムに選択
            action = np.random.choice(np.where(q_values > self.q_th)[0])
        else:
            action = np.argmax(q_values)

        return action
```

ここでは、例外処理を入れていないことに注意してください。
では、カスタマイズしたポリシーとEpsGreedyQPolicyで選択する行動に違いがあるか確認しましょう。

```main.py
from rl.policy import EpsGreedyQPolicy
from mypolicy import EpsGreedyCustomQPolicy
import numpy as np

eps = 0.99
q_th = 1.0

policy_orig = EpsGreedyQPolicy(eps=eps)
policy_custom = EpsGreedyCustomQPolicy(eps=eps, q_th=q_th)

q_values = np.array([1.2, 0.8, 1.1, 0.2, 0.5])

action_orig = policy_orig.select_action(q_values)
action_custom = policy_custom.select_action(q_values)

print("Q-values: " + str(q_values))
print("EpsGreedy: " + str(action_orig))
print("Custom: " + str(action_custom))
```

何度かやってみると、EpsGreedyQPolicyはランダムに選んでいるのに対し、
EpsGreedyCustomQPolicyは0か2のインデックスしか選ばないことがわかると思います。

```
Q-values: [1.2 0.8 1.1 0.2 0.5]
EpsGreedy: 3
Custom: 2
```

# おわりに
「カスタムポリシーを作成する」といっても、
「Q値のリストであるq_valuesから行動のインデックスにどう変換するか？」
という非常にシンプルな問題です。

強化学習を適用する際に最初からポリシーに頭を悩ませる必要はないと思いますが、
うまく学習ができない時などに検討してみるのもよいでしょう。