トップページ -> Pythonで体験する強化学習 -> 強化学習を体験しよう ~Cart-Pole問題~

強化学習を体験しよう ~Cart-Pole問題~

環境の説明

今回はcart-pole問題をQ学習で解決します. cart-pole問題とは以下の動画のように左右(1次元的)に動く滑車の上に回転軸まわりにのみ運動するように固定された振子を倒さないように滑車を制御(右か左に動かす)する問題です. 今回のポイントは状態をどのように把握するかです.


Open AI Gymの環境を用います.滑車を制御する際に利用できる情報は以下の通りです. ゲームの詳細が知りたい方はこちらを参照してください.
Min Max
滑車の位置 -4.8 +4.8
滑車の速度 -∞ +∞
棒の角度 -0.418ラジアン(-24度) +0.418ラジアン(+24度)
棒の角速度 -∞ +∞
滑車の位置か棒の角度が最大値・最小値を超えてしまった場合,失敗となります.

強化学習をする

今回もQ学習で問題を解決していきます. 前回の三目並べと異なるのは,状態(state)が連続的に変化する点です. 滑車の位置を-4.8 -4.79999 -4.79998 … と細かく観測するようにすると,状態数が多すぎて学習が進まないうえに,利用可能な記憶容量を超えてしまいます. そのため,状態の観測の仕方を少し工夫する必要があります.

余分な情報をそぎ落とす

状態を多少 大雑把に観測するようにしても学習は進みます.そこで連続的な状態を以下のように離散化してみます.
0 1 2 3 4 5
滑車の位置 -2.4未満 -1.2未満 0未満 1.2未満 2.4未満 2.4以上
滑車の速度 -3未満 -1.5未満 0未満 1.5未満 3未満 3以上
棒の角度 -0.28未満 -0.14未満 0未満 0.14未満 0.28未満 0.28以上
棒の角速度 -2.0未満 -1.0未満 0未満 1.0未満 2.0未満 2.0以上
それぞれの変数を6通りで分類するようにしました.変数が4つあるため,状態数は6^4通りです. それぞれの状態に対して右に動かす 左に動かすの2通りの行動がありますから,大きさ 2×(6^4) のQ_tableを用意します. 滑車の位置が1 滑車の速度が2 棒の角度が2 棒の角速度が4とすると,数字の順番は自由ですが,4221(6進表記)と考えればそれぞれの状態に一対一対応させることができます.

実際のコード

posit:滑車の位置 Cvelo:滑車の速度 angle:角度 Pvelo:棒の角速度 としてあります. np.digitize(x, np.linspace(a, b, n)) 以外は前回の三目並べとほとんど変わりません.
np.linspace(a, b, n): aからbまで要素nの等間隔の配列を返します. 例えば, np.linspace(0, 100, 5) = [0,25,50,75,100] と言ったように先読みをすることができるようになります.
np.digitize(x, numpy_array): 単調増大 単調減少の配列に対して,xが(i-1)番目の要素より大きく,i番目の要素より小さくなるようなiを返します.

学習に使う関数

                
import numpy as np
import matplotlib.pyplot as plt
import gym
import random

Q_table = np.random.uniform(-1,1,(6**4,2))

# 評価値が最大になるように行動を選択する関数
def choice(state,Q_table,epsilon):
    rnd = random.random()
    if rnd < epsilon:
        action = random.randint(0,1)
    else:
        state = return_index(state)
        action = np.argmax(Q_table[state])
        
    return action
   
# state をQ_table のインデックスに変換する関数
def return_index(state):
    posit,Cvelo,angle,Pvelo = state
    indQ1 = [np.digitize(posit, np.linspace(-2.4, 2.4, 5)),np.digitize(Cvelo, np.linspace(-3.0, 3.0, 5)),np.digitize(angle, np.linspace(-0.28, 0.28, 5)),np.digitize(Pvelo, np.linspace(-2.0, 2.0, 5))] #Qテーブルのどこを見るかがわかる
    indQ2 = indQ1[0] + indQ1[1]*6 + indQ1[2]*(6**2) + indQ1[3]*(6**3) #それぞれが0~5まででこれを0~1295に1対1対応させたいので

    return indQ2

# Q_tableを更新する関数
def Q_value(state,action,next_state,Q_table,reward,alpha,gamma):
    state = return_index(state)
    next_state = return_index(next_state)
    Q_table[state,action] = Q_table[state,action] + alpha*(reward + gamma*max(Q_table[next_state]) - Q_table[state,action])
                
            
return_indexはQ_tableのインデックスを返す関数です.

学習をするコード

                
# 環境を'CartPole-v1'にする
env = gym.make('CartPole-v1')
score_list =np.zeros(1200)
for i in range(1200):
    state = env.reset() # 最初の状態をstateに代入
    epsilon = 0.7 - i/1000 # 徐々にεを下げていき,700以降ではランダムに動かなくする
    for t in range(200):
        # env.render() # 動きを動画で見たくないときにコメントアウトする
        
        action = choice(state,Q_table,epsilon)
        
        next_state, reward, done, info = env.step(action)
        
        if done:
            # 195stepより先に倒れてしまったら -200 の報酬
            if t < 195:
                reward = -200
            else:
                reward = 1
        # 終わっていない間は1step耐えるごとに 1 の報酬
        else:
            reward = 1
            
        Q_value(state,action,next_state,Q_table,reward,0.2,0.95)
        state = next_state
    
        if done: # done == True ならループを抜ける         
            break
            
    print(f"{i}: {t+1} timestep後に倒れた")
    score_list[i] = t

env.close()  #環境を閉じる

# グラフの描画
plt.plot(score_list)
plt.show()
                
            
徐々にεを下げていき,ランダム性を減らしています. 700回目以降は完全にQ_tableに従って動きます. ちなみにε=0として探索を行わないようにすると以下のようになかなか学習が進みません.
ε=0のグラフ

ε=0.7-i/1000 とした場合は,以下のようになります.
ε=0.7-i/1000のグラフ

探索を積極的に行わせるようにし1200回学習させることで滑車の上の棒を倒さないようにある程度維持できるようになりました. このやり方でもよいのですが,今回の場合 ε-グリーディな探索のやり方は無駄が多いです. なぜなら,Q_table[state][0] = 1000, Q_table[state][1] = -1000 のような行動0を取れば1000の報酬が期待できるが,行動1を取れば-1000の報酬になりそう. といった,極端な場合でも一定の確率でランダムに動いてしまいます.つまり,探索が必要ないような場面でも探索を行ってしまうのです. ソフトマックス関数と呼ばれる関数を使って,それぞれの行動が選ばれる確率を設定します.
ソフトマックス戦略

以下がソフトマックス戦略で行動を選択する関数です.
                
# ソフトマックス戦略で行動を選択する関数
def choice(state,Q_table):
    rnd = random.random()
    a = 0
    state = return_index(state)
    for i in range(2):
        a += np.exp(Q_table[state][i])/(np.exp(Q_table[state][0])+np.exp(Q_table[state][1]))
        if rnd < a:
            action = i
            break
            
    return action
                
            
この関数を利用して学習を行ったときの結果のグラフが以下の通りです.
ソフトマックス戦略のグラフ

探索を効率的に行うことで500回程度で学習が終了し以降は,200timestepのあいだ棒を倒さずに維持できるようになりました.

実演しませんが,状態の観測の仕方を変える(区切りを細かくするなど)と学習が速くなったりします. 余裕がある人は試してみてください.

今回はソフトマックス戦略を用いてQ学習を行いました.

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