具備手臂特徵的多臂老虎機教學課程

開始使用

在 TensorFlow.org 上檢視 在 Google Colab 中執行 在 GitHub 上檢視原始碼 下載筆記本

本教學課程逐步說明如何將 TF-Agents 程式庫用於情境式老虎機問題,其中動作 (手臂) 具有自己的特徵,例如以特徵 (類型、發行年份等) 表示的電影清單。

先決條件

假設讀者已大致熟悉 TF-Agents 的 Bandit 程式庫,尤其是已研讀過TF-Agents 中 Bandit 的教學課程,再閱讀本教學課程。

具備手臂特徵的多臂老虎機

在「經典」情境式多臂老虎機設定中,代理程式在每個時間步收到情境向量 (又稱觀察),且必須從一組有限的編號動作 (手臂) 中選擇,以最大化其累積獎勵。

現在考慮一種情境,其中代理程式向使用者推薦要觀看的下一部電影。每次必須做出決策時,代理程式都會收到關於使用者的情境資訊 (觀看記錄、類型偏好等),以及要從中選擇的電影清單。

我們可以嘗試透過將使用者資訊作為情境來制定此問題,而手臂會是 movie_1, movie_2, ..., movie_K,但此方法有多個缺點

  • 動作的數量必須是系統中的所有電影,而且新增電影很麻煩。
  • 代理程式必須為每部電影學習模型。
  • 未考量電影之間的相似性。

除了為電影編號之外,我們可以做一些更直覺的事情:我們可以使用一組特徵 (包括類型、長度、演員、評分、年份等) 來表示電影。此方法的優點有很多

  • 跨電影的類化。
  • 代理程式僅學習一個獎勵函數,該函數會使用使用者和電影特徵來建立獎勵模型。
  • 輕鬆從系統中移除或新增電影。

在此新設定中,動作的數量甚至不必在每個時間步都相同。

TF-Agents 中的 Per-Arm Bandits

TF-Agents Bandit 套件的開發目的,是讓人們也可以將其用於 per-arm 案例。有 per-arm 環境,而且大多數政策和代理程式也可以在 per-arm 模式下運作。

在我們深入研究編碼範例之前,我們需要必要的匯入。

安裝

pip install tf-agents
pip install tf-keras
import os
# Keep using keras-2 (tf-keras) rather than keras-3 (keras).
os.environ['TF_USE_LEGACY_KERAS'] = '1'

匯入

import functools
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

from tf_agents.bandits.agents import lin_ucb_agent
from tf_agents.bandits.environments import stationary_stochastic_per_arm_py_environment as p_a_env
from tf_agents.bandits.metrics import tf_metrics as tf_bandit_metrics
from tf_agents.drivers import dynamic_step_driver
from tf_agents.environments import tf_py_environment
from tf_agents.replay_buffers import tf_uniform_replay_buffer
from tf_agents.specs import tensor_spec
from tf_agents.trajectories import time_step as ts

nest = tf.nest

參數 - 隨意試驗

# The dimension of the global features.
GLOBAL_DIM = 40 
# The elements of the global feature will be integers in [-GLOBAL_BOUND, GLOBAL_BOUND).
GLOBAL_BOUND = 10 
# The dimension of the per-arm features.
PER_ARM_DIM = 50 
# The elements of the PER-ARM feature will be integers in [-PER_ARM_BOUND, PER_ARM_BOUND).
PER_ARM_BOUND = 6 
# The variance of the Gaussian distribution that generates the rewards.
VARIANCE = 100.0 
# The elements of the linear reward parameter will be integers in [-PARAM_BOUND, PARAM_BOUND).
PARAM_BOUND = 10 

NUM_ACTIONS = 70 
BATCH_SIZE = 20 

# Parameter for linear reward function acting on the
# concatenation of global and per-arm features.
reward_param = list(np.random.randint(
      -PARAM_BOUND, PARAM_BOUND, [GLOBAL_DIM + PER_ARM_DIM]))

簡單的 Per-Arm 環境

在另一個教學課程中說明的靜態隨機環境具有 per-arm 對應項。

