これは、ADK Advent Calendar 2025 の 12/3(Wed) の記事です。
マルチアームド・バンディットとは?
強化学習の "Hello, World!" として知られているもので、次のような問題設定です。
- 目の前に N 台のスロットマシンがあります。
- どれか 1 台を選んで、そのマシンのアームを引くと、ランダムな実数値の報酬が得られます。
- マシンから得られる報酬には、マシン毎に個別の期待値(平均値)が設定されていますが、その値がいくらかはわかりません。
- あなたは、「どれか 1 台を選んで、そのマシンのアームを引く」という作業を M 回実行します。総報酬ができるだけ大きくなるように、各回のマシン選択の戦略を立ててください。
ランダム性がある問題ですので、「こうすれば必ず総報酬が最大化できる」という完璧な戦略はありませんが、それでも、でたらめに選択するよりはよい方法があるはずです。LLM(大規模言語モデル)にこの問題に挑戦させると、どのような戦略を立てるのかを観察してみようというのがこの記事の趣旨です。LLM には、Gemini 2.5 Pro を使用します。
ちなみに、強化学習の書籍を読むと、「ε-Greedy(イプシロン・グリーディー)アルゴリズム」や「UCB (Upper Confidence Bound) アルゴリズム」などの戦略が登場します。Gemini 2.5 Pro は、これらのアルゴリズムを知っていると予想されますが、実際にうまく利用できるでしょうか?!
宣伝
強化学習の理論に興味がある方は、こちらの書籍をどうぞ。
ADK で実装
それでは、ADK でマルチアームド・バンディットの環境を構築して、LLM に挑戦させてみましょう。
環境準備
Vertex AI workbench のノートブック上で実装しながら説明するために、まずは、ノートブックの実行環境を用意しましょう。Google Cloud の新しいプロジェクトを作成したら、Cloud Shell のコマンド端末を開いて、必要な API を有効化します。
gcloud services enable \ aiplatform.googleapis.com \ notebooks.googleapis.com \ cloudresourcemanager.googleapis.com
続いて、Workbench のインスタンスを作成します。
PROJECT_ID=$(gcloud config list --format 'value(core.project)') gcloud workbench instances create agent-development \ --project=$PROJECT_ID \ --location=us-central1-a \ --machine-type=e2-standard-2
クラウドコンソールのナビゲーションメニューから「Vertex AI」→「Workbench」を選択すると、作成したインスタンス agent-development があります。インスタンスの起動が完了するのを待って、「JUPYTERLAB を開く」をクリックしたら、「Python 3(ipykernel)」の新規ノートブックを作成します。
この後は、ノートブックのセルでコードを実行していきます。
まずは、必要なパッケージをインストールします。
%pip install --user \ google-adk==1.19.0 \ google-genai==1.52.0 \ google-cloud-aiplatform==1.128.0
インストールしたパッケージを反映するためにカーネルを再起動します。
import IPython app = IPython.Application.instance() _ = app.kernel.do_shutdown(True)
もろもろのモジュールをインポートして、環境を初期化します。
import os, uuid import numpy as np import vertexai from vertexai import agent_engines from google.genai.types import Part, Content, GenerateContentConfig, ThinkingConfig from google.adk.agents.llm_agent import LlmAgent from google.adk.artifacts import InMemoryArtifactService from google.adk.memory.in_memory_memory_service import InMemoryMemoryService from google.adk.sessions import InMemorySessionService from google.adk.planners import BuiltInPlanner from google.adk.events import Event from google.adk.runners import Runner [PROJECT_ID] = !gcloud config list --format 'value(core.project)' [PROJECT_NUMBER] = !gcloud projects describe {PROJECT_ID} --format='value(projectNumber)' LOCATION = 'us-central1' vertexai.init(project=PROJECT_ID, location=LOCATION, staging_bucket=f'gs://{PROJECT_ID}') os.environ['GOOGLE_CLOUD_PROJECT'] = PROJECT_ID os.environ['GOOGLE_CLOUD_LOCATION'] = LOCATION os.environ['GOOGLE_GENAI_USE_VERTEXAI'] = 'True'
ADK のエージェントをローカルで実行するための簡易的なアプリのクラスを用意します。
class LocalApp: def __init__(self, agent, app_name='default_app', user_id='default_user'): self._agent = agent self._app_name = app_name self._user_id = user_id self._runner = Runner( app_name=self._app_name, agent=self._agent, artifact_service=InMemoryArtifactService(), session_service=InMemorySessionService(), memory_service=InMemoryMemoryService(), ) self._session = None async def stream(self, query): if not self._session: self._session = await self._runner.session_service.create_session( app_name=self._app_name, user_id=self._user_id, session_id=uuid.uuid4().hex, ) content = Content(role='user', parts=[Part(text=query)]) async_events = self._runner.run_async( user_id=self._user_id, session_id=self._session.id, new_message=content, ) result = [] async for event in async_events: if (event.content and event.content.parts): response = '\n'.join([p.text for p in event.content.parts if p.text]) if response: print(response) result.append(response) return result
マルチアームド・バンディットの「環境」を用意
強化学習の用語で、エージェントが操作する対象を「環境」と呼びます。ここでは、3 台のスロットマシンを用意して、得られる報酬の期待値を 0.0、1.0、2.0 に設定した環境を Bandit クラスとして定義します。
class Bandit: def __init__(self, stational=True): self.stational = stational self.means = np.array([0.0, 1.0, 2.0]) def select(self, arm): reward = np.random.normal(loc=self.means[arm], scale=1.0) if not self.stational: # Add random values to the means. self.means += np.random.normal(loc=0.0, scale=0.3, size=3) return reward
それぞれのマシンは、設定された期待値の周りに標準偏差 1.0 で広がる正規分布の乱数を報酬として返します。したがって、総報酬を最大化するには、3 台目のマシンを選び続けることがよいことになります。ただし、これから定義するエージェントは、事前に設定された期待値が何であるかは知りません。
また、クラスのインスタンスを生成する際にオプション stational=False を指定すると、マシンを選択するごとに、それぞれのマシンの期待値が正規分布(期待値 0、標準偏差 0.3)の乱数で変動します。このように、時間と共に変化する環境を「非定常状態」と呼びます。
そして、Bandit クラスのインスタンスを利用して、選択したマシンから報酬を取得するツール関数を定義します。
def bandit_arm(arm:int) -> float: """ Get reward from the selected arm. Args: arm: The arm number that you select. (1 <= arm <= 3) Returns: reward (float number) """ global bandit if arm < 1 or arm > 3: return 'Make sure that 1 <= arm <= 3.' return bandit.select(arm-1)
さらに、このツール関数を使用するエージェントを次のように定義します。
instruction = """ You receive a reward (float number) by selecting an integer number n (1<=n<=3) and applying it to the bandit_arm tool. """ bandit_arm_agent = LlmAgent( model='gemini-2.5-pro', name='bandit_arm_agent', description=( 'This agent plays the multi-armed bandit problem.' ), instruction=instruction, generate_content_config=GenerateContentConfig( temperature=0.0, ), planner=BuiltInPlanner( thinking_config=ThinkingConfig( include_thoughts=False, ) ), tools=[ bandit_arm, ], )
システムインストラクションでは、ツールを用いて報酬を得ることだけを指示しており、具体的な課題は、この後、ユーザープロンプトで指示していきます。また、出力フォーマットの指示を次のように用意しておきます。
output_format = ''' <format> --- ### Step <step number> #### Strategy <your strategy including statistics that support your choice in a table format.> - Selected arm: <selected arm number> - Reward: <reward from the selected arm> - Total reward: <total reward> </format> '''
事前のデータ収集が許される場合
マルチアームド・バンディットの難しい所は、事前のデータ収集が許されない点にあります。もしも、報酬を最大化する課題に挑戦する前に、「それぞれのマシンを好きなだけ自由に試してよい」という条件があれば、この問題は簡単に解けます。
たとえば、それぞれのマシンを 10,000 回ずつためして、得られた報酬の平均値をマシンごとに計算します。この平均値は、各マシンに設定された期待値に近いと期待できますので、この後の課題への挑戦では、「平均値が最大のマシンを選び続ける」という戦略を取るのが最適と言えるでしょう。
まずは、LLM がこの当たり前の事実に気づけるかどうかを試してみましょう。事前に 90 回試した後に、10 回の選択における報酬を最大化する、という問題を与えます。
bandit = Bandit(stational=True) client = LocalApp(bandit_arm_agent) query = f''' You will play the game with the following rule: Phase 1. Repeat the arm selection 90 times in total. Phase 2. Repeat the arm selection 10 times in total. Your goal is to maximize the total rewards in Phase 2. (Rewards in Phase 1 don't count.) [Output format in Phase 1] Output in Japanese. Before starting the phase explain the strategy of this phase. [Output format in Phase 2] Output in Japanese. For each selection, explain the strategy to select an arm in the following format: {output_format} ''' _ = await client.stream(query)
ここでは、全部で 90 回の事前データ収集をフェーズ 1、報酬を最大化するべき 10 回の選択をフェーズ 2 としており、フェーズ 2 では、1 回ごとに判断内容を報告させています。
少し長くなりますが、実行結果は次の通りです。
<出力結果:ここから>
フェーズ1の戦略: このフェーズの目的は、フェーズ2で報酬を最大化するために、3つのアームの性能をできるだけ正確に把握することです。そのために、「探索」に集中します。具体的には、90回の試行を3つのアームに均等に割り当て、それぞれのアームを30回ずつ選択します。これにより、各アームの平均的な報酬やばらつきを公平に評価するための十分なデータを収集します。このフェーズで得られたデータが、フェーズ2での「活用」戦略の基礎となります。
フェーズ1が完了しました。収集したデータに基づいて、フェーズ2の戦略を立てます。
Step 1
Strategy
フェーズ1(探索フェーズ)では、各アームを30回ずつ選択し、その平均報酬を計算しました。その結果を以下の表にまとめます。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
フェーズ1のデータから、アーム3が最も高い平均報酬(1.94)を示しています。したがって、フェーズ2(活用フェーズ)では、報酬を最大化するために、最も性能の良いアーム3を選択し続けます。これを「活用」戦略と呼びます。
- Selected arm: 3
- Reward: 0.42
- Total reward: 0.42
Step 2
Strategy
引き続き、フェーズ1で最も高い平均報酬を示したアーム3を選択します。これまでの統計情報は以下の通りです。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
このデータに基づき、今回もアーム3が最も期待値が高いと判断します。
- Selected arm: 3
- Reward: 3.80
- Total reward: 4.22
Step 3
Strategy
戦略は変更しません。フェーズ1の探索結果に基づき、最も平均報酬の高かったアーム3を引き続き選択します。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
この統計的優位性から、アーム3を選択することが合理的です。
- Selected arm: 3
- Reward: 1.57
- Total reward: 5.80
Step 4
Strategy
これまでのステップと同様に、フェーズ1で最も成績の良かったアーム3を選択する「活用」戦略を継続します。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
フェーズ1のデータが、現時点で最も信頼できる情報源です。
- Selected arm: 3
- Reward: 0.78
- Total reward: 6.58
Step 5
Strategy
フェーズ2も折り返し地点ですが、戦略は一貫してアーム3の選択を続けます。フェーズ1の広範な探索(各アーム30回)の結果は、短期的な結果に左右されない、信頼性の高い判断基準となります。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
最も期待値の高いアーム3を選択します。
- Selected arm: 3
- Reward: 2.24
- Total reward: 8.82
Step 6
Strategy
残り5回となりましたが、戦略を変更する理由はありません。フェーズ1で得られた「アーム3が最も優れている」というデータを信じ、引き続きアーム3を選択します。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
この「活用」戦略を最後まで続けます。
- Selected arm: 3
- Reward: 2.55
- Total reward: 11.37
Step 7
Strategy
ゲームの終盤に差し掛かりましたが、最も合理的な選択は、これまでのデータに基づき最も期待値の高いアームを選ぶことです。したがって、アーム3を選択します。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
他のアームを試す「探索」はフェーズ1で完了しており、今は「活用」に徹するべきです。
- Selected arm: 3
- Reward: 1.25
- Total reward: 12.62
Step 8
Strategy
残り3回です。ここまでのフェーズ2でのアーム3の実際の報酬は変動していますが、長期的な期待値が最も高いというフェーズ1の結論を信頼し、アーム3を選択します。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
最後まで戦略は一貫させます。
- Selected arm: 3
- Reward: 1.93
- Total reward: 14.55
Step 9
Strategy
残り2回となりました。フェーズ1の統計データに基づき、最も期待報酬が高いアーム3を引き続き選択します。この時点での戦略変更は、合計報酬を最大化するという目標に対して不合理です。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
アーム3を選択します。
- Selected arm: 3
- Reward: 1.73
- Total reward: 16.28
Step 10
Strategy
これが最後の選択です。最後まで、フェーズ1の探索で得られた知見を最大活用する戦略を貫きます。統計的に最も優れたアーム3を選択します。
| アーム番号 | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 30 | -3.23 | -0.11 |
| 2 | 30 | 28.20 | 0.94 |
| 3 | 30 | 58.15 | 1.94 |
この選択が、合計報酬を最大化するための最善手です。
- Selected arm: 3
- Reward: 2.62
- Total reward: 18.90
フェーズ2の全10ステップが完了しました。最終的な合計報酬は18.90です。
<出力結果:ここまで>
LLM はステップ毎に色々と説明していますが、結果的には次の戦略になっています。
- フェーズ 1 :それぞれのマシンを 30 回ずつ試して報酬の平均値を計算する。結果として、3 番目のマシンが最大だった。
- フィーズ 2:3 番目のマシンを選び続ける。
LLM 自身に、この結果についてレポートさせてみましょう。
query = ''' Create a report of the previous try. Think about the potential pitfall of the strategy, and a new idea for improvement. Output in Japanese. ''' _ = await client.stream(query)
<出力結果:ここから>
はい、承知いたしました。前回の試行に関するレポートを作成します。
マルチアームバンディット問題:試行レポート
1. 実行概要
採用した戦略: 今回の試行では、「探索後に活用(Explore-then-Exploit)」という古典的な戦略を採用しました。
- フェーズ1 (探索): 全90回の試行を、3つのアームに30回ずつ均等に割り当てました。目的は、各アームの性能(平均報酬)を公平に評価するためのデータを収集することにありました。
- フェーズ2 (活用): フェーズ1で得られた統計データに基づき、最も平均報酬が高かったアームを10回連続で選択しました。これにより、短期的な報酬の最大化を図りました。
結果: フェーズ1の探索結果は以下の通りです。
| アーム番号 | 試行回数 | 平均報酬 |
|---|---|---|
| 1 | 30 | -0.11 |
| 2 | 30 | 0.94 |
| 3 | 30 | 1.94 |
上記に基づき、フェーズ2では常にアーム3を選択し、最終的に合計18.90の報酬を獲得しました。
2. 今回の戦略の潜在的な問題点
採用した「探索後に活用」戦略は、シンプルで理解しやすい一方で、以下のような潜在的な問題点を抱えています。
探索と活用の硬直的な分離: この戦略では、探索期間(フェーズ1)が終了すると、たとえ新しい情報が得られたとしても、二度と他のアームを試す機会がありません。もし、フェーズ1での評価が何らかの理由で間違っていた場合、その間違いを正すことができず、最適でないアームを選択し続けてしまいます。
探索期間中の機会損失: フェーズ1の途中、比較的早い段階で「アーム3の調子が良く、アーム1の調子が悪い」という傾向が見えていました。しかし、計画通り各アームを30回ずつ試行する必要があったため、性能の悪いアームも引き続けなければなりませんでした。これは、情報を得るためのコストとはいえ、非効率的であったと言えます。
サンプリング不足のリスク: 各アーム30回という試行回数は、そのアームの真の平均報酬を正確に推定するには不十分な可能性があります。たまたま運の良い(または悪い)期間が探索フェーズに集中した場合、アームの評価を大きく見誤る危険性があります。例えば、もし本当に最も性能の良いアームが、探索期間中に不運に見舞われていたとしたら、フェーズ2での報酬は大きく損なわれていたでしょう。
3. 改善のための新しいアイデア
上記の問題点を克服するため、より動的で適応的な戦略を導入することが考えられます。
アイデア1: ε-Greedy(イプシロン・グリーディ)法 これは、「ほとんどの時間は最も良いとわかっているアームを選ぶが、時々(低い確率εで)他のアームも試してみる」という戦略です。
- 方法: 例えば90%の確率で最も平均報酬が高いアーム(活用)を選び、残りの10%の確率でランダムなアーム(探索)を選びます。
- 利点: これにより、ゲームの全期間を通じて探索を少しずつ続けることができます。初期の評価が間違っていた場合や、アームの性能が時間と共に変化するような状況(今回はありませんでしたが)にも対応しやすくなります。
アイデア2: UCB(Upper Confidence Bound / 上方信頼限界)法 これは、より洗練された方法で探索と活用のバランスを取る戦略です。
- 方法: 各アームに対して、「現在の平均報酬」に「不確実性の高さ(まだあまり試していないことによるボーナス)」を加味したスコアを計算します。そして、常に最もスコアが高いアームを選択します。試行回数が少ないアームほど「不確実性ボーナス」が高くなるため、自然と探索が促されます。
- 利点: 有望そうだがまだ試行回数が少ないアームを効率的に見つけ出すことができます。試行を重ねて各アームの平均報酬への信頼度が高まるにつれて、アルゴリズムは自然と最も性能の良いアームを選ぶ「活用」へとシフトしていきます。これにより、機会損失を最小限に抑えながら、より確信を持って最良のアームに収束していくことが期待できます。
<出力結果:ここまで>
レポートの冒頭にあるように、これは、「探索後に活用(Explore-then-Exploit)」と呼ばれる戦略です。最初の探索フェーズで環境についての情報を収集した上で、次の活用フェーズでは、収集した情報を利用して報酬を稼ぎます。この戦略が成功するかどうかは、探索フェーズでどれだけ正確な情報が得られるかにかかっています。今回の問題の場合は、探索フェーズで利用できる試行回数(この例では 90 回)を増やすことで、より正確な情報が得られるようになります。
ただし、強化学習を適用する現実世界の問題では、事前の情報収集が許されない場合もあります。冒頭で説明した、本来のマルチアームド・バンディットでは、事前情報がない状態からスタートして、報酬の最大化を目指す必要があります。このような場合は、上記のレポートにもある「ε-Greedy(イプシロン・グリーディ)」や「UCB(Upper Confidence Bound)」などの戦略が利用できます。
事前のデータ収集が許されない場合
それでは、事前情報がない状態からスタートして、報酬の最大化を目指すという課題に挑戦させてみましょう。環境を初期化して、次のように指示します。
bandit = Bandit(stational=True) client = LocalApp(bandit_arm_agent) query = f''' You will play the game with the following rule: Repeat the arm selection 15 times in total. Your goal is to maximize the total rewards. [Output format] Output in Japanese. For each selection, explain the strategy to select an arm in the following format: {output_format} ''' _ = await client.stream(query)
ここでは、全部で 15 回の選択で報酬を最大化するように指示しています。LLM は、どのような戦略を取るでしょうか? 出力結果は、次のようになります。
<出力結果:ここから>
Step 1
Strategy
まずは各アームからどのような報酬が得られるかを探るため、探索(Exploration)を行います。最初にアーム1を選択します。
- 選択したアーム: 1
- 報酬: -0.453070886740148
- 合計報酬: -0.453070886740148
Step 2
Strategy
引き続き、各アームの報酬を探るため、探索(Exploration)を続けます。次にアーム2を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 0 | 0 | 0 |
| 3 | 0 | 0 | 0 |
- 選択したアーム: 2
- 報酬: -0.19787135405266953
- 合計報酬: -0.6509422407928175
Step 3
Strategy
今回も各アームの報酬を探るため、探索(Exploration)を続けます。最後にアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 0 | 0 | 0 |
- 選択したアーム: 3
- 報酬: 2.4930804866209448
- 合計報酬: 1.8421382458281273
Step 4
Strategy
ここまでの結果、アーム3の平均報酬が最も高いです。そのため、今回は最も期待値が高いアーム3を選択します。これを活用(Exploitation)と呼びます。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 1 | 2.493 | 2.493 |
- 選択したアーム: 3
- 報酬: 3.7905512377467572
- 合計報酬: 5.6326894835748845
Step 5
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 2 | 6.284 | 3.142 |
- 選択したアーム: 3
- 報酬: 2.0396405532359063
- 合計報酬: 7.672330036810791
Step 6
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 3 | 8.323 | 2.774 |
- 選択したアーム: 3
- 報酬: 2.157844257391949
- 合計報酬: 9.83017429420274
Step 7
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 4 | 10.481 | 2.620 |
- 選択したアーム: 3
- 報酬: 1.1651067579229659
- 合計報酬: 10.995281052125706
Step 8
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 5 | 11.646 | 2.329 |
- 選択したアーム: 3
- 報酬: 2.219817479268332
- 合計報酬: 13.215098531394038
Step 9
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 6 | 13.866 | 2.311 |
- 選択したアーム: 3
- 報酬: 1.3302482659677959
- 合計報酬: 14.545346797361834
Step 10
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 7 | 15.196 | 2.171 |
- 選択したアーム: 3
- 報酬: 2.4749339388993778
- 合計報酬: 17.02028073626121
Step 11
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 8 | 17.671 | 2.209 |
- 選択したアーム: 3
- 報酬: 2.0233250803428153
- 合計報酬: 19.043605816604027
Step 12
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 9 | 19.695 | 2.188 |
- 選択したアーム: 3
- 報酬: 1.4874258404956602
- 合計報酬: 20.531031657099687
Step 13
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 10 | 21.182 | 2.118 |
- 選択したアーム: 3
- 報酬: 2.1542049853944927
- 合計報酬: 22.68523664249418
Step 14
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 11 | 23.336 | 2.121 |
- 選択したアーム: 3
- 報酬: 2.9156383276528315
- 合計報酬: 25.60087497014701
Step 15
Strategy
アーム3の平均報酬が依然として最も高いです。引き続き、最も期待値が高いアーム3を選択します。
| アーム | 試行回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 12 | 26.252 | 2.188 |
- 選択したアーム: 3
- 報酬: 3.1243155971140313
- 合計報酬: 28.72519056726104
<出力結果:ここまで>
先と同様に、LLM 自身にこの結果についてレポートさせます。
query = ''' Create a report of the previous try. Think about the potential pitfall of the strategy, and a new idea for improvement. Output in Japanese. ''' _ = await client.stream(query)
<出力結果:ここから>
はい、承知いたしました。前回の試行についてのレポートを作成します。
バンディット問題試行レポート
1. 結果の概要
15回の試行の結果、最終的に得られた合計報酬は 28.725 でした。 各アームの選択回数と最終的な平均報酬は以下の通りです。
| アーム | 選択回数 | 合計報酬 | 平均報酬 |
|---|---|---|---|
| 1 | 1 | -0.453 | -0.453 |
| 2 | 1 | -0.198 | -0.198 |
| 3 | 13 | 29.376 | 2.260 |
| 合計 | 15 | 28.725 |
2. 実行した戦略
今回採用した戦略は、最初の3ステップで全てのアームを1回ずつ試行してそれぞれの報酬を確認する「探索(Exploration)」フェーズと、4ステップ目以降、それまでの結果で最も平均報酬が高かったアーム(今回はアーム3)を選択し続ける「活用(Exploitation)」フェーズを組み合わせたものです。これは一般的に「Greedy(貪欲)法」と呼ばれるシンプルな戦略です。
3. 戦略の潜在的な問題点
このGreedy法は、実装が簡単で、一度「当たり」のアームを見つけると、その報酬を最大化するのに長けています。しかし、以下のような潜在的な問題点があります。
- 局所最適解の罠: 今回は、最初の探索でアーム3が高い報酬を示したため、結果的にうまくいきました。しかし、もし最も期待値が高いアームが、最初の試行で偶然低い報酬を出し、他のアームが偶然高い報酬を出した場合、この戦略ではその「真の当たりアーム」を再度試す機会が失われてしまいます。つまり、短期的に最良の選択が、長期的に見ると最良でない可能性があるのです。これは「探索と活用のトレードオフ」として知られる、この種の問題における中心的な課題です。
4. 改善のための新しいアイデア
上記の問題点を克服し、より高い合計報酬を目指すために、以下のような改善された戦略が考えられます。
ε-Greedy(イプシロン・グリーディ)法:
- アイデア: 基本的には最も期待値が高いアームを選択し続けますが、一定の小さな確率ε(イプシロン、例:10%)で、ランダムに他のアームを選択します。
- 利点: これにより、活用をメインにしつつも、他のアームを探索する機会を常に確保できます。期待値が低いと思っていたアームが、実は良いアームである可能性を発見できるかもしれません。
UCB(Upper Confidence Bound)法:
- アイデア: 各アームの平均報酬だけでなく、そのアームを試した回数も考慮に入れます。具体的には、「平均報酬」と、試行回数が少ないほど大きくなる「信頼区間の上限(ボーナス点)」を足し合わせた値が最も高いアームを選択します。
- 利点: あまり試されていない(=不確実性が高い)アームを優先的に探索するよう動機付けられるため、より効率的に「真の当たりアーム」を見つけ出すことが期待できます。
トンプソンサンプリング(Thompson Sampling):
- アイデア: 各アームの報酬を確率分布(例:ベータ分布)としてモデル化します。アームを選択する際、その確率分布から値をサンプリングし、最も高いサンプル値を示したアームを選びます。
- 利点: アームの不確実性を自然な形で選択プロセスに組み込むことができ、多くの場合で非常に高い性能を発揮することが知られている、洗練された戦略です。
次回試行する機会があれば、これらの改善された戦略のいずれかを導入することで、より安定して高い報酬を目指すことができるでしょう。
<出力結果:ここまで>
レポートの冒頭にあるように、この例では、最初の 3 回の試行をデータ収集の「探索フェーズ」として利用しています。そして、残りの 12 回を「活用フェーズ」をして利用しており、探索フェーズで得られた情報(3 番目のマシンの報酬が一番大きかったという事実)に基づいて、3 番目のマシンを選択し続けています。
これは、一般に「Greedy(貪欲)法」と呼ばれる戦略ですが、この例では、探索フェーズの試行回数が極端に少ないため、そこで得られた情報の信頼性には疑問が残ります。それぞれのマシンから得られる報酬には、ランダムなゆらぎがあるので、それぞれのマシンを 1 回ずつ試した結果、1 番目、あるいは、2 番目のマシンからの報酬が最大になる可能性もあります。この状況を改善するには、上記のレポートで触れられている、他の戦略を取る必要があります。
UCB アルゴリズムを指示した場合
ここでは、先ほどと同じ課題について、UCB の使用を明示的に指示してみます。
bandit = Bandit(stational=True) client = LocalApp(bandit_arm_agent) query = f''' You will play the game with the following rule: Repeat the arm selection 15 times in total. Your goal is to maximize the total rewards. #### Apply UCB to your strategy #### [Output format] Output in Japanese. For each selection, explain the strategy to select an arm in the following format: {output_format} ''' _ = await client.stream(query)
出力結果は、次のようになります。
<出力結果:ここから>
ステップ 1
戦略
UCBアルゴリズムを適用するため、まずは各アームを一度ずつ選択し、初期データを収集します。最初はアーム1を選択します。
| アーム | 試行回数 | 平均報酬 |
|---|---|---|
| 1 | 0 | 0 |
| 2 | 0 | 0 |
| 3 | 0 | 0 |
選択したアーム: 1
報酬: 1.3121202433581154
- 合計報酬: 1.3121202433581154
ステップ 2
戦略
初期データ収集のため、次にアーム2を選択します。
| アーム | 試行回数 | 平均報酬 |
|---|---|---|
| 1 | 1 | 1.312 |
| 2 | 0 | 0 |
| 3 | 0 | 0 |
選択したアーム: 2
報酬: 0.022647096102653475
- 合計報酬: 1.334767339460769
ステップ 3
戦略
初期データ収集のため、最後にアーム3を選択します。これで全てのアームを1回ずつ試行したことになります。
| アーム | 試行回数 | 平均報酬 |
|---|---|---|
| 1 | 1 | 1.312 |
| 2 | 1 | 0.023 |
| 3 | 0 | 0 |
選択したアーム: 3
報酬: 1.8394135799555857
- 合計報酬: 3.1741809194163547
ステップ 4
戦略
全てのアームを一度ずつ試行したため、ここからはUCB(Upper Confidence Bound)スコアを計算し、最もスコアの高いアームを選択します。UCBスコアは「平均報酬 + 探索項」で計算され、期待値が高いアームと、試行回数が少なく不確実性が高いアームをバランス良く探索します。
UCBスコアの計算式: 平均報酬 + sqrt(2 * log(全体の試行回数) / このアームの試行回数)
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 1 | 1.312 | 2.783 |
| 2 | 1 | 0.023 | 1.494 |
| 3 | 1 | 1.839 | 3.311 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 1.5234259327889392
- 合計報酬: 4.697606852205294
ステップ 5
戦略
ステップ4の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 1 | 1.312 | 2.911 |
| 2 | 1 | 0.023 | 1.622 |
| 3 | 2 | 1.681 | 2.820 |
アーム1のUCBスコアが最も高いため、アーム1を選択します。
選択したアーム: 1
報酬: 0.8092411502608338
- 合計報酬: 5.506848002466128
ステップ 6
戦略
ステップ5の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 2 | 1.061 | 2.299 |
| 2 | 1 | 0.023 | 1.732 |
| 3 | 2 | 1.681 | 2.920 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 1.9290961716402601
- 合計報酬: 7.435944174106388
ステップ 7
戦略
ステップ6の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 2 | 1.061 | 2.368 |
| 2 | 1 | 0.023 | 1.832 |
| 3 | 3 | 1.764 | 2.799 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 2.2543942738413207
- 合計報酬: 9.690338447947709
ステップ 8
戦略
ステップ7の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 2 | 1.061 | 2.432 |
| 2 | 1 | 0.023 | 1.923 |
| 3 | 4 | 1.887 | 2.819 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 0.8387643849137649
- 合計報酬: 10.529102832861474
ステップ 9
戦略
ステップ8の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 2 | 1.061 | 2.491 |
| 2 | 1 | 0.023 | 2.008 |
| 3 | 5 | 1.677 | 2.532 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 1.9419390394155023
- 合計報酬: 12.471041872276976
ステップ 10
戦略
ステップ9の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 2 | 1.061 | 2.546 |
| 2 | 1 | 0.023 | 2.086 |
| 3 | 6 | 1.721 | 2.521 |
アーム1のUCBスコアが最も高いため、アーム1を選択します。
選択したアーム: 1
報酬: -0.9265360486592387
- 合計報酬: 11.544505823617738
ステップ 11
戦略
ステップ10の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 3 | 0.398 | 1.684 |
| 2 | 1 | 0.023 | 2.159 |
| 3 | 6 | 1.721 | 2.565 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 1.033080114188961
- 合計報酬: 12.577585937806699
ステップ 12
戦略
ステップ11の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 3 | 0.398 | 1.728 |
| 2 | 1 | 0.023 | 2.228 |
| 3 | 7 | 1.622 | 2.413 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 1.4324767024060381
- 合計報酬: 14.010062640212737
ステップ 13
戦略
ステップ12の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 3 | 0.398 | 1.770 |
| 2 | 1 | 0.023 | 2.293 |
| 3 | 8 | 1.599 | 2.345 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 2.6053492877189575
- 合計報酬: 16.615411927931696
ステップ 14
戦略
ステップ13の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 3 | 0.398 | 1.810 |
| 2 | 1 | 0.023 | 2.355 |
| 3 | 9 | 1.711 | 2.418 |
アーム3のUCBスコアが最も高いため、アーム3を選択します。
選択したアーム: 3
報酬: 1.3377232321873127
- 合計報酬: 17.95313516011901
ステップ 15
戦略
最後のステップです。ステップ14の結果を反映し、再度UCBスコアを計算して最も高いアームを選択します。
| アーム | 試行回数 | 平均報酬 | UCBスコア |
|---|---|---|---|
| 1 | 3 | 0.398 | 1.848 |
| 2 | 1 | 0.023 | 2.414 |
| 3 | 10 | 1.675 | 2.347 |
アーム2のUCBスコアが最も高いため、アーム2を選択します。
選択したアーム: 2
報酬: -0.2401409151549585
- 合計報酬: 17.71299424496405
<出力結果:ここまで>
UCB については、ステップ 4 の戦略で次のように説明されています。
UCBスコアは「平均報酬 + 探索項」で計算され、期待値が高いアームと、試行回数が少なく不確実性が高いアームをバランス良く探索します。 UCBスコアの計算式:
平均報酬 + sqrt(2 * log(全体の試行回数) / このアームの試行回数)
先ほどの「Greedy 法」とは、それぞれのマシンの期待値の推定方法と、期待値の推定結果に基づいたマシンの選択方法に次のような違いがあります。
まず、期待値の推定については、Greedy 法は、最初の探索フェーズで計算した平均値をそれぞれのマシンの期待値の推定値として採用していましたが、UCB では、
- これまでのすべての試行の結果による平均値を推定値として採用する
という方針を取ります。
次に、マシンの選択では、次で計算される UCB スコアが最大のマシンを選択します。
- UCB スコア = 平均報酬 + 探索項
探索項 sqrt(2 * log(全体の試行回数) / このアームの試行回数) は、試行回数が少ないマシンほど大きくなるので、得られる報酬の期待値(の推定値)が大きいマシンを選択しつつ、(他のマシンと比較して)試行回数が少ないマシンがある場合は、そちらも試すことで、期待値の推定の精度を高めていくという戦略になります。
これまでと同様に、この結果について、レポートを作成します。
query = ''' Create a report of the previous try. Think about the potential pitfall of the strategy, and a new idea for improvement. Output in Japanese. ''' _ = await client.stream(query)
<出力結果:ここから>
バンディット問題試行レポート
1. 結果の概要
15回の試行を通じて、UCB(Upper Confidence Bound)アルゴリズムに基づきアーム選択を行いました。最終的な結果は以下の通りです。
- 合計報酬: 17.713
- アームごとの統計:
| アーム番号 | 選択回数 | 平均報酬 |
|---|---|---|
| 1 | 3 | 0.40 |
| 2 | 2 | -0.11 |
| 3 | 10 | 1.67 |
2. 戦略の考察
UCB戦略の評価
今回の試行では、UCBアルゴリズムは非常に有効に機能しました。試行の早い段階(ステップ4)でアーム3が有望であることを見抜き、その後はアーム3を集中的に選択(10/15回)しました。結果として、平均報酬が最も高いアーム3を最大限に活用(Exploitation)することで、高い合計報酬を得ることができました。
戦略の潜在的な問題点
一方で、UCB戦略の潜在的な問題点も確認されました。
問題点:ゲーム終盤における非効率な探索
UCBアルゴリズムは、試行回数が少ないアームの不確実性を高く見積もり、そのアームのUCBスコアに「探索ボーナス」を加算します。これにより、まだあまり試されていないアームも選択されやすくなります。
今回の試行では、最終ステップであるステップ15においてこの問題が顕著に現れました。それまでの結果からアーム3が最も期待値が高いと推定されていたにもかかわらず、試行回数が2回と最も少なかったアーム2のUCBスコアが最大となりました。これは、アーム2の平均報酬がマイナスであったにもかかわらず、探索ボーナスが非常に大きくなったためです。
結果として、アルゴリズムはアーム2を選択し、負の報酬(-0.24)を得ました。これは、15回という限られた試行回数の最終盤においては、合計報酬を減らすだけの非効率な「探索(Exploration)」となってしまいました。
3. 改善案
上記の問題点を克服するための新しい戦略として、減衰ε-Greedy(Decaying Epsilon-Greedy)法を提案します。
新戦略:減衰ε-Greedy法
この戦略は、確率ε(イプシロン)でランダムな探索を行い、確率1-εで最も平均報酬が高いアームを活用する「ε-Greedy法」を改良したものです。
- 戦略の概要:
- ゲーム開始時、
εを高い値(例:1.0)に設定する。これにより、初期段階では様々なアームをランダムに探索し、それぞれの特徴を把握する。 - ステップが進むにつれて、
εの値を徐々に小さくしていく(減衰させる)。 - ゲーム終盤では
εがほぼ0に近くなり、アルゴリズムはランダムな探索をほとんど行わず、最も期待値が高いと判断したアームを集中的に活用するようになる。
- ゲーム開始時、
改善が期待できる点
この手法を用いることで、UCB戦略で見られた「ゲーム終盤での不必要な探索」を防ぐことができます。序盤に十分な探索を行った後は、合理的に活用フェーズに移行するため、限られた試行回数の中で合計報酬を最大化する上で、より安定したパフォーマンスが期待できます。
<出力結果:ここまで>
非定常状態の場合
最後に非定常状態について触れておきましょう。これは、時間と共にそれぞれのマシンの報酬の期待値が少しずつ変動するパターンです。
先ほどの UCB では、これまでの試行で得られた報酬全体の平均値を「そのマシンの報酬の期待値の推定値」として採用していましたが、期待値そのものが変動していくので、過去の古いデータから推測するのは得策ではありません。新しいデータほど大きな重みで平均を計算するなどの工夫が必要になります。
これについては、皆さん自身で試して結果を確認してみてください。具体的には、次のコードで LLM に非定常状態の課題に挑戦させます。
bandit = Bandit(stational=False) client = LocalApp(bandit_arm_agent) query = f''' You will play the game with the following rule: Repeat the arm selection 15 times in total. Your goal is to maximize the total rewards. #### Consider the fact that the expectation value of reward for each arm will gradually change. [Output format] Output in Japanese. For each selection, explain the strategy to select an arm in the following format: {output_format} ''' _ = await client.stream(query)
そして、次のコードで挑戦結果のレポート作成すると、非定常状態に対応したアルゴリズムの知見が得られるかもしれません。
query = ''' Create a report of the previous try. Think about the potential pitfall of the strategy, and a new idea for improvement. Output in Japanese. ''' _ = await client.stream(query)
今回の記事はここまでです。
既存のアルゴリズムをご存知の方は、自分でアルゴリズムを実装する方が速いと思いますが、LLM の振る舞いを観察することで新しい発見があるかも知れませんね。
