トップページ -> Pythonで体験する強化学習 -> 強化学習を体験しよう ~バンディットタスク~

強化学習を体験しよう ~バンディットタスク~

今回は強化学習とはどのようなものなのかを解説した後に,バンディットタスク 迷路 三目並べ カートポール問題を通して強化学習を体験してみようと思います.

強化学習とは ~バンディットタスク~

強化学習を体験する前に強化学習の概要について簡単に説明します. 強化学習のキーワードとして以下のようなものがあります.

  1. エージェント(agent)
  2. 私たちが強化学習によって制御しようとするものです. ロボット制御ならロボットですし,将棋AIならプレイヤーがエージェントとなります.
  3. 状態(state)
  4. 強化学習エージェントは自身の状態を把握することができるように設計します. ロボット制御なら自身の位置 腕の曲がり具合など 将棋AIなら盤面の状態を把握できるようにします.
  5. 行動(action)
  6. 強化学習エージェントは「行動」を取ることにより「状態」を変化させることができます. ロボット制御なら腕を上げるのか 下すのか 将棋AIなら指し手により「状態」を変化させます.
  7. 報酬(reward)
  8. 強化学習エージェントは「行動」の結果により「環境」から「報酬」を受け取るように設計します. ロボット制御なら物を持ち上げられたら報酬+1を 落としてしまったら報酬-1 将棋AIなら勝ったら報酬+1 負けたら報酬-1 などを与えます.
  9. 環境(environment)
  10. 状態Sで行動Aを取ると報酬Rがもらえるといった「状態 行動 報酬」をセットにしたものを「環境」と言います. ゲームを考える場合はルールそのものが環境です.
強化学習では「報酬」を適切に設定することによってエージェントに最適な「行動」を学習させることを目的とします.

それでは早速,強化学習を使って問題解決をしてみましょう.

バンディットタスク

2本の腕A,Bを持つ機械を考えます. 一方の腕は引くと平均して45円が出てきます.もう一方の腕は引くと平均して60円が出てきます. 私たちはこの機械の腕を30回引くことができます. たくさんのお金を得るためにはどうすればいいでしょうか? まずは,実際にプレイしてみましょう.

バンディットタスクに関する関数

            
# バンディットタスクに関わる関数
# ランダムに行動するエージェント
def random_agent():
    action = random.choice(["A","B"])
    return action

# 人間がプレイする場合に使う関数
def human_choice():
    action = input("A or B:")
    if action == "A" or action == "B":
        return action
    else:
        action = random.choice(["A","B"])
        return action
        
# 強化学習エージェント
def RL_agent():
    ####### ここで強化学習エージェントを設計する #######
    pass
    ####### ここで強化学習エージェントを設計する #######

# 2本腕バンディットマシーン
def bandit_machine(action,A,B):
    if action == "A":
        reward = np.random.normal(A,20)
    elif action == "B":
        reward = np.random.normal(B,20)
    
    return int(reward)
            
        

ランダムに30回選んだ場合の結果

            
# ランダムに腕を引かせた場合のバンディットタスク
import random
import numpy as np

# A,B の平均を決める
rnd = random.random()
if rnd <= 0.5:
    A,B = 45,60
else:
    A,B = 60,45
    
# 報酬のリストを初期化
reward_list = np.zeros(30)

for i in range(30):
    ###### ここで行動選択 ########
    action = random_agent()
    ###### ここで行動選択 ########
    
    reward = bandit_machine(action,A,B)
    reward_list[i] = reward
    
print(sum(reward_list/30))
            
        
ランダムで引いた場合は平均して52くらいの報酬が得られます.

バンディットタスクを自分でプレイする

            
# 自分で腕を引く場合のバンディットタスク 30回やってみる
import random
import numpy as np

# A,B の平均を決める
rnd = random.random()
if rnd <= 0.5:
    A,B = 45,60
else:
    A,B = 60,45
    
# 報酬のリストを初期化
reward_list = np.zeros(30)

for i in range(30):
    ###### ここで行動選択 ########
    action = human_choice()
    ###### ここで行動選択 ########
    
    reward = bandit_machine(action,A,B)
    print(reward)
    reward_list[i] = reward
    
print(f"A:{A} B:{B}")
print("average:",sum(reward_list/30))
            
        

自分でプレイしてみた場合はどうでしょうか? ぜひ1度試してみてください. 回数が少ないうえに排出量に乱数が絡むためランダムより平均報酬が少なくなる場合もあるかもしれませんが, おおむね50より大きな報酬が得られたと思います.(回数が少ないので正しいほうを多く選んでいても少なくなることがありますが,気にしないでください) なぜ,私たちはランダムより大きな報酬を得ることができたのでしょうか? 実際にプレイされた方は,今までにAを引いて出た報酬とBを引いて出た報酬を比べ 経験(予想される報酬)に基づいて「多く出そうな方」を選択されたのではないでしょうか? また,「もしかしたらこっちの方が多く出るかもしれない」とあえて違う方の腕を引いたりもしたのではないでしょうか? 強化学習では経験に基づいて良さそうな行動を選択することを経験の「活用」 そうではない行動を取ってみることを「探索」と言います. 私たちは「探索」と「活用」を行ってよりよい報酬を得たのです. それでは,強化学習の方法を使ってコンピュータにバンディットタスクを解かせてみましょう.