若要初始化 per-arm 環境,必須定義產生下列項目的函數

  • 全域和 per-arm 特徵:這些函數沒有輸入參數,且在呼叫時會產生單一 (全域或 per-arm) 特徵向量。
  • 獎勵:此函數將全域和 per-arm 特徵向量的串連作為參數,並產生獎勵。基本上,這是代理程式必須「猜測」的函數。在此值得注意的是,在 per-arm 案例中,每個手臂的獎勵函數都相同。這與經典老虎機案例有根本上的差異,在經典老虎機案例中,代理程式必須獨立估計每個手臂的獎勵函數。
def global_context_sampling_fn():
  """This function generates a single global observation vector."""
  return np.random.randint(
      -GLOBAL_BOUND, GLOBAL_BOUND, [GLOBAL_DIM]).astype(np.float32)

def per_arm_context_sampling_fn():
  """"This function generates a single per-arm observation vector."""
  return np.random.randint(
      -PER_ARM_BOUND, PER_ARM_BOUND, [PER_ARM_DIM]).astype(np.float32)

def linear_normal_reward_fn(x):
  """This function generates a reward from the concatenated global and per-arm observations."""
  mu = np.dot(x, reward_param)
  return np.random.normal(mu, VARIANCE)

現在我們已準備好初始化我們的環境。

per_arm_py_env = p_a_env.StationaryStochasticPerArmPyEnvironment(
    global_context_sampling_fn,
    per_arm_context_sampling_fn,
    NUM_ACTIONS,
    linear_normal_reward_fn,
    batch_size=BATCH_SIZE
)
per_arm_tf_env = tf_py_environment.TFPyEnvironment(per_arm_py_env)

以下我們可以檢查此環境產生什麼。

print('observation spec: ', per_arm_tf_env.observation_spec())
print('\nAn observation: ', per_arm_tf_env.reset().observation)

action = tf.zeros(BATCH_SIZE, dtype=tf.int32)
time_step = per_arm_tf_env.step(action)
print('\nRewards after taking an action: ', time_step.reward)
observation spec:  {'global': TensorSpec(shape=(40,), dtype=tf.float32, name=None), 'per_arm': TensorSpec(shape=(70, 50), dtype=tf.float32, name=None)}

