TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

PokéLLMonの論文を読む

LLMを使用してポケモンバトルをプレイするPokéLLMon論文を読んだ際のメモ。

概要

  • LLMを使用して、ポケモンバトルをプレイするエージェントを作成する。
  • 現在の状態をテキストとして与え、行動を生成する
  • 以前のターンの結果をテキストとして、コンテキストに与える「インコンテキスト強化学習
  • 外部知識として、技や能力の効果、タイプ相性をコンテキストに与える
  • パニック スイッチング(強い相手のときに交代を繰り返す)を防ぐため、プロンプトエンジニアリング手法のSelf-Consistency(SC)を使用

行っていないこと

  • 探索は行わない
  • モデルのパラメータの学習は行わない

アルゴリズムの概要は、図4が分かりやすい。

結果

  • オンライン対戦(Pokemon showdown)において、ラダー戦で勝率49%、招待戦で勝率56%を達成
  • 平均的な人間レベル

課題

  • 長期計画に弱い
  • 短期的な利益を達成できる行動を取る傾向がある
  • 人間プレイヤーの消耗戦略に対して脆弱

プロンプト

論文では詳細なプロンプトについて記載がなかったので、GitHubのソースを実際に動かしてプロンプトを確認してみた。
環境構築については、後で記載する。

環境変数「OPENAI_LOG=debug」を設定することで、実際にOpenAI APIにPOSTされたJSONデータを確認した。
後で気づいたが、battle_log/pokellmon_vs_bot/output.jsonlにも出力される。

1ターン目のプロンプト