予測に従って行動するエージェント

期待される報酬のみに従って行動するエージェントが以下のコードです. 1回プレイするごとにプレイの結果に基づいて予測報酬を更新していきます. 予想よりも大きな報酬が得られたら上方修正 予想よりも小さな報酬しか得られなければ下方修正したいため, 予測と実際の報酬の誤差を使って次のように更新します.
V:予測報酬 reward:実際に得られた報酬 alpha:学習率 としたとき, V += alpha*(reward - V) で予測報酬を更新します. 学習率αはどの程度 慎重に予測報酬を更新するかを表します. 学習率が大きければ大きいほど予測報酬を大きく更新します. 例えば,予測報酬が20の状態で実際のプレイで40の報酬が得られたとします. つまりV=20, reward=40という状態です. このとき,alpha=0.2 alpha=0.6 alpha=1としてそれぞれ計算してみます.
alpha=0.2のとき V += 0.2*(40-20) → V = 24
alpha=0.6のとき V += 0.6*(40-20) → V = 32
alpha = 1のとき V += (40-20) → V = 40
このように予測報酬を更新していくことでよりよい行動を目指します. 以下が予測報酬のみに沿って行動するエージェントです. 予測報酬の初期値V_a,V_bはそれぞれ20としてあります.

予測報酬のみに沿って行動するエージェント

            
import random
import numpy as np

# 予測報酬が大きいほうを選ぶエージェント
def RL_agent(V_a,V_b):
    if V_a < V_b:
        action = "B"
    elif V_a >= V_b:
        action = "A"
        
    return action

# A,B の平均を決める
rnd = random.random()
if rnd <= 0.5:
    A,B = 45,60
else:
    A,B = 60,45
    
# 報酬のリストを初期化
reward_list = np.zeros(30)

count_A = 0 # Aを選んだ回数
count_B = 0 # Bを選んだ回数

# A,Bの期待値を記憶する
V_a = 20
V_b = 20

for i in range(30):
    ###### ここで行動選択 ########
    action = RL_agent(V_a,V_b)
    ###### ここで行動選択 ########
    
    reward = bandit_machine(action,A,B)
    if action == "A":
        V_a += 0.5*(reward - V_a) # 予測報酬を誤差を使って更新する
        count_A += 1
    if action == "B":
        V_b += 0.5*(reward - V_b) # 予測報酬を誤差を使って更新する
        count_B += 1
        
    reward_list[i] = reward
    
print(f"A:{A} B:{B}")
print(f"V_a:{V_a:.1f} V_b:{V_b:.1f}")
print(f"count_A:{count_A} count_B:{count_B}")
print(f"average: {sum(reward_list/30):.1f}")
            
        

このような期待報酬の高い行動を取る戦略のことをグリーディな戦略と言います. このコードでは探索を行わないため多くの場合どちらか一方の行動しか選ばれません. 鋭い方には「初期値を20にしているんだからそれはそうだ」と見抜かれてしまうかもしれません. 確かにこの問題の場合は事前に報酬が45,60であることが分かっているため初期値を高めに80(楽観的初期値)などと設定しておけばいいのですが,多くの問題は強化学習エージェントを設計する段階では プログラムを書く私たちにもどの程度の期待値で報酬を返すのかが分かりません. そのため,このような事故が起きてしまいました. 解決するために一定の割合で探索をするように改良してみましょう.

探索を行うエージェント

            
# epsilon(0<=epsilon<=1)の確率で探索するエージェント
def epsilon_greedy(V_a,V_b,epsilon):
    rnd = random.random()
    # epsilonの確率で期待報酬の低いほうを選び探索する
    if rnd < epsilon:
        if V_a < V_b:
            action = "A"
        elif V_a >= V_b:
            action = "B"
    # 1-epsilonの確率で期待報酬の高いほうを選ぶ
    else:
        if V_a < V_b:
            action = "B"
        elif V_a >= V_b:
            action = "A"
        
    return action
            
        

行動選択の部分をepsilon_greedyに変えて何回か実行してみてください. 今回は探索を行ったため期待値の大きい場合を選ぶことが多そうです. 探索の割合は高すぎると無駄が多くなり,低すぎると正しいほうを選べなくなります. 様々な割合や学習率で試してみてください. 確率εで探索を,確率1-εで活用を行うような戦略をε-グリーディな戦略と言います.

強化学習エージェントが探索と予測報酬の更新によって学習を進めていく様子を見ていただきました. 次回は強化学習エージェントが迷路を学習する様子を見ていきましょう.

<- 前へ戻る 【目次に戻る】 次へ進む ->