An observation:  {'global': <tf.Tensor: shape=(20, 40), dtype=float32, numpy=
array([[ -4.,   8.,  -5.,   7.,  -3.,  -7.,  -1.,   8.,   4.,   2.,  -8.,
          5.,   9.,   7.,   4.,  -6.,  -1.,   1., -10.,   1.,   3.,   1.,
          3.,   8.,  -4.,   1.,   5.,  -8.,   1., -10.,   7.,   7.,  -5.,
          8.,  -4.,   7.,   4.,  -3.,  -5.,   9.],
       [  9.,   2.,   3.,   1.,  -2.,   7.,   6.,   2.,  -9.,  -3.,  -2.,
          8.,   5.,  -1.,   6.,  -4.,   2.,  -2.,  -5.,   6.,  -2., -10.,
         -3.,   3.,  -7.,   5.,  -3.,   4.,  -2.,   0.,   2.,  -4.,  -6.,
          2.,   1.,  -4.,   9.,  -1.,   8.,  -3.],
       [  3.,   9.,  -3.,   8.,  -1.,   0.,   9.,   9.,   6.,   8.,   2.,
          1.,  -5.,  -3.,  -4.,   7.,  -7.,  -4.,   2.,   3.,   4.,  -4.,
          6.,  -3.,  -3.,   7.,   2.,   1.,  -3.,  -8.,   7.,  -2.,   2.,
          5.,  -5.,   4.,   6.,   7.,   9.,   2.],
       [ -2.,   7.,   8.,  -3.,   8., -10.,  -9.,  -5.,   7.,   9.,   0.,
         -3.,  -1., -10.,   2.,   8.,  -8.,   4.,  -2.,   6.,   3.,  -4.,
         -8., -10.,   9.,  -2.,  -8.,  -7.,  -4.,   0.,   6.,  -3.,   9.,
         -4.,   1.,  -5.,   4.,   2.,  -2.,   0.],
       [ -6.,   5.,  -8.,   3.,   2.,   2.,  -8.,   4.,  -6.,   6.,   0.,
          3.,   4.,  -4.,   2., -10.,  -7.,   3.,   6.,   9.,  -9.,  -2.,
          6.,   0.,  -4.,   8.,   0.,  -4.,   0.,   3.,  -3.,   9.,   0.,
         -5.,   0.,   5.,  -7.,  -2.,   0.,   3.],
       [  4.,   9.,  -2.,  -6.,  -3., -10.,  -2.,   8.,   8.,   3.,   0.,
          3.,  -1.,  -9.,   1.,  -6.,   8.,  -5.,   1.,   2.,   3.,  -3.,
          4.,   7.,   8.,  -8.,   0.,  -3.,   1.,   0.,  -4.,  -7.,  -2.,
         -5.,  -6.,   4.,  -2.,   3.,   1.,  -4.],
       [ -3.,  -2.,   7.,  -5.,  -7.,  -3.,   0.,  -1.,   8.,  -6.,   1.,
          9.,  -9.,  -6.,   3.,   2.,  -6.,   6., -10.,   6.,  -6.,   6.,
         -5.,   3.,  -7.,  -4.,   6.,   4.,  -7.,  -4.,  -5.,   1., -10.,
          5.,  -6.,  -9.,  -3.,  -2., -10.,  -4.],
       [  7.,   9.,   2.,   4.,  -4.,  -7.,   4.,  -6.,   2.,   9.,   7.,
          8., -10.,   7.,   6.,   7.,  -4.,  -1.,  -4.,   8.,  -4.,  -9.,
         -6.,  -1.,   7.,  -8.,  -5.,  -6.,  -3.,   2.,  -5.,   9.,  -6.,
         -6.,   8.,  -2.,  -1.,  -2.,  -5.,  -6.],
       [  5.,   7.,   7.,  -8.,  -3.,   9.,   6.,   7.,   1.,  -3.,  -2.,
          7.,  -5.,   5.,   0.,  -7.,   2.,  -1.,  -1.,   6.,  -8.,  -2.,
        -10.,   6.,   2.,   8.,   0.,   3.,   1.,  -7.,   5.,   3.,   4.,
          8.,  -2.,  -2.,  -8.,   8.,   5.,   0.],
       [  1.,   0.,  -2.,  -6.,   7.,   8.,  -5.,  -8.,  -7.,  -8.,  -4.,
         -9.,   3.,  -9.,   8.,  -4.,  -1., -10.,   2.,  -1.,   1.,  -4.,
          6.,  -1.,   4.,   1.,  -7.,  -4.,  -8.,  -6.,   7.,   4.,  -8.,
         -3.,  -7.,   5.,  -1.,  -4., -10.,  -4.],
       [ -7.,   4.,   0.,  -9.,  -8.,  -6.,  -7.,   8.,   3.,   7.,  -7.,
         -1.,   7.,  -3.,   5.,   6.,   1.,  -5.,   3., -10.,  -7.,   0.,
         -4.,  -4.,  -7.,   2.,  -5.,   3.,   2.,  -3.,   3.,  -7.,  -1.,
        -10.,   9.,   1.,  -2.,   3.,   4.,  -8.],
       [  8.,  -5., -10.,   7.,   7.,  -7.,   2.,   7.,  -1., -10.,   6.,
         -4.,  -5.,  -3.,  -8.,  -2.,  -2.,   3.,   1.,   2.,   1.,  -6.,
          8.,  -7.,   7.,   8.,  -8., -10.,   2.,  -7.,   1.,  -2.,  -3.,
         -6.,   9.,   4.,   2.,  -1.,  -7.,  -1.],
       [  2.,   5.,  -2., -10.,  -2.,   2.,   2.,  -9.,  -9.,  -8.,  -1.,
         -7.,  -9.,  -4.,  -2.,  -3.,  -9.,  -3.,   5.,   5.,  -1.,   0.,
          8.,  -8.,   9.,  -3.,   8.,   9.,   7.,  -8.,   4.,  -7.,   0.,
          1.,  -1.,   1.,   0.,   8.,  -1., -10.],
       [ -6.,  -1.,   9.,   4.,  -8.,  -5.,   8.,   0., -10., -10., -10.,
         -3.,   8.,  -7.,  -2.,  -2., -10.,   2.,  -3.,  -9.,   0.,   7.,
          0.,   2.,  -7.,  -6.,  -6.,   3.,   2.,   6.,   8.,   9., -10.,
          7.,  -4.,  -9.,   7.,  -9.,   3.,  -5.],
       [ -2.,   4.,   1.,   7.,  -5.,  -7.,  -1.,  -8.,  -9.,  -1.,  -7.,
          4.,  -7.,  -7.,   7.,  -2.,  -5.,   3., -10.,   9.,   9.,  -5.,
          1.,   4.,   5.,   0.,  -1.,   5.,   9.,   1.,   8.,  -9.,  -9.,
          6.,  -6.,  -9.,   6.,   7.,   5.,   9.],
       [-10.,  -3.,  -5.,   7.,  -9.,  -4.,   7.,  -9.,  -2.,   3.,  -1.,
         -5.,  -9.,  -7.,  -6.,   6.,  -4.,  -7.,   2.,   0.,   1., -10.,
         -3., -10.,  -7.,  -4.,  -9.,   0.,   3.,  -8.,  -7.,   7.,  -2.,
          3.,   1.,   3.,  -9.,  -2.,  -9.,  -3.],
       [  8.,   3.,  -4.,  -2.,  -7.,  -9., -10.,  -1.,   1.,  -5.,   0.,
          6.,   0.,   5.,   9.,  -3., -10.,   5.,   9.,   0.,  -8.,  -2.,
          4.,   8.,   3.,   5.,   0.,  -6.,  -5.,  -2.,  -1.,   3.,  -2.,
         -3.,  -1.,   8.,  -1.,   1.,  -1.,   5.],
       [ -8.,   3.,  -6.,   4.,  -8.,   8.,  -8.,   4.,   2.,   1.,   4.,
         -8.,  -9.,   8.,  -8.,   3.,   2.,   0., -10.,  -5.,   5.,  -3.,
         -7.,  -3.,   1.,   1.,  -7.,   9.,   1.,  -3.,   8.,   8.,   1.,
          7.,  -2.,  -9.,  -3.,  -6.,  -1., -10.],
       [-10.,  -5.,   4.,   4.,  -9.,  -5.,  -8.,   6.,   5.,  -9.,  -8.,
          4.,  -7.,   2.,   7.,   2.,   6.,  -1.,   0.,   8.,  -6.,   3.,
          2.,   7.,  -2.,  -7.,  -7.,  -3.,   5.,   1.,   9.,   8.,   2.,
         -1.,   3.,  -5.,   6.,  -1.,  -9.,  -8.],
       [ -6.,  -8.,  -7.,  -2., -10.,   7.,   3.,  -2.,  -8.,   7.,  -8.,
        -10.,   7.,   8.,  -2.,   6.,   3.,   6.,  -1.,   0.,  -6.,  -7.,
          7.,   2.,  -4.,   7.,  -9.,  -5.,   2.,   1.,  -1.,  -9.,   7.,
          9.,  -5., -10.,   6.,   9.,   6.,  -2.]], dtype=float32)>, 'per_arm': <tf.Tensor: shape=(20, 70, 50), dtype=float32, numpy=
array([[[ 0.,  5.,  3., ..., -2.,  0., -4.],
        [-5.,  5., -5., ...,  3.,  3.,  4.],
        [ 1., -6.,  2., ...,  0., -4., -1.],
        ...,
        [ 1., -3., -5., ..., -5.,  4.,  3.],
        [ 3., -4.,  0., ..., -5., -4.,  2.],
        [-3., -4., -6., ..., -1., -5., -2.]],

       [[ 3., -3., -6., ..., -2., -4., -1.],
        [-5.,  5., -4., ..., -1.,  3., -1.],
        [-4.,  4.,  5., ...,  3., -3., -3.],
        ...,
        [-4., -4.,  5., ..., -2.,  0., -4.],
        [ 5., -6.,  1., ..., -1., -5., -5.],
        [ 5., -4.,  5., ...,  4., -4., -4.]],

       [[-3.,  4.,  0., ...,  1.,  0.,  0.],
        [ 1., -1., -5., ..., -4.,  5., -4.],
        [ 2.,  4.,  1., ..., -6., -4., -4.],
        ...,
        [ 0.,  3.,  4., ..., -6., -4.,  1.],
        [ 3.,  5., -5., ...,  5., -2.,  4.],
        [ 3., -5.,  4., ...,  2., -3., -5.]],

       ...,

       [[ 1., -5., -3., ..., -1., -1.,  1.],
        [-5.,  2., -4., ..., -3.,  4., -6.],
        [-3., -3.,  1., ...,  0., -3., -1.],
        ...,
        [-1.,  2., -2., ..., -4.,  3.,  1.],
        [-4.,  1., -3., ...,  2., -5., -5.],
        [-4., -4., -2., ...,  4., -6., -4.]],

       [[ 3.,  4.,  5., ..., -5., -2., -1.],
        [-6.,  4., -4., ...,  3., -5., -3.],
        [ 2., -3.,  5., ..., -2.,  2.,  1.],
        ...,
        [ 4.,  2., -1., ..., -5.,  5.,  1.],
        [ 1., -6.,  2., ...,  3.,  3.,  0.],
        [ 0.,  4., -6., ...,  4.,  4., -6.]],

       [[ 0.,  5., -4., ...,  4.,  1., -6.],
        [ 3., -1.,  4., ...,  1., -1., -2.],
        [ 0., -4., -1., ...,  5.,  0.,  3.],
        ...,
        [ 0.,  1., -3., ...,  0.,  5.,  4.],
        [-1.,  4., -6., ...,  2., -4., -1.],
        [ 4., -2., -6., ..., -5., -5.,  5.]]], dtype=float32)>}

