Suttonの強化学習本をひたすら読む(2章)

Richard S. Suttonの強化学習のメモ。

概要

この章で扱う問題のイメージ f:id:takekbys:20180304121608p:plain

  • 環境

この章で扱う環境はn本腕Bandit問題。 エージェントがプレイする度にいくらかの「報酬」を返すマシンがあり、そのようなマシンn台から構成されている。 それぞれのマシンが返す報酬は以下で定義されている。

* 平均:平均0分散1の正規分布(マシンごとに固有の値が一番最初に決まっている)
* 分散:平均0分散1の正規分布(プレイするたびに変わる)

「状態」に当てはまるものは特にない。

  • エージェント

より多くの報酬を得る、もしくは探索をするため、何番目のマシンをプレイするかという「行動」を決定する。 この章では、エージェントが最適な行動をとるための手法を扱っている。 説明の便宜上、これらの手法を報酬推定、行動決定に分類して示す。

2.2章 標本平均、ε-greedy

標本平均

「報酬推定」に当たる手法。 過去にi番目のマシン(マシンi)から得た報酬の平均値により、マシンiによる報酬を推定するもの。

ε-greedy

「行動決定」に当たる手法。 小さい値εの確率で探索(報酬の推定値とは無関係にランダムなマシンをプレイ)し、1-εの確率でgreedyな行動(報酬の推定値が最も大きいマシンをプレイ)を取る。

f:id:takekbys:20180309180308p:plain 上記の結果はn本腕Bandit問題を2000回解き、その全ての試行に対して平均を取ったもの。 左右のグラフはそれぞれ、ある時点において過去に得た報酬、平均報酬が最も大きい行動を取った割合を表す。 ε=0.1の場合は探索の結果最適な行動を見出している。 ε=0の場合は一切探索を行わず、ε=1の場合は完全にランダムにプレイすることになる。

2.3 softmax法

「行動決定」に当たる手法。 1-εの確率でgreedyな行動を取るのはε-greedyと同様。ただし探索を行う場合は、ランダムではなく報酬推定に応じた重み付けに従い探索を行う。 f:id:takekbys:20180309180316p:plain τ=0の場合は全く探索を行わない結果となり、τ=1の場合はε-greedy手法の結果と完全に一致する。

2.6 指数減衰荷重平均

「報酬推定」に当たる手法。 報酬推定にあたり、過去に得た全ての報酬を平等に扱うのではなく、直近に得た報酬を重視するような加重平均をとる。 環境が時間変化する場合に有効に働く。

2.7 optimistic初期値

「報酬推定」に当たる手法。 報酬推定の初期値を設定する際、十分に大きい報酬を期待して(optimisticな)初期値を設定する。 行動の結果期待したほどの報酬が得られないため、全ての行動について試行、評価を行うことに繋がる。 ただし初期値のみに焦点を当てた手法であり、非定常問題に適しておらず、一般に探索を促す目的で使用されることは少ない。

f:id:takekbys:20180309180303p:plain

2.8 強化比較

「行動決定」に当たる手法。 この手法を用いる場合には報酬推定を行わない。 ステップ毎に、そのステップで受け取った報酬とリファレンス報酬を比較し、受け取った報酬の大きさに応じて行動の優先度を設定する。 f:id:takekbys:20180309180313p:plain

2.9 追跡手法

「行動決定」に当たる手法。 ステップ毎に、greedyな行動の優先度を上げ、そうでない行動の優先度を下げる。 f:id:takekbys:20180309180310p:plain

コード

各手法を試すに当たって書いたn本腕Bandit問題、プレーヤーのコードは以下(実行部分は省略する)。 (コード部分をシンタックスハイライトで表示させると、なぜか記事中の画像が表示されなくなるのでべた貼り・・・)

import numpy as np import matplotlib.pyplot as plt

class n_Bandit(): """n本腕Bandit問題を定義""" def init(self, n): """n個のマシンから成るゲームの初期設定を行う""" self.machine_num = n self.mean_reward = np.random.randn(self.machine_num) # マシンが返す報酬の平均値を設定 self.lavish = np.argmax(self.mean_reward)

def reset(self):
    """全てのマシンの設定を初期化する"""
    self.mean_reward = np.random.randn(self.machine_num)
    self.lavish = np.argmax(self.mean_reward)

def play(self, nth):
    """報酬を返させる関数.playごとの報酬は確率的に変動するが、平均値はself.mean_rewardとなる."""
    return np.random.randn()+self.mean_reward[nth]

