21 KiB
前提条件
このレッスンでは、OpenAI Gymというライブラリを使用して、さまざまな環境をシミュレーションします。このレッスンのコードはローカル環境(例: Visual Studio Code)で実行することができ、その場合シミュレーションは新しいウィンドウで開きます。オンラインでコードを実行する場合は、こちらに記載されているように、コードにいくつかの調整が必要になる場合があります。
OpenAI Gym
前回のレッスンでは、ゲームのルールと状態が自分で定義したBoardクラスによって提供されていました。今回は、垂直の棒をバランスさせる物理をシミュレーションする特別なシミュレーション環境を使用します。強化学習アルゴリズムをトレーニングするための最も人気のあるシミュレーション環境の1つがGymであり、これはOpenAIによって管理されています。このGymを使用することで、カートポールのシミュレーションからアタリゲームまで、さまざまな環境を作成できます。
Note: OpenAI Gymで利用可能な他の環境はこちらで確認できます。
まず、Gymをインストールし、必要なライブラリをインポートしましょう(コードブロック1):
import sys
!{sys.executable} -m pip install gym
import gym
import matplotlib.pyplot as plt
import numpy as np
import random
演習 - カートポール環境を初期化する
カートポールのバランス問題に取り組むには、対応する環境を初期化する必要があります。各環境には以下が関連付けられています:
-
観測空間: 環境から受け取る情報の構造を定義します。カートポール問題では、棒の位置、速度、その他の値を受け取ります。
-
アクション空間: 可能なアクションを定義します。この場合、アクション空間は離散的で、左と右の2つのアクションで構成されています。(コードブロック2)
-
初期化するには、以下のコードを入力してください:
env = gym.make("CartPole-v1") print(env.action_space) print(env.observation_space) print(env.action_space.sample())
環境がどのように動作するかを確認するために、100ステップの短いシミュレーションを実行してみましょう。各ステップで、action_spaceからランダムに選択したアクションを提供します。
-
以下のコードを実行して結果を確認してください。
✅ このコードはローカルのPythonインストールで実行することを推奨します!(コードブロック3)
env.reset() for i in range(100): env.render() env.step(env.action_space.sample()) env.close()以下の画像のような結果が表示されるはずです:
-
シミュレーション中に、どのように行動するかを決定するために観測値を取得する必要があります。実際には、
step関数は現在の観測値、報酬関数、およびシミュレーションを続行するかどうかを示すフラグを返します。(コードブロック4)env.reset() done = False while not done: env.render() obs, rew, done, info = env.step(env.action_space.sample()) print(f"{obs} -> {rew}") env.close()ノートブックの出力で以下のような結果が表示されるはずです:
[ 0.03403272 -0.24301182 0.02669811 0.2895829 ] -> 1.0 [ 0.02917248 -0.04828055 0.03248977 0.00543839] -> 1.0 [ 0.02820687 0.14636075 0.03259854 -0.27681916] -> 1.0 [ 0.03113408 0.34100283 0.02706215 -0.55904489] -> 1.0 [ 0.03795414 0.53573468 0.01588125 -0.84308041] -> 1.0 ... [ 0.17299878 0.15868546 -0.20754175 -0.55975453] -> 1.0 [ 0.17617249 0.35602306 -0.21873684 -0.90998894] -> 1.0シミュレーションの各ステップで返される観測ベクトルには以下の値が含まれています:
- カートの位置
- カートの速度
- 棒の角度
- 棒の回転速度
-
これらの数値の最小値と最大値を取得してください。(コードブロック5)
print(env.observation_space.low) print(env.observation_space.high)また、各シミュレーションステップでの報酬値が常に1であることに気付くかもしれません。これは、目標ができるだけ長く生き残ること、つまり棒を垂直に近い位置に保つことだからです。
✅ 実際、CartPoleシミュレーションは、100回連続の試行で平均報酬が195に達した場合に解決されたと見なされます。
状態の離散化
Q-Learningでは、各状態で何をすべきかを定義するQ-テーブルを構築する必要があります。これを行うには、状態を離散化する必要があります。つまり、有限の離散値を含む必要があります。そのため、観測値を離散化し、有限の状態セットにマッピングする必要があります。
これを行う方法はいくつかあります:
- ビンに分割する: 特定の値の範囲がわかっている場合、その範囲をいくつかのビンに分割し、その値を属するビン番号に置き換えることができます。これはnumpyの
digitizeメソッドを使用して行うことができます。この場合、選択したビンの数に応じて状態サイズを正確に把握できます。
✅ 線形補間を使用して値を有限の範囲(例えば-20から20)に持っていき、丸めることで整数に変換することができます。この方法では、入力値の正確な範囲がわからない場合でも、状態サイズを少し制御できます。例えば、今回のケースでは、4つの値のうち2つは上限/下限が定義されていないため、状態の数が無限になる可能性があります。
この例では、2番目の方法を使用します。後で気付くかもしれませんが、上限/下限が定義されていないにもかかわらず、これらの値が特定の有限範囲を超えることはほとんどありません。そのため、極端な値を持つ状態は非常にまれです。
-
以下は、モデルからの観測値を受け取り、4つの整数値のタプルを生成する関数です。(コードブロック6)
def discretize(x): return tuple((x/np.array([0.25, 0.25, 0.01, 0.1])).astype(np.int)) -
ビンを使用した別の離散化方法も試してみましょう。(コードブロック7)
def create_bins(i,num): return np.arange(num+1)*(i[1]-i[0])/num+i[0] print("Sample bins for interval (-5,5) with 10 bins\n",create_bins((-5,5),10)) ints = [(-5,5),(-2,2),(-0.5,0.5),(-2,2)] # intervals of values for each parameter nbins = [20,20,10,10] # number of bins for each parameter bins = [create_bins(ints[i],nbins[i]) for i in range(4)] def discretize_bins(x): return tuple(np.digitize(x[i],bins[i]) for i in range(4)) -
短いシミュレーションを実行し、これらの離散化された環境値を観察してみましょう。
discretizeとdiscretize_binsの両方を試して違いがあるか確認してください。✅
discretize_binsは0ベースのビン番号を返します。そのため、入力変数が0付近の値の場合、範囲の中央(10)の番号を返します。一方、discretizeでは出力値の範囲を気にせず、負の値を許容しているため、状態値はシフトされず、0が0に対応します。(コードブロック8)env.reset() done = False while not done: #env.render() obs, rew, done, info = env.step(env.action_space.sample()) #print(discretize_bins(obs)) print(discretize(obs)) env.close()✅ 環境の実行を確認したい場合は、
env.renderで始まる行をコメント解除してください。そうでない場合はバックグラウンドで実行することができ、これにより速度が向上します。この「非表示」実行をQ-Learningプロセス中に使用します。
Q-テーブルの構造
前回のレッスンでは、状態は0から8までの単純な数字のペアであり、そのためQ-テーブルを形状が8x8x2のnumpyテンソルで表現するのが便利でした。ビンの離散化を使用する場合、状態ベクトルのサイズもわかっているため、同じアプローチを使用して状態を形状が20x20x10x10x2の配列で表現することができます(ここで2はアクション空間の次元であり、最初の次元は観測空間内の各パラメータに使用するビンの数に対応します)。
ただし、観測空間の正確な次元がわからない場合があります。discretize関数の場合、元の値の一部が制限されていないため、状態が特定の制限内に留まることを保証することはできません。そのため、少し異なるアプローチを使用し、Q-テーブルを辞書で表現します。
-
*(state,action)*のペアを辞書のキーとして使用し、その値をQ-テーブルのエントリ値に対応させます。(コードブロック9)
Q = {} actions = (0,1) def qvalues(state): return [Q.get((state,a),0) for a in actions]ここでは、
qvalues()関数も定義しており、指定された状態に対応するすべての可能なアクションに対するQ-テーブル値のリストを返します。Q-テーブルにエントリが存在しない場合、デフォルト値として0を返します。
Q-Learningを始めましょう
これで、ピーターにバランスを取る方法を教える準備が整いました!
-
まず、いくつかのハイパーパラメータを設定しましょう。(コードブロック10)
# hyperparameters alpha = 0.3 gamma = 0.9 epsilon = 0.90ここで、
alphaは学習率であり、各ステップでQ-テーブルの現在の値をどの程度調整するべきかを定義します。前回のレッスンでは1から始め、トレーニング中にalphaを低い値に減少させました。この例では簡単にするために一定に保ちますが、後でalpha値を調整して実験することができます。gammaは割引率であり、現在の報酬よりも将来の報酬をどの程度優先するべきかを示します。epsilonは探索/活用率であり、探索を優先するか活用を優先するかを決定します。このアルゴリズムでは、epsilonの割合でQ-テーブル値に基づいて次のアクションを選択し、残りのケースではランダムなアクションを実行します。これにより、これまで見たことのない探索空間の領域を探索することができます。✅ バランスを取るという観点では、ランダムなアクション(探索)を選択することは、間違った方向へのランダムなパンチのようなものであり、棒はこれらの「間違い」からバランスを回復する方法を学ぶ必要があります。
アルゴリズムを改善する
前回のレッスンからアルゴリズムを2つ改善することができます:
-
平均累積報酬を計算する: 複数のシミュレーションにわたって平均累積報酬を計算します。5000回の反復ごとに進捗を表示し、その期間の累積報酬を平均化します。これにより、195ポイント以上を獲得した場合、問題が解決されたと見なすことができ、必要条件を超える高品質な結果が得られます。
-
最大平均累積結果を計算する:
Qmaxを計算し、その結果に対応するQ-テーブルを保存します。トレーニングを実行すると、平均累積結果が低下し始めることがあり、トレーニング中に観察された最良のモデルに対応するQ-テーブルの値を保持したいと考えています。
-
各シミュレーションで累積報酬を
rewardsベクトルに収集し、後でプロットします。(コードブロック11)def probs(v,eps=1e-4): v = v-v.min()+eps v = v/v.sum() return v Qmax = 0 cum_rewards = [] rewards = [] for epoch in range(100000): obs = env.reset() done = False cum_reward=0 # == do the simulation == while not done: s = discretize(obs) if random.random()<epsilon: # exploitation - chose the action according to Q-Table probabilities v = probs(np.array(qvalues(s))) a = random.choices(actions,weights=v)[0] else: # exploration - randomly chose the action a = np.random.randint(env.action_space.n) obs, rew, done, info = env.step(a) cum_reward+=rew ns = discretize(obs) Q[(s,a)] = (1 - alpha) * Q.get((s,a),0) + alpha * (rew + gamma * max(qvalues(ns))) cum_rewards.append(cum_reward) rewards.append(cum_reward) # == Periodically print results and calculate average reward == if epoch%5000==0: print(f"{epoch}: {np.average(cum_rewards)}, alpha={alpha}, epsilon={epsilon}") if np.average(cum_rewards) > Qmax: Qmax = np.average(cum_rewards) Qbest = Q cum_rewards=[]
これらの結果から以下のことがわかります:
-
目標に近い: 100回以上の連続したシミュレーションで195の累積報酬を達成する目標に非常に近づいているか、実際に達成している可能性があります。たとえ小さい数値を得たとしても、5000回の実行で平均化しているため、正式な基準では100回の実行のみが必要です。
-
報酬が低下し始める: 報酬が低下し始めることがあり、これはQ-テーブルに既に学習された値を上書きして状況を悪化させる可能性があることを意味します。
この観察はトレーニングの進捗をプロットするとより明確になります。
トレーニング進捗のプロット
トレーニング中に、各反復で累積報酬値をrewardsベクトルに収集しました。これを反復番号に対してプロットすると以下のようになります:
plt.plot(rewards)
このグラフからは何も判断できません。これは確率的なトレーニングプロセスの性質上、トレーニングセッションの長さが大きく異なるためです。このグラフをより理解しやすくするために、100回の実験にわたる移動平均を計算します。これをnp.convolveを使用して便利に計算できます。(コードブロック12)
def running_average(x,window):
return np.convolve(x,np.ones(window)/window,mode='valid')
plt.plot(running_average(rewards,100))
ハイパーパラメータの調整
学習をより安定させるために、トレーニング中にいくつかのハイパーパラメータを調整することが理にかなっています。特に:
-
学習率
alphaについては、1に近い値から始め、その後徐々にこのパラメータを減少させることができます。時間が経つにつれて、Q-テーブルに良い確率値が得られるようになり、新しい値で完全に上書きするのではなく、わずかに調整するべきです。 -
epsilonを増加させる:
epsilonを徐々に増加させ、探索を減らし、活用を増やすことが理にかなっています。低い値のepsilonから始め、ほぼ1に近づけるのが良いでしょう。
タスク 1: ハイパーパラメータの値を調整して、より高い累積報酬を得られるか試してみましょう。195を超えていますか? タスク 2: 問題を正式に解決するには、100回連続の実行で平均報酬195を達成する必要があります。トレーニング中にそれを測定し、問題を正式に解決したことを確認してください!
結果を実際に確認する
トレーニングされたモデルがどのように動作するかを見るのは興味深いでしょう。シミュレーションを実行し、トレーニング中と同じ行動選択戦略を採用して、Q-Tableの確率分布に従ってサンプリングしてみましょう:(コードブロック 13)
obs = env.reset()
done = False
while not done:
s = discretize(obs)
env.render()
v = probs(np.array(qvalues(s)))
a = random.choices(actions,weights=v)[0]
obs,_,done,_ = env.step(a)
env.close()
以下のような結果が表示されるはずです:
🚀チャレンジ
タスク 3: ここでは、Q-Tableの最終版を使用していましたが、それが必ずしも最良のものとは限りません。最もパフォーマンスの良いQ-Tableを
Qbest変数に保存していることを思い出してください!QbestをQにコピーして、最良のQ-Tableを使用した場合に違いがあるかどうか試してみてください。
タスク 4: ここでは各ステップで最良の行動を選択するのではなく、対応する確率分布に基づいてサンプリングしていました。Q-Tableの値が最も高い行動を常に選択する方が理にかなっているでしょうか?これは、
np.argmax関数を使用してQ-Table値が最も高い行動番号を見つけることで実現できます。この戦略を実装して、バランスが改善されるかどうか確認してください。
講義後のクイズ
課題
結論
私たちは、ゲームの望ましい状態を定義する報酬関数を提供し、探索空間を賢く探索する機会を与えることで、エージェントをトレーニングして良い結果を達成する方法を学びました。Q-Learningアルゴリズムを離散環境と連続環境のケースで成功裏に適用しましたが、行動は離散的でした。
行動状態も連続的である場合や、観測空間がより複雑である場合(例えば、Atariゲーム画面の画像など)についても研究することが重要です。そのような問題では、良い結果を得るためにニューラルネットワークのようなより強力な機械学習技術を使用する必要があることがよくあります。これらのより高度なトピックは、今後のより高度なAIコースのテーマとなります。
免責事項:
この文書は、AI翻訳サービス Co-op Translator を使用して翻訳されています。正確性を追求しておりますが、自動翻訳には誤りや不正確な部分が含まれる可能性があります。元の言語で記載された文書を正式な情報源としてご参照ください。重要な情報については、専門の人間による翻訳を推奨します。この翻訳の使用に起因する誤解や誤解釈について、当方は一切の責任を負いません。