Rewards after taking an action:  tf.Tensor(
[-496.27966     94.56397     47.344288   326.10242     82.47867
 -287.3221    -148.02356    184.77959    330.40982    -78.458405
  436.3813     -13.64361    251.81743    375.51117      6.9300766
  414.30618    434.41226    373.14758    374.16064    229.50754  ], shape=(20,), dtype=float32)

我們看到觀察規格是一個具有兩個元素的字典

  • 一個具有索引鍵 'global':這是全域情境部分,其形狀符合參數 GLOBAL_DIM
  • 一個具有索引鍵 'per_arm':這是 per-arm 情境,其形狀為 [NUM_ACTIONS, PER_ARM_DIM]。此部分是每個時間步中每個手臂的手臂特徵的預留位置。

LinUCB 代理程式

LinUCB 代理程式實作同名的 Bandit 演算法,該演算法會估計線性獎勵函數的參數,同時也維護估計值周圍的信賴橢球。代理程式會選擇具有最高估計預期獎勵的手臂,並假設參數位於信賴橢球內。

建立代理程式需要觀察和動作規格的知識。定義代理程式時,我們會將布林參數 accepts_per_arm_features 設定為 True

observation_spec = per_arm_tf_env.observation_spec()
time_step_spec = ts.time_step_spec(observation_spec)
action_spec = tensor_spec.BoundedTensorSpec(
    dtype=tf.int32, shape=(), minimum=0, maximum=NUM_ACTIONS - 1)