{
"messages": [
{
"role": "system",
"content": "You are a pokemon battler that targets to win the pokemon battle. You can choose to take a move or switch in another pokemon. Here are some battle tips: Use status-boosting moves like swordsdance, calmmind, dragondance, nastyplot strategically. The boosting will be reset when pokemon switch out. Set traps like stickyweb, spikes, toxicspikes, stealthrock strategically. When face to a opponent is boosting or has already boosted its attack/special attack/speed, knock it out as soon as possible, even sacrificing your pokemon. if choose to switch, you forfeit to take a move this turn and the opposing pokemon will definitely move first. Therefore, you should pay attention to speed, type-resistance and defense of your switch-in pokemon to bear the damage from the opposing pokemon. And If the switch-in pokemon has a slower speed then the opposing pokemon, the opposing pokemon will move twice continuously."
},
{
"role": "user",
"content": "Historical turns:\nBattle start: You sent out Qwilfish. Opponent sent out Tsareena. Qwilfish\'s ability: Intimidate. It decreased opposing Tsareena\'s atk 1 level.\nTurn 1: Current battle state:\nOpponent has 6 pokemons left.\nOpposing pokemon:tsareena,Type:Grass,HP:100%,Is dynamax:False,Attack:168(-1 stage boosted),Defense:213,Special attack:132,Special defense:213,Speed:169,Ability:Queenly Majesty(The Pokemon\'s majesty pressures opponents and makes them unable to use priority moves against the Pokemon or its allies.) Ice, Bug-type attack is super-effective (2x damage) to tsareena. Water, Ground-type attack is ineffective (0.5x damage) to tsareena.\ntsareena\'s all the possible attacks:[powerwhip,grass,Power:120],[rapidspin,normal,Power:50],[knockoff,dark,Power:65],[uturn,bug,Power:70],[highjumpkick,fighting,Power:130],[tripleaxel,ice,Power:20],\nYour current pokemon:qwilfish,Type:Water and Poison,HP:100%,Attack:210,Defense:193,Special attack:142,Special defense:142,Speed:193(faster than tsareena).Ability:Intimidate(When the Pokemon enters a battle, it intimidates opposing Pokemon and makes them cower, lowering their Attack stats.),Item:Focus Sash(An item to be held by a Pokemon. If it has full HP, the holder will endure one potential KO attack, leaving 1 HP.) Ice, Fighting, Bug-type attack is ineffective (0.5x damage) to qwilfish.\nYour qwilfish has 4 moves:\nMove:toxicspikes,Type:Poison,Status-move,Power:0,Acc:100%,Effect:Scatters poisoned spikes, poisoning opposing Pokemon that switch in.\nMove:spikes,Type:Ground,Status-move,Power:0,Acc:100%,Effect:Scatters Spikes, hurting opposing Pokemon that switch in.\nMove:taunt,Type:Dark,Status-move,Power:0,Acc:100%,Effect:For the next few turns, the target can only use damaging moves.\nMove:waterfall,Type:Water,Power:79,Acc:100%,Effect:Has a 20% chance to make the target flinch.(ineffective (0.5x damage) to tsareena)\nYou have 5 pokemons:\nPokemon:rhyperior,Type:Ground and Rock,HP:100%,Attack:273,Defense:257,Special attack:136,Special defense:136,Speed:111(slower than tsareena). Moves:[stoneedge,Rock,1x damage],[megahorn,Bug,2x damage],[earthquake,Ground,0.5x damage], Grass-type attack is extremely-effective (4x damage) to rhyperior. Ice, Fighting-type attack is super-effective (2x damage) to rhyperior. Normal-type attack is ineffective (0.5x damage) to rhyperior.\nPokemon:greedent,Type:Normal,HP:100%,Attack:208,Defense:208,Special attack:141,Special defense:174,Speed:82(slower than tsareena). Moves:[bodyslam,Normal,1x damage],[payback,Dark,1x damage],[earthquake,Ground,0.5x damage], Fighting-type attack is super-effective (2x damage) to greedent.\nPokemon:heracross,Type:Bug and Fighting,HP:100%,Attack:246,Defense:166,Special attack:110,Special defense:198,Speed:182(faster than tsareena). Moves:[megahorn,Bug,2x damage],[knockoff,Dark,1x damage],[closecombat,Fighting,1x damage],[facade,Normal,1x damage], Fighting, Grass, Dark, Bug-type attack is ineffective (0.5x damage) to heracross.\nPokemon:inteleon,Type:Water,HP:100%,Attack:182,Defense:150,Special attack:246,Special defense:150,Speed:238(faster than tsareena). Moves:[darkpulse,Dark,1x damage],[uturn,Bug,2x damage],[icebeam,Ice,2x damage],[hydropump,Water,0.5x damage], Grass-type attack is super-effective (2x damage) to inteleon. Ice-type attack is ineffective (0.5x damage) to inteleon.\nPokemon:ninetalesalola,Type:Ice and Fairy,HP:100%,Attack:110,Defense:164,Special attack:174,Special defense:204,Speed:218(faster than tsareena). Moves:[moonblast,Fairy,1x damage],[freezedry,Ice,2x damage],[blizzard,Ice,2x damage], Ice, Dark, Bug-type attack is ineffective (0.5x damage) to ninetalesalola.\nChoose the best action and your output MUST be a JSON like: {\"move\":\"\"} or {\"switch\":\"\"}\n"
}
],
"model": "gpt-4-0125-preview",
"max_tokens": 100,
"response_format": {
"type": "json_object"
},
"stop": [],
"stream": False,
"temperature": 0.8
}

<日本語訳>
システムプロンプト】
あなたはポケモンバトルで勝利することを目指すポケモンバトラーです。 行動するか、別のポケモンに切り替えるかを選択できます。 戦闘のヒントは次のとおりです: ソードダンス、カームマインド、ドラゴンダンス、ナスティプロットなどのステータスを高める動きを戦略的に使用してください。 ブーストはポケモンが交代するとリセットされます。 スティッキーウェブ、スパイク、トキシックスパイク、ステルスロックなどのトラップを戦略的に設定します。 対戦相手がブースト中、またはすでに攻撃/特殊攻撃/スピードをブーストしている場合は、ポケモンを犠牲にしてでもできるだけ早く敵をノックアウトします。 切り替えることを選択した場合、このターンに移動することを放棄し、相手のポケモンが確実に最初に移動します。 したがって、相手のポケモンからのダメージに耐えるために、スイッチインポケモンの素早さ、タイプ耐性、防御力に注意を払う必要があります。 また、交代ポケモンの素早さが相手ポケモンより遅い場合、相手ポケモンは2回連続で動きます。

