著者 | マキシム・ラボンヌ 編纂者:岳陽 🚢🚢🚢AIテクノロジーソフトウェアと技術交流グループへのご参加をお待ちしております!最新のトレンドを把握し、一緒に技術的な課題を探求しましょう! この記事の目標は、強化学習アルゴリズムを用いてFrozen Lakeのゲーム環境を解く方法を人工知能に教えることです。ゼロからスタートし、Q学習アルゴリズムを自ら再現してみます。その仕組みを理解するだけでなく、さらに重要なのは、なぜそのように設計されているのかを理解することです。 この記事は、読者がQ学習アルゴリズムを習得し、他の実世界の問題に適用するためのスキルを身につけることを目的としています。これは、強化学習の仕組みをより深く理解し、より創造的な製品アイデアを生み出すのに役立つ楽しいミニプロジェクトです。 まず、Frozen Lake ゲーム環境をインストールし、ゲーム環境をシミュレートするための gym 、乱数を生成するための random 、数学演算用の numpy などの必要なライブラリをインポートする必要があります。 !pip インストール -q ジム !pip インストール -q matplotlib ジムをインポート ランダムにインポート numpyをnpとしてインポートする 01 ❄️ 凍った湖さて、このチュートリアルでアルゴリズムを使って解くゲームについてお話ししましょう。Frozen Lakeはブロックで構成されたシンプルなゲーム環境で、AIはスタートブロックからターゲットブロックまで移動する必要があります。
ゲーム環境コードでは、各ブロックは次のように文字で表されます。 S F F F (S: 出発点、安全点) F H F H (F:凍結面、安全) F F F H (H: 穴、永遠に閉じ込められる) H F F G(G:ゴール、セーフ) ゲームを理解するために、上記の例を手動で解いてみましょう。次の一連のアクションが正しい解法かどうか確認してみましょう。右に移動 → 右に移動 → 右に移動 → 下に移動 → 下に移動 → 下に移動。エージェントはブロックSからスタートするため、右に移動すると氷の上に✅移動し、再び右に移動すると✅、再び右に移動すると✅、そして最後に下に移動すると穴に落ちます❌。 実は、複数の正解を見つけるのは難しくありません。右に移動 → 右に移動 → 下に移動 → 下に移動 → 下に移動 → 右に移動 というシンプルな解答です。しかし、目標ブロックに到達する前に穴を10回周回するという一連のアクションを考案することもできます。このエージェントのアクションパスは有効ですが、ゲームの要件を満たしていません。エージェントは可能な限り少ないアクションで目標ブロックに到達する必要があります。この例では、ゲームを完了するために必要な最小アクション数は6です。エージェントがFrozen Lakeゲームを本当にマスターしたかどうかを確認するには、この結論を覚えておく必要があります。 gymライブラリを使ってゲーム環境を初期化しましょう。このゲームには2つのバージョンがあります。1つは滑りやすい氷の表面を持つバージョンで、ゲームコマンドがエージェントによって一定確率で無視されます。もう1つは滑りやすい表面を持たないバージョンで、ゲームコマンドは無視されません。この記事では、分かりやすいため、まず滑りにくいバージョンを使用します。 環境 = gym.make("FrozenLake-v1", is_slippery=False)
環境.リセット()
環境.レンダリング()🟥FFF FHFH FFFH HFFG ご覧のとおり、作成したゲーム環境は前の例のゲーム設定と全く同じです。エージェントの位置は赤い四角で示されています。このゲームはシンプルなスクリプトとif...else条件文を使って解くことができ、よりシンプルな手法と私たちのAIを比較するのに役立ちます。しかし、今回はより興味深いソリューション、強化学習を試してみたいと思います。 02 🏁 QテーブルFrozen Lakeゲームには16個のブロックがあり、エージェントは16通りの位置(状態)を取ることができ、結果として16通りの状態が考えられます。それぞれの状態に対して、左へ移動◀️、下へ移動🔽、右へ移動▶️、上へ移動🔼の4つのアクションから選択できます。Frozen Lakeの遊び方を学ぶことは、それぞれの状態においてどのアクションを選択するかを学ぶことに似ています。特定の状態においてどのアクションが最適かを判断するには、アクションに品質値を割り当てる必要があります。16の状態と4つのアクションがある場合、16 x 4 = 64通りの品質値を計算する必要があります。 これを表現する良い方法は、Qテーブルと呼ばれるテーブルを使うことです。行は状態`s`、列はアクション`a`を表します。このQテーブルでは、各セルには値`Q(s, a)`が含まれます。これは、状態`s`におけるアクション`a`の品質値を表します(現在の状態において最適なアクションであれば品質値は1、最悪のアクションであれば品質値は0です)。エージェントが特定の状態`s`にあるとき、このテーブルを参照して、どのアクションが最も高い品質値を持つかを確認します。最も高い品質値を持つアクションを選択することは合理的な選択ですが、後で説明するように、より良い解決策を設計することができます。 S ◀️左🔽下 ▶️右🔼上 0 Q(0,◀️) Q(0,🔽) Q(0,▶️) Q(0,🔼) 1 Q(1,◀️) Q(1,🔽) Q(1,▶️) Q(1,🔼) 2 Q(2,◀️) Q(2,🔽) Q(2,▶️) Q(2,🔼) ………………………… 14 問(14,◀️) 問(14,🔽) 問(14,▶️) 問(14,🔼) G Q(15,◀️) Q(15,🔽) Q(15,▶️) Q(15,🔼) Qテーブルの簡単な例。各セルには、特定の状態s(行)におけるアクションa(列)の値Q(a, s)が含まれます。 まず、Q テーブルを作成し、各状態における各操作の値はまだわかっていないため、テーブル内のすべてのセルに 0 を入力します。 # テーブルのサイズは次のとおりです。
# (行 x 列) = (状態 x アクション) = (16 x 4)
qtable = np.zeros((16, 4))
# あるいは、ジムの図書館から直接
# 状態とアクションの数を取得するには
# "env.observation_space.n" と "env.action_space.n"
nb_states = 環境.観測空間.n # = 16
nb_actions = 環境.アクションスペース.n # = 4
qtable = np.zeros((nb_states, nb_actions))
# 見てみましょう
print('Qテーブル =')
print(qtable)Qテーブル = [[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]] 素晴らしい!これで、予想通り、16行(16の状態)と4列(4つのアクション)を持つQテーブルができました。次は何をすべきでしょうか?Qテーブルの各値は現在0なので、まだ情報がありません。エージェントがランダムにアクションを選択すると仮定しましょう。左に移動◀️、下に移動🔽、右に移動▶️、または上に移動🔼です。 `random` ライブラリの `choice` メソッドを使用して、アクションをランダムに選択できます。 random.choice(["左", "下", "右", "上"]) '左' しかし、エージェントは現在、初期マスSにいるので、実行可能なアクションは右へ移動▶️と下へ移動🔽の2つだけです。エージェントは上へ移動🔼と左へ移動◀️を選択することもできますが、実際には状態が変化しないため、エージェントは移動できません。したがって、利用可能なアクションに制限を設けず、エージェントはこれらのアクションの一部が実質的に効果がないことを自然に理解します。 `random.choice()` を使い続けることもできますが、gymライブラリには既にアクションをランダムに選択するメソッドが用意されています。これを使うと手間が省けるかもしれないので、試してみましょう。 環境.アクションスペース.サンプル() 0 ああ、困った… ランダムアクションは数字です。ジムのドキュメント [1] を読むことはできますが、残念ながらドキュメントは非常に限られています。でもご安心ください。GitHub [2] のソースコードを確認すれば、これらの数字の意味を理解できます。実際には非常にシンプルで分かりやすいです。 ◀️ 左 = 0 🔽 ダウン = 1 ▶️ 右 = 2 🔼 アップ = 3 さて、gymが数字と方向をどのように関連付けているのかがわかったので、gymを使ってエージェントを右 ▶️ に移動させてみましょう。今回はstep(action)メソッドを使えます。選択された方向(右)に対応する数字2を使って、エージェントが動くかどうか確認してみましょう。 環境.ステップ(2) 環境.レンダリング() (右) シンプル FHFH FFFH HFFG 素晴らしい!コードは正常に実行されました。赤い四角形は初期位置Sから右に移動しました。環境とやり取りするために必要な情報は次のとおりです。
特定の環境条件を完全に理解するために、次のことを追加することもできます。
ジム環境とのインタラクション方法がわかったので、この記事で解説したQ学習アルゴリズムに戻りましょう。強化学習では、ゲーム環境はエージェントが事前に設定された目標を達成すると報酬を与えます。Frozen Lakeゲームでは、エージェントは状態Gに到達した場合にのみ報酬を受け取ります(ソースコードを参照)。この報酬は制御できず、環境によって設定されます。エージェントがGに到達した場合は報酬が1、到達しなかった場合は報酬が0になります。 アクションが実行されるたびに報酬値を出力してみましょう。報酬は`step(action)`メソッドによって与えられます。 アクション = environment.action_space.sample()
# 2. このアクションを実装し、エージェントを目的の方向に移動させます
new_state、reward、done、info = environment.step(action)
# 結果(報酬とマップ)を表示する
環境.レンダリング()
print(f'報酬 = {報酬}')(左) 🟥FFF FHFH FFFH HFFG 報酬 = 0.0 報酬値は確かに0です…😱うわ、ゲーム全体で正の報酬を得られる状態は1つしかないので、これは困った状況ですね。ゲーム終了時にしか確認できないのなら、最初から正しい方向をどうやって選べるのでしょうか?報酬値を1にしたいなら、正しい行動の順序を偶然見つけられるほどの幸運が必要です。残念ながら、まさにその通りです…エージェントがランダムに目標Gに到達するまで、Qテーブルは常に0です。 エージェントを目標Gへと導くための小さな報酬を途中で得ることができれば、問題ははるかに単純になります。しかし、これは実際には強化学習における主要な問題の一つです。スパース報酬と呼ばれるこの現象は、長い一連の行動の後にのみ報酬が得られる問題でエージェントを訓練することを非常に困難にします。この問題を軽減する手法はいくつかありますが、それらについては後ほど説明します。 03 🤖 Q学習議論している問題に戻りましょう。今度は、幸運にも目標Gを偶然見つけられる必要があります。しかし、もし見つけたら、どのようにして情報を初期状態まで逆伝播させるのでしょうか?Q学習アルゴリズムはこの問題に巧妙な解決策を提供します。 (1) 次の状態に到達するための報酬値、(2) 次の状態の最大値を考慮して、状態と行動のペア(Qテーブルの各セル)の値を更新する必要があります。 エージェントは目標マスGに移動すると報酬(値1)を受け取ることが分かっています。先ほど述べたように、この報酬はGに隣接する状態(ここではG-1とします)の値を増加させます。さて、これで話は終わりです。エージェントは勝利し、ゲームが再開されます。エージェントが次にG-1の次の状態に到達したとき、G-1への到達に関連する操作を用いて、その状態(ここではG-2とします)の値を増加させます。エージェントは次にG-2の次の状態に到達したときも同じ操作を行います。このプロセスは、初期状態Sに到達するまで繰り返されます。 状態Gから状態Sへの値の逆伝播に適した式を見つけてみましょう。この値は、特定の状態における行動の質を表すことを覚えておいてください(その状態における最悪の行動は0、最良の行動は1です)。状態sₜにおける行動aₜの値を更新する必要があります(エージェントが初期状態Sにある場合、sₜ=0)。この値はQテーブル内の行番号sₜと列番号aₜに対応するセル、つまりQ(sₜ, aₜ)です。 前述のように、(1) 次の状態の報酬値(rₜと表記)と(2) 次の状態の最大値(maxₐQ(sₜ₊₁, a)と表記)を用いて更新する必要があります。したがって、更新式は以下のようになります。 新しい値は、現在の値と報酬値、そして次の状態における最高値を足した値です。この式の正しさは手動で検証できます。エージェントが初期状態G-1でターゲットGの次の状態にあると仮定すると、状態G-1における勝利アクションに対応する値は、以下の方法で更新できます。 初期値では、Q(G-1, aₜ) = 0、Qテーブルが空であるためmaxₐQ(G, a) = 0、ゲーム環境で獲得した報酬が1つだけであるためrₜ = 1となり、結果としてQ{new}(G-1, aₜ) = 1となります。エージェントがこの状態(G-2)に隣接する状態になったときも、同じ式で更新し、同じ結果、Q{new}(G-2, aₜ) = 1を得ます。最後に、QテーブルでGからSへバックプロパゲーションを行います。これは確かに機能しますが、結果は2値、つまり誤った状態と行動のペアか、最適なペアかのどちらかになります。今後、より詳細な情報が得られることを期待しています。 実際、真の Q 学習アルゴリズムの更新式を見つけることに近づいていますが、まだ 2 つのパラメータを追加する必要があります。
実際の Q 学習アルゴリズムでは、新しい値を計算するための式は次のとおりです。 さて、まずはこの新しい式を試してみて、それから使ってみましょう。エージェントが初期状態Gの次の状態にあると仮定しましょう。この式を使って状態と行動のペアを更新し、ゲームで勝利を収めることができます。Q{new}(G-1, aₜ) = 0 + α - (1 + γ - 0 - 0)。αとγに任意の値を割り当てて結果を計算できます。α = 0.5、γ = 0.9のとき、Q{new}(G-1, aₜ) = 0 + 0.5 - (1 + 0.9 - 0 - 0) = 0.5となります。エージェントが 2 回目にこの状態になると、Q{new}(G-1, aₜ) = 0.5 + 0.5 - (1 + 0.9 - 0 - 0.5) = 0.75 となり、その後 0.875、0.9375、0.96875 と続きます。 コードを使用してエージェントをトレーニングするということは、次のことを意味します。
matplotlib.pyplot を plt としてインポートします。
plt.rcParams['figure.dpi'] = 300
plt.rcParams.update({'font.size': 17})
# Qテーブルを再初期化します
qtable = np.zeros((environment.observation_space.n, environment.action_space.n))
# ハイパーパラメータ
エピソード = 1000 # エピソードの総数
アルファ = 0.5 # 学習率
ガンマ = 0.9 # 割引係数
# プロットする結果のリスト
結果 = []
print('トレーニング前のQテーブル:')
print(qtable)
# トレーニング
_ が範囲内(エピソード数)の場合:
状態 = 環境.reset()
完了 = False
# デフォルトでは、私たちは結果を失敗とみなします
outcomes.append("失敗")
# エージェントが穴にはまったりゴールに到達するまでトレーニングを続ける
完了していない間:
# 現在の状態で最も高い値を持つアクションを選択します
np.max(qtable[state]) > 0の場合:
アクション = np.argmax(qtable[状態])
# 最適なアクションがない場合(ゼロのみ)、ランダムに選択します
それ以外:
アクション = environment.action_space.sample()
# このアクションを実装し、エージェントを目的の方向に移動させます
new_state、reward、done、info = environment.step(action)
# Q(s,a) を更新する
qtable[状態, アクション] = qtable[状態, アクション] + \
アルファ * (報酬 + ガンマ * np.max(qtable[新しい状態]) - qtable[状態、アクション])
# 現在の状態を更新する
状態 = 新しい状態
# 報酬が得られれば、それは結果が成功したことを意味します
報酬の場合:
結果[-1] = "成功"
印刷()
印刷('===============================================')
print('トレーニング後のQ表:')
print(qtable)
# プロットの結果
plt.figure(図サイズ=(12, 5))
plt.xlabel("実行番号")
plt.ylabel("結果")
斧 = plt.gca()
ax.set_facecolor('#efeeea')
plt.bar(range(len(outcomes)), 結果, color="#0A047A", width=1.0)
plt.show()トレーニング前のQ表: [[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]] =========================================== トレーニング後のQ表: [[0. 0. 0.59049 0. ] [0. 0. 0.6561 0.] [0. 0.729 0. 0. ] [0. 0. 0. 0. ] [0. 0.02050313 0. 0. ] [0. 0. 0. 0. ] [0. 0.81 0. 0. ] [0. 0. 0. 0. ] [0. 0. 0.17085938 0.] [0. 0. 0.49359375 0.] [0. 0.9 0. 0. ] [0. 0. 0. 0. ] [0. 0. 0. 0. ] [0. 0. 0. 0. ] [0. 0. 1. 0. ] [0. 0. 0. 0. ]] このエージェントのトレーニングが完了しました!グラフ上の青いバーはそれぞれ、エージェントがゲームで勝利した回数を表しています。つまり、トレーニング開始当初はエージェントが目標ブロックに到達するのに苦労していましたが、目標ブロックに複数回連続して到達した後は、安定してゲームに勝利できるようになったことがわかります。🥳 トレーニング済みのQテーブルも非常に役立ちます。これらの値は、エージェントが目標ブロックに到達するために学習した最適なアクションシーケンスを表しています。 エージェントのパフォーマンスを評価するために、100ラウンドのゲームプレイを完了させます。これでエージェントのトレーニングは完了したとみなせるので、Qテーブルを更新する必要はなくなりました。❄️Frozen Lakeゲーム環境におけるエージェントのパフォーマンスを理解するために、ターゲットブロックへの到達率(成功率)を計算します。 エピソード = 100
nb_success = 0
# 評価
範囲(100)内の_の場合:
状態 = 環境.reset()
完了 = False
# エージェントが行き詰まるかゴールに到達するまでトレーニングを続ける
完了していない間:
# 現在の状態で最も高い値を持つアクションを選択します
np.max(qtable[state]) > 0の場合:
アクション = np.argmax(qtable[状態])
# 最適なアクションがない場合(ゼロのみ)、ランダムに選択します
それ以外:
アクション = environment.action_space.sample()
# このアクションを実装し、エージェントを目的の方向に移動させます
new_state、reward、done、info = environment.step(action)
# 現在の状態を更新する
状態 = 新しい状態
# 報酬をもらったら、ゲームを解いたということになります
nb_success += 報酬
# 成功率を確認してみましょう!
print (f"成功率 = {nb_success/episodes*100}%")成功率 = 100.0% このエージェントは訓練を受けているだけでなく、100%の成功率を誇ります。よくできました!凍った湖の滑り止めバージョンをクリアしました! 次のコードを実行すると、エージェントがマップ上を移動し、実行するアクションのシーケンスを出力して、それが最適なアクションのシーケンスであるかどうかを確認できます。 IPython.displayからclear_outputをインポートする
インポート時間
状態 = 環境.reset()
完了 = False
シーケンス = []
完了していない間:
# 現在の状態で最も高い値を持つアクションを選択します
np.max(qtable[state]) > 0の場合:
アクション = np.argmax(qtable[状態])
# 最適なアクションがない場合(ゼロのみ)、ランダムに選択します
それ以外:
アクション = environment.action_space.sample()
# シーケンスにアクションを追加する
シーケンス.append(アクション)
# このアクションを実装し、エージェントを目的の方向に移動させます
new_state、reward、done、info = environment.step(action)
# 現在の状態を更新する
状態 = 新しい状態
# レンダリングを更新する
clear_output(wait=True)
環境.レンダリング()
時間.睡眠(1)
print(f"シーケンス = {シーケンス}")(右) SFFF FHFH FFFH はーん🟥 シーケンス = [2, 2, 1, 1, 1, 2] エージェントは、[2, 2, 1, 1, 1, 2]、[1, 1, 2, 2, 1, 2] など、複数の正しいアクションシーケンスを学習できます。上記の出力からわかるように、エージェントはアクションシーケンス内で6つのアクションのみを学習しました。これは、記事の冒頭で計算した最短のアクションシーケンスの長さです。つまり、エージェントは❄️Frozen Lakeゲームを最適な方法で解くことを学習したことを意味します。出力 [2, 2, 1, 1, 1, 2] に対応するアクションシーケンスは、右 → 右 → 下 → 下 → 下 → 右であり、これは記事の冒頭で予測したシーケンスとまったく同じです。📣 04 📐 ε-貪欲アルゴリズム上記の方法では、エージェントは常に最も高い値を持つ行動を選択します。したがって、状態と行動のペアが非ゼロの値を持つようになると、エージェントは常にその行動を選択します。他の行動は選択されず、その値も更新されません。…しかし、もしある行動がエージェントが通常選択する行動よりも優れている場合はどうなるでしょうか?エージェントが定期的に新しい方法を試し、改善があるかどうかを確認するように促すべきではないでしょうか? つまり、エージェントが次のいずれかの操作を実行できるようにします。
これら2つの操作のトレードオフは非常に重要です。エージェントが最初の操作のみに集中すると、新しい解を探索できず、したがってそれ以上学習できません。一方、エージェントがランダムな行動しか取れない場合、Qテーブルを利用しないため、トレーニングは無意味になります。したがって、このパラメータは時間の経過とともに変更する必要があります。トレーニング開始時には、ゲーム環境を可能な限り探索します。しかし、エージェントがあらゆる状態と行動のペアを学習するにつれて、探索は次第に面倒になります。このパラメータは、エージェントの行動選択におけるランダム性の度合いを表します。 この手法はしばしばε-greedyアルゴリズムと呼ばれます。εはパラメータです。これは、行動方針を決定するのに役立つ妥協点を見つけるための、シンプルでありながら非常に効果的な手法です。エージェントが次のマスに移動するための行動を選択する必要がある場合、ランダムな行動を選択する確率はε、最も高い価値を持つ行動を選択する確率は1-εです。各ゲーム終了時にεの値を固定値(線形減衰)または現在のεの値に基づいて減少させる方法(指数減衰)を選択できます。 まず、線形減衰方式を選択します。その前に、任意のパラメータで曲線がどのように見えるかを確認しましょう。ε = 1から始めて、完全な探索モードに入り、ゲームラウンドごとに値を0.001ずつ減らしていきます。 ソリューションをより明確に理解できたので、それを実装し、エージェントの動作がどのように変化するかを観察し始めることができます。 qtable = np.zeros((environment.observation_space.n, environment.action_space.n))
# ハイパーパラメータ
エピソード = 1000 # エピソードの総数
アルファ = 0.5 # 学習率
ガンマ = 0.9 # 割引係数
イプシロン = 1.0 # アクション選択におけるランダム性の量
epsilon_decay = 0.001 # 減少する固定量
# プロットする結果のリスト
結果 = []
print('トレーニング前のQテーブル:')
print(qtable)
# トレーニング
_ が範囲内(エピソード数)の場合:
状態 = 環境.reset()
完了 = False
# デフォルトでは、私たちは結果を失敗とみなします
outcomes.append("失敗")
# エージェントが穴にはまったりゴールに到達するまでトレーニングを続ける
完了していない間:
#0から1の間の乱数を生成する
rnd = np.random.random()
# 乱数 < イプシロンの場合、ランダムなアクションを実行する
rnd < イプシロンの場合:
アクション = environment.action_space.sample()
# それ以外の場合は、現在の状態で最も高い値を持つアクションを実行します
それ以外:
アクション = np.argmax(qtable[状態])
# このアクションを実装し、エージェントを目的の方向に移動させます
new_state、reward、done、info = environment.step(action)
# Q(s,a) を更新する
qtable[状態, アクション] = qtable[状態, アクション] + \
アルファ * (報酬 + ガンマ * np.max(qtable[新しい状態]) - qtable[状態、アクション])
# 現在の状態を更新する
状態 = 新しい状態
# 報酬が得られれば、それは結果が成功したことを意味します
報酬の場合:
結果[-1] = "成功"
# イプシロンを更新
イプシロン = 最大(イプシロン - イプシロン減衰, 0)
印刷()
印刷('===============================================')
print('トレーニング後のQ表:')
print(qtable)
# プロットの結果
plt.figure(図サイズ=(12, 5))
plt.xlabel("実行番号")
plt.ylabel("結果")
斧 = plt.gca()
ax.set_facecolor('#efeeea')
plt.bar(range(len(outcomes)), 結果, color="#0A047A", width=1.0)
plt.show()トレーニング前のQ表: [[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]] =========================================== トレーニング後のQ表: [[0.531441 0.59049 0.59049 0.531441 ] [0.531441 0. 0.6561 0.56396466] [0.58333574 0.729 0.56935151 0.65055117] [0.65308668 0. 0.33420534 0.25491326] [0.59049 0.6561 0. 0.531441] [0. 0. 0. 0. ] [0. 0.81 0. 0.65519631] [0. 0. 0. 0. ] [0.6561 0. 0.729 0.59049] [0.6561 0.81 0.81 0.] [0.72899868 0.9 0. 0.72711067] [0. 0. 0. 0. ] [0. 0. 0. 0. ] [0. 0.81 0.9 0.729] [0.81 0.9 1. 0.81] [0. 0. 0. 0. ]] エージェントがゲームにコンスタントに勝つためには、より多くの時間をトレーニングに費やす必要があります。さらに、Qテーブルには以前よりも大幅に多くの非ゼロ値があり、エージェントが目標ブロックに到達するための複数のアクションシーケンスを学習したことを示しています。これは当然のことです。新しいエージェントは、単に非ゼロ値に対応するアクションを使用するのではなく、新しい状態とアクションのペアを探索する必要があるからです。 前のエージェントと同様に、ターゲットブロックに無事に到達できるかどうか確認してみましょう。評価モードでは、エージェントはすでに学習済みなので、探索モードに入る必要はありません。 エピソード = 100
nb_success = 0
# 評価
範囲(100)内の_の場合:
状態 = 環境.reset()
完了 = False
# エージェントが行き詰まるかゴールに到達するまでトレーニングを続ける
完了していない間:
# 現在の状態で最も高い値を持つアクションを選択します
アクション = np.argmax(qtable[状態])
# このアクションを実装し、エージェントを目的の方向に移動させます
new_state、reward、done、info = environment.step(action)
# 現在の状態を更新する
状態 = 新しい状態
# 報酬をもらったら、ゲームを解いたということになります
nb_success += 報酬
# 成功率を確認してみましょう!
print (f"成功率 = {nb_success/episodes*100}%")成功率 = 100.0% わあ、また100%の成功率です!しかもモデルのパフォーマンスは低下していません。😌 この手法のメリットはこの例ではすぐには分かりにくいかもしれませんが、モデルはもはや静的ではなく、より柔軟になっています。以前の手法のように1つのパスだけを学習するのではなく、SブロックからGブロックまでの複数のパス(アクションシーケンス)を学習します。エージェントの探索を増やすとパフォーマンスが低下する可能性がありますが、新しい環境に適応できるエージェントをトレーニングするには不可欠です。 05 ❄️ チャレンジ:フローズンレイクのウェットバージョンFrozen Lakeのゲーム環境問題全体を解決したわけではありません。滑りにくいゲーム環境のみでエージェントをトレーニングし、初期化時にパラメータを `is_slippery = False` に設定しました。滑りやすいゲーム環境では、エージェントが選択したアクションの成功率はわずか33%です。失敗した場合、エージェントは他の3つのアクションのいずれかをランダムに選択します。この特性によりトレーニングのランダム性が高まり、エージェントが問題を解決する方法を学習するのがより困難になります。では、この新しいゲーム環境で以前のコードがどのように動作するかを見てみましょう。 環境 = gym.make("FrozenLake-v1", is_slippery=True)
環境.リセット()
# Qテーブルを再初期化します
qtable = np.zeros((environment.observation_space.n, environment.action_space.n))
# ハイパーパラメータ
エピソード = 1000 # エピソードの総数
アルファ = 0.5 # 学習率
ガンマ = 0.9 # 割引係数
イプシロン = 1.0 # アクション選択におけるランダム性の量
epsilon_decay = 0.001 # 減少する固定量
# プロットする結果のリスト
結果 = []
print('トレーニング前のQテーブル:')
print(qtable)
# トレーニング
_ が範囲内(エピソード数)の場合:
状態 = 環境.reset()
完了 = False
# デフォルトでは、私たちは結果を失敗とみなします
outcomes.append("失敗")
# エージェントが穴にはまったりゴールに到達するまでトレーニングを続ける
完了していない間:
#0から1の間の乱数を生成する
rnd = np.random.random()
# 乱数 < イプシロンの場合、ランダムなアクションを実行する
rnd < イプシロンの場合:
アクション = environment.action_space.sample()
# それ以外の場合は、現在の状態で最も高い値を持つアクションを実行します
それ以外:
アクション = np.argmax(qtable[状態])
# このアクションを実装し、エージェントを目的の方向に移動させます
new_state、reward、done、info = environment.step(action)
# Q(s,a) を更新する
qtable[状態, アクション] = qtable[状態, アクション] + \
アルファ * (報酬 + ガンマ * np.max(qtable[新しい状態]) - qtable[状態、アクション])
# 現在の状態を更新する
状態 = 新しい状態
# 報酬が得られれば、それは結果が成功したことを意味します
報酬の場合:
結果[-1] = "成功"
# イプシロンを更新
イプシロン = 最大(イプシロン - イプシロン減衰, 0)
印刷()
print('===========================================')
print('Q-table after training:')
print(qtable)
# Plot outcomes
plt.figure(figsize=(12, 5))
plt.xlabel("Run number")
plt.ylabel("Outcome")
ax = plt.gca()
ax.set_facecolor('#efeeea')
plt.bar(range(len(outcomes)), outcomes, color="#0A047A", width=1.0)
plt.show()
episodes = 100
nb_success = 0
# 評価
for _ in range(100):
state = environment.reset()
完了 = False
# Until the agent gets stuck or reaches the goal, keep training it
完了していない間:
# Choose the action with the highest value in the current state
action = np.argmax(qtable[state])
# Implement this action and move the agent in the desired direction
new_state, reward, done, info = environment.step(action)
# Update our current state
state = new_state
# When we get a reward, it means we solved the game
nb_success += reward
# Let's check our success rate!
print (f"Success rate = {nb_success/episodes*100}%")Q-table before training: [[0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.] [0. 0. 0. 0.]] =========================================== Q-table after training: [[0.06208723 0.02559574 0.02022059 0.01985828] [0.01397208 0.01425862 0.01305446 0.03333396] [0.01318348 0.01294602 0.01356014 0.01461235] [0.01117016 0.00752795 0.00870601 0.01278227] [0.08696239 0.01894036 0.01542694 0.02307306] [0. 0. 0. 0. ] [0.09027682 0.00490451 0.00793372 0.00448314] [0. 0. 0. 0. ] [0.03488138 0.03987256 0.05172554 0.10780482] [0.12444437 0.12321815 0.06462294 0.07084008] [0.13216145 0.09460133 0.09949734 0.08022573] [0. 0. 0. 0. ] [0. 0. 0. 0. ] [0.1606242 0.18174032 0.16636549 0.11444442] [0.4216631 0.42345944 0.40825367 0.74082329] [0. 0. 0. 0. ]] Success rate = 17.0% 呀!这种方法不太好。但是,你能通过调整前文讨论过的不同参数来改善模型性能吗?我鼓励你接受这个小挑战,自己尝试亲自实践强化学习,验证一下自己对本文的理解。为什么不对ε-贪婪算法也进行指数衰减呢?在本文介绍这个小案例的过程中,你可能会意识到稍微修改超参数就会完全改变运行结果。这是强化学习的另一个“怪处”:它的超参数非常敏感,如果你想调整它们,理解它们的含义是很重要的。尝试、测试新的技术组合总是有好处的,可以帮助我们建立算法直觉,提高效率。祝你好运,玩得愉快! 06 結論Q-learning 是一种简单而强大的算法,是强化学习的核心。在本文中:
Frozen Lake 是一个非常简单的游戏环境,但其他环境的状态和行为可能非常多,以至于无法在内存中存下Q-table。尤其是在事件不是离散而是连续的环境中(如《超级马里奥兄弟》或Minecraft) ,情况更加复杂。当面临这些挑战时,常用的解决方案是训练深度神经网络来模拟Q-table。这种方法会增加一些复杂度,因为神经网络不是太稳定。 🚢🚢🚢欢迎小伙伴们加入AI技术软件及技术交流群,追踪前沿热点,共探技术难题~ 終わり |