agent = lin_ucb_agent.LinearUCBAgent(time_step_spec=time_step_spec,
                                     action_spec=action_spec,
                                     accepts_per_arm_features=True)

訓練資料流程

本節簡要介紹 per-arm 特徵如何從政策流向訓練的機制。如果您有興趣,可以隨時跳到下一節 (定義遺憾指標),稍後再回到這裡。

首先,讓我們看一下代理程式中的資料規格。代理程式的 training_data_spec 屬性會指定訓練資料應具有哪些元素和結構。

print('training data spec: ', agent.training_data_spec)
training data spec:  Trajectory(
{'step_type': TensorSpec(shape=(), dtype=tf.int32, name='step_type'),
 'observation': {'global': TensorSpec(shape=(40,), dtype=tf.float32, name=None)},
 'action': BoundedTensorSpec(shape=(), dtype=tf.int32, name=None, minimum=array(0, dtype=int32), maximum=array(69, dtype=int32)),
 'policy_info': PerArmPolicyInfo(log_probability=(), predicted_rewards_mean=(), multiobjective_scalarized_predicted_rewards_mean=(), predicted_rewards_optimistic=(), predicted_rewards_sampled=(), bandit_policy_type=(), chosen_arm_features=TensorSpec(shape=(50,), dtype=tf.float32, name=None)),
 'next_step_type': TensorSpec(shape=(), dtype=tf.int32, name='step_type'),
 'reward': TensorSpec(shape=(), dtype=tf.float32, name='reward'),
 'discount': BoundedTensorSpec(shape=(), dtype=tf.float32, name='discount', minimum=array(0., dtype=float32), maximum=array(1., dtype=float32))})

如果我們仔細查看規格的 observation 部分,我們會看到它不包含 per-arm 特徵!