【ユーザプロンプト】
ターン履歴:
戦闘開始: クウィルフィッシュを送り出しました。 相手はツァリーナを送り出した。 クウィルフィッシュの能力: 威圧。 相手のツァリーナの攻撃力を1レベルダウンさせた。
ターン 1: 現在の戦闘状態:
相手の残りポケモンは6匹。
相手ポケモン:ツァリーナ、タイプ:草、HP:100%、ダイマックス:偽、攻撃:168(-1段階アップ)、防御:213、特攻:132、特防:213、すばやさ:169、特性:クイーンリー マジェスティ(ポケモンの威厳が相手にプレッシャーをかけ、ポケモンやその仲間に対して優先技を使えなくする。) こおり、むしタイプの攻撃はツァリーナに非常に効果的(ダメージ2倍)。 ツァリーナには水、じめんタイプの攻撃が効かない(ダメージ0.5倍)。
ツァリーナの可能なすべての攻撃:[パワーウィップ、グラス、パワー:120]、[ラピッドスピン、ノーマル、パワー:50]、[ノックオフ、ダーク、パワー:65]、[ターン、バグ、パワー:70]、[ ハイジャンプキック、格闘、パワー:130]、[トリプルアクセル、アイス、パワー:20]、
現在のポケモン:クウィルフィッシュ、タイプ:みずとどく、HP:100%、攻撃:210、防御:193、特攻:142、特防:142、すばやさ:193(ツァリーナより速い)、特性:威圧( ポケモンが戦闘に参加すると、相手のポケモンを威嚇してすくめさせ、攻撃ステータスを低下させます。 HPが1残る)クウィルフィッシュには氷・格闘・むし系の攻撃は無効(ダメージ0.5倍)。
クウィルフィッシュには 4 つの動きがあります。
技:毒スパイク、タイプ:どく、ステータスわざ、威力:0、命中率:100%、効果:毒スパイクをばらまき、入れ替わった相手ポケモンを毒状態にする。
技:トゲ、タイプ:じめん、ステータスわざ、威力:0、命中率:100%、効果:トゲをばらまいて、入れ替わった相手ポケモンにダメージを与える。
技:挑発、タイプ:闇、ステータス技、威力:0、命中率:100%、効果:数ターンの間、対象はダメージ技のみ使用可能。
技:滝、種族:水、威力:79、命中:100%、効果:20%の確率で対象をひるませる(ツァリーナには無効(ダメージ0.5倍))
あなたは5匹のポケモンを持っています:
ポケモン:ライペリア、タイプ:じめん、いわ、HP:100%、攻撃:273、防御:257、特攻:136、特防:136、すばやさ:111(ツァリーナより遅い)。 技:[ストーンエッジ、岩、ダメージ1倍]、[メガホーン、むし、ダメージ2倍]、[地震、地面、ダメージ0.5倍]、草タイプの攻撃がリペリアに非常に効果的(ダメージ4倍)。 氷、格闘タイプの攻撃がリスペリアに超有効(ダメージ2倍)。 ライペリアにはノーマルタイプの攻撃は無効(ダメージ0.5倍)。
ポケモン:グリーン、タイプ:ノーマル、HP:100%、攻撃:208、防御:208、特攻:141、特防:174、素早さ:82(ツァリーナより遅い)。 技:[ボディスラム、通常、ダメージ1倍]、[払い戻し、闇、ダメージ1倍]、[地震、地面、ダメージ0.5倍]、貪欲に格闘タイプの攻撃が超有効(ダメージ2倍)。
ポケモン:ヘラクロス、タイプ:むし・かくとう、HP:100%、攻撃:246、防御:166、特攻:110、特防:198、すばやさ:182(ツァリーナより速い)。 技:[メガホーン、むし、ダメージ2倍]、[なぎ倒し、あく、ダメージ1倍]、[接近戦、格闘、ダメージ1倍]、[正面、通常、ダメージ1倍]、格闘、草、闇、むし系の攻撃は無効 ヘラクロスに(ダメージ0.5倍)。
ポケモン:インテレオン、タイプ:みず、HP:100%、攻撃:182、防御:150、特攻:246、特防:150、すばやさ:238(ツァリーナより速い)。 技:[ダークパルス、闇、ダメージ1倍]、[ターン、バグ、ダメージ2倍]、[アイスビーム、氷、ダメージ2倍]、[ハイドロポンプ、水、ダメージ0.5倍]、くさタイプの攻撃が効果抜群(ダメージ2倍) )インテレオンに。 インテレオンにはこおりタイプの攻撃は無効(ダメージ0.5倍)。
ポケモン:ナインタレサローラ、タイプ:こおりとフェアリー、HP:100%、攻撃:110、防御:164、特攻:174、特防:204、素早さ:218(ツァリーナより速い)。 技:[ムーンブラスト、フェアリー、ダメージ1倍]、[フリーズドライ、氷、ダメージ2倍]、[ブリザード、氷、ダメージ2倍]、氷、あく、むし系の攻撃は効かない(ダメージ0.5倍)。
最適なアクションを選択し、出力は次のような JSON でなければなりません: {\"move\":\"\"} または {\"switch\":\"\"}

解説

システムプロンプトは、行動選択時と交代時でそれぞれ固定のプロントが使用される。

PokeLLMon/poke_env/player/gpt_player.py at bf3fa25c6c0e40715a87d56483f323056905b3e7 · git-disl/PokeLLMon · GitHub

ユーザプロンプトは、以前のターンの履歴情報、現在の状態、出力の指示文で構成されている。

ポケモンには、タイプ、HPなどのパラメータ、ワザ、特性の説明、どのタイプの攻撃が効くかが「くさタイプの攻撃が効果抜群(ダメージ2倍)」というように記述される。
早さについては、「ツァリーナより速い」といった情報を与えている。

指示文には、技名か、交代するポケモン名を出力するように指示している。

感想

外部情報として、どの技が効くかや、素早さだけでなくどちらが先に行動できるか情報を与えているため、LLMはそれらのテキストを解釈して、最適な行動を出力していると思われる。
答えをルールベースで教えているのに近いという印象を受けた。
同等以上の行動をとれるルールベースのエージェントを記述するのはおそらく可能である。

高度な状況判断は行っていないため、平均的な人間レベルといっても、上級者にはまったくかなわないと思われる。
平均的なプレイヤーもその状況で効果的な技を選んでいるだけだと思うので、正確なデータベースを参照して効果的な技の答えを与えている分、平均くらいのプレイができても驚きはない。

情報をテキストのみで与えて行動を選択できているという自然言語の解釈能力にしては、LLMの性能が発揮されている。
ただし、答えに近い情報を計算した上で渡しているため、わざわざテキストで与える意味は薄いと思った。

まとめ

LLMでポケモンバトルをプレイするエージェントに関する論文を読んだ。
将棋AIで、AIがなぜその手を指したのかはAIが教えてくれないという課題があり、LLMによって解決できないかと考えていて、そのヒントにならないかと思って読んでみた。
しかし、使われている手法は、データベースを使ってあらかじめ効果的な技を計算してテキストとして与えているため、将棋AIへの応用は難しいと思った。
将棋AIの場合、現在の状況を言語化することや、効果的な指し手を言語化して与えるのがそもそも難しい。

LLMを使用したより洗練されたアプローチや新しいアイディアが生まれることを期待したい。

参考情報

環境構築

ubuntu:22.04のDockerイメージを使用して構築した。

nodejsインストール
apt update &&
apt install nodejs npm curl -y &&
npm install -g n &&
n stable &&
apt purge nodejs npm -y
ソースclone
git clone https://github.com/git-disl/PokeLLMon.git
cd PokeLLMon
Pokemon Showdownインストールと起動

git clone https://github.com/smogon/pokemon-showdown.git
cd pokemon-showdown
npm install
cp config/config-example.js config/config.js
node pokemon-showdown start --no-security &
cd ..
|

Pythonインストール
apt install python3-pip python-is-python3 -y
ライブラリインストール
pip install openai numpy orjson requests websockets gymnasium
ソース修正

Llma2のエージェントは、peftのインストールが必要で関連ライブラリとしてPyTorchがインストールされるため、インストールを省くため、poke_env/player/__init__.pyの

from poke_env.player.llama_player import LLAMAPlayer

の行をコメントアウトした。

vs_bot.pyは、Pokemon Showdownのアカウントと、OpenAIのAPIキーをハードコードするようになっているので、環境変数から取得するように書き換えた。

                           api_key=os.getenv("OPENAI_API_KEY"),
                           ...
                           account_configuration=AccountConfiguration(os.getenv("USERNAME"), os.getenv("PASSWORD")),
実行
python vs_bot.py

ブラウザで、http://localhost:8000/にアクセスすると観戦できる。