class Player(): """プレーヤーを定義する""" def init(self, game2play, act_strategy=None, est_strategy=None, epsilon=0.0, tau=1.0, alpha=1.0, reward_init=0, alpha_rc=0.1, beta_pr=0.01): """プレーヤーの行動戦略、ゲーム報酬の予想戦略を設定する""" self.game = game2play self.machine_num = game2play.machine_num self.Reward_init = reward_init # 報酬予想の初期値を設定(reward_initが大きい場合optimistic初期値となる) self.Reward_est = np.ones(self.machine_num)*self.Reward_init self.PlayTime = np.ones(self.machine_num) # 初期状態で各行動を1回試行していると設定。optimistic初期値対応のため(報酬推定が正確ではなくなるが、大勢に影響はない) self.epsilon = 1-epsilon self.tau = tau self.alpha = alpha self.alpha_rc = alpha_rc self.beta_pr = beta_pr self.ref_reward = self.Reward_init # リファレンス報酬。強化比較の場合のみ使用する self.priority_rate = np.ones(self.machine_num) # 行動優先度。強化比較、追跡手法の場合のみ使用する # ----------------- 行動戦略を設定 ----------------- if act_strategy == "greedy": # εグリーディ self.act = self.greedy elif act_strategy == "softmax_selection": # softmax行動選択 self.act = self.softmax_selection elif act_strategy == "reinforcement_comparison": # 強化比較 self.act = self.reinforcement_comparison elif act_strategy == "pursuit": # 追跡手法 self.act = self.pursuit else: # 行動戦略の設定が上記いずれでもない場合は例外を出力 # todo 例外を出力 print("行動戦略設定エラー") # ----------------- 報酬の予想戦略を設定 ----------------- if est_strategy == "sample_average": # 標本平均 self.est = self.sample_average elif est_strategy == "recency_weighted_average": # 指数元帥加重平均 self.est = self.recency_weighted_average else: # 予想戦略の設定が上記いずれでもない場合は例外を出力 # todo 例外を出力 print("報酬の予想戦略設定エラー")

def reset(self):
    """プレーヤーの戦略は変えず、報酬の予想値、リファレンス報酬、行動優先度、プレイ回数をリセットする"""
    self.Reward_est = np.ones(self.machine_num) * self.Reward_init
    self.ref_reward = self.Reward_init
    self.priority_rate = np.ones(self.machine_num)
    self.PlayTime = np.ones(self.machine_num)  #

# ----------------- 行動戦略の定義 -----------------
def greedy(self):
    search = (np.random.rand() < self.epsilon)  # εの確率で探索、それ以外はgreedyに振る舞う
    if search:
        # greedyに振る舞う場合は、予想報酬がもっとも大きいマシンをプレイする
        n = np.argmax(self.Reward_est)
    else:
        # 探索する場合はランダム
        n = np.random.randint(self.machine_num)
    # n番目をプレイする
    reward = self.game.play(n)
    return n, reward

def softmax_selection(self):
    search = (np.random.rand() < self.epsilon)  # εの確率で探索、それ以外はgreedyに振る舞う
    if search:
        # greedyに振る舞う場合は、予想報酬がもっとも大きいマシンをプレイする
        n = np.argmax(self.Reward_est)
    else:
        # 探索する場合は予想報酬のsoftmaxに従い、プレイするマシンを選択する
        tmp = self.Reward_est/self.tau - np.max(self.Reward_est)/self.tau  # オーバーフロー回避
        softmax = np.exp(tmp) / np.sum(np.exp(tmp), axis=0)
        n = np.random.choice(self.machine_num, p=softmax)
    # n番目をプレイする
    reward = self.game.play(n)
    return n, reward

def reinforcement_comparison(self):
    tmp = self.priority_rate-np.max(self.priority_rate)
    softmax = np.exp(tmp) / np.sum(np.exp(tmp), axis=0)
    n = np.random.choice(self.machine_num, p=softmax)
    # n番目をプレイする
    reward = self.game.play(n)
    self.priority_rate[n] += self.beta_pr*(reward-self.ref_reward)
    self.ref_reward += self.alpha_rc*(reward-self.ref_reward)
    return n, reward

def pursuit(self):
    self.priority_rate /= np.sum(self.priority_rate, axis=0)
    n = np.random.choice(self.machine_num, p=self.priority_rate)
    # n番目をプレイする
    reward = self.game.play(n)
    n_greedy = np.argmax(self.Reward_est)
    self.priority_rate[n_greedy] += self.beta_pr*(1-self.priority_rate[n_greedy])
    for i in range(self.machine_num):
        if i != n_greedy:
            self.priority_rate[i] -= self.beta_pr*self.priority_rate[i]
    return n, reward

# ----------------- 報酬の予想戦略を定義 -----------------
def sample_average(self, n, reward):
    self.Reward_est[n] += 1/(self.PlayTime[n]+1)*(reward-self.Reward_est[n])

def recency_weighted_average(self, n, reward):
    self.Reward_est[n] += self.alpha*(reward-self.Reward_est[n])

# ----------プレイするメソッドの定義-----------
def play(self):
    n, reward = self.act()  # n番目のマシンをプレイし、報酬を得る
    if self.act != self.reinforcement_comparison:  # 行動戦略が強化比較でない場合は、報酬の予想値を更新する
        self.est(n, reward)
    self.PlayTime[n] += 1
    return n, reward