print('observation spec in training: ', agent.training_data_spec.observation)
observation spec in training:  {'global': TensorSpec(shape=(40,), dtype=tf.float32, name=None)}

per-arm 特徵發生了什麼事?若要回答這個問題,首先我們注意到,當 LinUCB 代理程式訓練時,它不需要所有手臂的 per-arm 特徵,只需要所選手臂的特徵。因此,捨棄形狀為 [BATCH_SIZE, NUM_ACTIONS, PER_ARM_DIM] 的張量是有道理的,因為這非常浪費,尤其是在動作數量很大時。

但是,所選手臂的 per-arm 特徵仍然必須存在於某處!為此,我們確保 LinUCB 政策將所選手臂的特徵儲存在訓練資料的 policy_info 欄位中

print('chosen arm features: ', agent.training_data_spec.policy_info.chosen_arm_features)
chosen arm features:  TensorSpec(shape=(50,), dtype=tf.float32, name=None)

我們從形狀中看到,chosen_arm_features 欄位僅具有一個手臂的特徵向量,而這將是所選的手臂。請注意,policy_info 以及 chosen_arm_features 是訓練資料的一部分,正如我們從檢查訓練資料規格中看到的那樣,因此它在訓練時可用。

定義遺憾指標

在開始訓練迴圈之前,我們先定義一些公用程式函數,以協助計算代理程式的遺憾。這些函數有助於判斷在給定一組動作 (由其手臂特徵給定) 和代理程式隱藏的線性參數的情況下,最佳預期獎勵。

def _all_rewards(observation, hidden_param):
  """Outputs rewards for all actions, given an observation."""
  hidden_param = tf.cast(hidden_param, dtype=tf.float32)
  global_obs = observation['global']
  per_arm_obs = observation['per_arm']
  num_actions = tf.shape(per_arm_obs)[1]
  tiled_global = tf.tile(
      tf.expand_dims(global_obs, axis=1), [1, num_actions, 1])
  concatenated = tf.concat([tiled_global, per_arm_obs], axis=-1)
  rewards = tf.linalg.matvec(concatenated, hidden_param)
  return rewards

def optimal_reward(observation):
  """Outputs the maximum expected reward for every element in the batch."""
  return tf.reduce_max(_all_rewards(observation, reward_param), axis=1)

regret_metric = tf_bandit_metrics.RegretMetric(optimal_reward)

現在我們已準備好開始我們的老虎機訓練迴圈。下方的驅動程式負責使用政策選擇動作、將所選動作的獎勵儲存在重播緩衝區中、計算預先定義的遺憾指標,以及執行代理程式的訓練步驟。

num_iterations = 20 # @param
steps_per_loop = 1 # @param

replay_buffer = tf_uniform_replay_buffer.TFUniformReplayBuffer(
    data_spec=agent.policy.trajectory_spec,
    batch_size=BATCH_SIZE,
    max_length=steps_per_loop)

observers = [replay_buffer.add_batch, regret_metric]

driver = dynamic_step_driver.DynamicStepDriver(
    env=per_arm_tf_env,
    policy=agent.collect_policy,
    num_steps=steps_per_loop * BATCH_SIZE,
    observers=observers)

regret_values = []

for _ in range(num_iterations):
  driver.run()
  loss_info = agent.train(replay_buffer.gather_all())
  replay_buffer.clear()
  regret_values.append(regret_metric.result())
WARNING:tensorflow:From /tmpfs/tmp/ipykernel_24657/1190294793.py:21: ReplayBuffer.gather_all (from tf_agents.replay_buffers.replay_buffer) is deprecated and will be removed in a future version.
Instructions for updating:
Use `as_dataset(..., single_deterministic_pass=True)` instead.

現在讓我們看看結果。如果我們做的一切都正確,代理程式就能夠良好地估計線性獎勵函數,因此政策可以選擇預期獎勵接近最佳獎勵的動作。這由我們上述定義的遺憾指標表示,該指標會下降並接近於零。

plt.plot(regret_values)
plt.title('Regret of LinUCB on the Linear per-arm environment')
plt.xlabel('Number of Iterations')
_ = plt.ylabel('Average Regret')

png

下一步?

上述範例已在我們的程式碼庫中實作,您也可以從中選擇其他代理程式,包括 Neural epsilon-Greedy 代理程式