RTBGym は,広告入札 (real-time bidding; RTB) のためのシミュレーション環境です.このシミュレーターは強化学習の定式化に則り,OpenAI Gym および Gymnasium のインターフェースに従っています.RTBGymは,WinningPriceDistribution
,ClickThroughRate
,ConversionRate
などの環境モジュールをカスタマイズできるように設計されています.
RTBGymは SCOPE-RL リポジトリの下で公開されており,オフライン強化学習の実装を試す環境として容易に使えます.
広告入札エージェントの目的は,ある予算制約が与えられもとで,一つのエピソード内でのKPI (クリックやコンバージョン) を最大化することです. KPIを最大にするために,エージェントは以下の式中の入札価格関数パラメータ
ここで$r^{\ast}$はKPIの予測値または期待値を表します.
この問題を制約付きマルコフ決定過程(CMDP)として定式化します.
-
タイムステップ
: 一つのエピソード(一日,一週間)に含まれるタイムステップ(24時間,数日など). -
状態
: それぞれのタイムステップで,環境からフィードバックが返ってきます.- タイムステップ
- 残予算
- 一つ前のタイムステップでのインプレッション単位の特徴(予算消費率,一つのインプレッションあたりのコスト,オークション落札率,報酬)
- 一つ前のタイムステップでの入札関数パラメータ(強化学習エージェントが選ぶ行動)
-
行動
: エージェントはKPIを最大化するために入札価格パラメータ$\alpha$を選択します. -
報酬
: 一つのタイムステップあたりに得られたクリックまたはコンバージョンの合計数. -
制約
: あらかじめ決められた,一つのエピソードあたりの予算.
目的は予算制約の中で強化学習を利用して, 累積報酬を最大化する方策を得ることです.
RTBGymでは2つの標準的なRTB環境を提供しています.
"RTBEnv-discrete-v0"
: 連続行動空間に対する標準的な環境."RTBEnv-continuous-v0"
: 離散行動空間に対する標準的な環境.
RTBGymは,次の2つの環境で構成されています.
- RTBEnv: 連続行動空間を持つ標準的な設定可能な環境.
- CustomizedRTBEnv: 行動空間と報酬予測器を指定するカスタマイズされた環境.
RTBGymは,以下の3つのモジュールについて設定可能です.
- WinningPriceDistribution: オークション入札の落札価格分布を定義するクラス.
- ClickThroughRate: ユーザーのクリック率を定義するクラス.
- ConversionRate: ユーザーのコンバージョン率を定義するクラス.
上記モジュールは,abstract classに従ってカスタマイズすることができます.
また,入札機能はBidderクラスで,オークションのシミュレーションはSimulatorクラスでそれぞれ定義されています.
RTBGymは,Pythonののpip
を使用して,SCOPE-RLの一部としてインストールできます.
pip install scope-rl
また,コードからインストールすることもできます
git clone https://github.com/hakuhodo-technologies/scope-rl
cd scope-rl
python setup.py install
標準実装の環境を用いる例とカスタマイズした環境を用いる例を紹介します. なお,オンライン/オフライン強化学習およびオフ方策評価の実装例は,SCOPE-RLのREADMEで提供されています.
RTBEnvの標準実装は,OpenAI Gym や Gymnasiumのインターフェースに従って gym.make()
から利用可能です.
# rtbgymとgymをインポートする
import rtbgym
import gym
# (1) 連続行動空間に対する標準的な環境
env = gym.make('RTBEnv-discrete-v0')
# (2) 離散行動空間に対する標準的な環境
env_ = gym.make('RTBEnv-continuous-v0')
基本的なインタラクションは,以下の4行のコードのみで実行できます.
obs, info = env.reset(), False
while not done:
action = agent.act(obs)
obs, reward, done, truncated, info = env.step(action)
ランダム方策で行うインタラクションを可視化してみましょう.(連続行動空間) 離散行動空間の場合も同様に扱うことができます.
# 他のライブラリからインポートする
from offlinegym.policy import OnlineHead
from d3rlpy.algos import RandomPolicy as ContinuousRandomPolicy
from d3rlpy.preprocessing import MinMaxActionScaler
import matplotlib.pyplot as plt
# ランダムエージェントを定義する (連続行動空間)
agent = OnlineHead(
ContinuousRandomPolicy(
action_scaler=MinMaxActionScaler(
minimum=0.1, # 方策が取り得る最小値
maximum=10, # 方策が取り得る最大値
)
),
name="random",
)
agent.build_with_env(env)
# (3) 連続空間での基本的なインタラクション
obs, info = env.reset()
done = False
# ログ
remaining_budget = [obs[1]]
cumulative_reward = [0]
while not done:
action = agent.predict_online(obs)
obs, reward, done, truncated, info = env.step(action)
# ログ
remaining_budget.append(obs[1])
cumulative_reward.append(cumulative_reward[-1] + reward)
# 結果を可視化する
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax1.plot(remaining_budget[:-1], label='remaining budget')
ax2 = ax1.twinx()
ax2.plot(cumulative_reward[:-1], label='cumulative reward', color='tab:orange')
ax1.set_xlabel('timestep')
ax1.set_ylabel('remainig budget')
ax1.set_ylim(0, env.initial_budget + 100)
ax2.set_ylabel('reward (coversion)')
ax1.legend(loc='upper left')
ax2.legend(loc='upper right')
plt.show()
1エピソード中の残予算と累積報酬の推移
ここでSCOPE-RL と d3rlpy を利用していますが,RTBGymはOpenAI Gym と Gymnasiumのようなインターフェースで動作する他のライブラリとも互換性があります.
次に,環境のカスタマイズの方法を説明します.
環境設定のリスト: (クリックして展開)
objective
: RTBの重要性能指標 (KPI, "click"または"conversion")cost_indicator
:コストが発生するタイミング("impression", "click", "conversion")step_per_episode
: 一エピソードでの意思決定の数initial_budget
: 一エピソードでの初期予算 (制約)n_ads
: 入札オークション環境上の広告の数n_users
: 入札オークション環境上のユーザーの数ad_feature_dim
: 広告の特徴ベクトルの次元user_feature_dim
: ユーザーの特徴ベクトルの次元ad_feature_vector
: 広告特徴量 (ベクトル)user_feature_vector
: ユーザー特徴量 (ベクトル)ad_sampling_rate
: 入札オークションでどの広告を選ぶかを決めるサンプリング確率user_sampling_rate
: 入札オークションでどのユーザーが対象となるかを決めるサンプリング確率WinningPriceDistribution
: 入札オークションの落札価格分布ClickTroughRate
: クリック率 (クリック/ インプレッション)ConversionRate
: コンバージョン率 (コンバージョン/ クリック)standard_bid_price_distribution
: 平均インプレッション確率が0.5と予想される時の入札価格の分布minimum_standard_bid_price
: 標準入札価格の最低値search_volume_distribution
: タイムステップごとの検索ボリューム分布minimum_search_volume
: タイムステップごとの最小の検索ボリュームrandom_state
: ランダムシード
from rtbgym import RTBEnv
env = RTBEnv(
objective="click", # クリック数の合計を最大化
cost_indicator="click", # クリックするごとにコストが発生
step_per_episode=14, # 1エピソード14日とする14 days as an episode
initial_budget=5000, # 14日で予算は5000
random_state=random_state,
)
具体的には,ユーザーは以下のように独自の WinningPriceDistribution
とClickThroughRate
とConversionRate
を定義できます
# rtbgymモジュールをインポートする
from rtbgym import BaseWinningPriceDistribution
from rtbgym.utils import NormalDistribution
# その他必要なものをインポートする
from dataclasses import dataclass
from typing import Optional, Union, Tuple
import numpy as np
@dataclass
class CustomizedWinningPriceDistribution(BaseWinningPriceDistribution):
"""初期化."""
n_ads: int
n_users: int
ad_feature_dim: int
user_feature_dim: int
step_per_episode: int
standard_bid_price_distribution: NormalDistribution = NormalDistribution(
mean=50,
std=5,
random_state=12345,
)
minimum_standard_bid_price: Optional[Union[int, float]] = None
random_state: Optional[int] = None
def __post_init__(self):
self.random_ = check_random_state(self.random_state)
def sample_outcome(
self,
bid_prices: np.ndarray,
**kwargs,
) -> Tuple[np.ndarray]:
"""各オークションのインプレッションとセカンドプライスを確率的に決定する."""
# 単純な正規分布からの落札価格のサンプリング
winning_prices = self.random_.normal(
loc=self.standard_bid_price,
scale=self.standard_bid_price / 5,
size=bid_prices.shape,
)
impressions = winning_prices < bid_prices
return impressions.astype(int), winning_prices.astype(int)
@property
def standard_bid_price(self):
return self.standard_bid_price_distribution.mean
from rtbgym import BaseClickAndConversionRate
from rtbgym.utils import sigmoid
@dataclass
class CustomizedClickThroughRate(BaseClickAndConversionRate):
"""初期化."""
n_ads: int
n_users: int
ad_feature_dim: int
user_feature_dim: int
step_per_episode: int
random_state: Optional[int] = None
def __post_init__(self):
self.random_ = check_random_state(self.random_state)
self.ad_coef = self.random_.normal(
loc=0.0,
scale=0.5,
size=(self.ad_feature_dim, 10),
)
self.user_coef = self.random_.normal(
loc=0.0,
scale=0.5,
size=(self.user_feature_dim, 10),
)
def calc_prob(
self,
ad_ids: np.ndarray,
user_ids: np.ndarray,
ad_feature_vector: np.ndarray,
user_feature_vector: np.ndarray,
timestep: Union[int, np.ndarray],
) -> np.ndarray:
"""CTRの計算 (インプレッションあたりのクリック)."""
ad_latent = ad_feature_vector @ self.ad_coef
user_latent = user_feature_vector @ self.user_coef
ctrs = sigmoid((ad_latent * user_latent).mean(axis=1))
return ctrs
def sample_outcome(
self,
ad_ids: np.ndarray,
user_ids: np.ndarray,
ad_feature_vector: np.ndarray,
user_feature_vector: np.ndarray,
timestep: Union[int, np.ndarray],
) -> np.ndarray:
"""impression=Trueの場合にクリックが発生するかどうかを確率的に決定します."""
ctrs = self.calc_prob(
timestep=timestep,
ad_ids=ad_ids,
user_ids=user_ids,
ad_feature_vector=ad_feature_vector,
user_feature_vector=user_feature_vector,
)
clicks = self.random_.rand(len(ad_ids)) < ctrs
return clicks.astype(int)
CustomizedRTBEnv
は行動空間の離散化や再定義を可能にするモジュールです.
CustomizedRTBEnv
を用いて,独自の reward_predictor
を設定することもできます.
環境設定のリスト: (クリックして展開)
original_env
: ベースとなるRTB環境reward_predictor
: 入札価格を決定するための報酬を予測する機械学習モデルscaler
: 入札価格決定に使用するスケーリング係数 (定数)action_min
: 調整率の最小値action_max
: 調整率の最大値action_type
: 強化学習エージェントの行動タイプ ("discrete" または "continuous")n_actions
: 離散行動の数action_meaning
: エージェントの行動インデックスと実際の"離散"行動のマッピング関数
from rtbgym import CustomizedRTBEnv
custom_env = CustomizedRTBEnv(
original_env=env,
reward_predictor=None, # 真の報酬予測器として真の(期待)報酬を利用
action_type="discrete",
)
より多くの例はquickstart_ja/rtb/rtb_synthetic_customize_env_ja.ipynbを参照してください.
環境の統計量の可視化はexamples/quickstart_ja/rtb/rtb_synthetic_data_collection_ja.ipynbで確認できます.
さらに,オンライン/オフライン強化学習やオフ方策評価,オフ方策選択の実装例は以下を参照してください.examples/quickstart_ja/rtb/rtb_synthetic_discrete_basic_ja.ipynb (離散行動空間) and examples/quickstart_ja/rtb/rtb_synthetic_continuous_basic_ja.ipynb (連続行動空間).
ソフトウェアを使用する場合は,以下の論文の引用をお願いします.
Haruka Kiyohara, Ren Kishimoto, Kosuke Kawakami, Ken Kobayashi, Kazuhide Nakata, Yuta Saito.
SCOPE-RL: A Python Library for Offline Reinforcement Learning and Off-Policy Evaluation
Bibtex:
@article{kiyohara2023scope,
author = {Kiyohara, Haruka and Kishimoto, Ren and Kawakami, Kosuke and Kobayashi, Ken and Nataka, Kazuhide and Saito, Yuta},
title = {SCOPE-RL: A Python Library for Offline Reinforcement Learning and Off-Policy Evaluation},
journal={arXiv preprint arXiv:2311.18206},
year = {2023},
}
SCOPE-RLへの貢献も歓迎しています! プロジェクトへの貢献方法については, CONTRIBUTING.mdを参照してください.
このプロジェクトはApache 2.0ライセンスのもとでライセンスされています - 詳細についてはLICENSEファイルをご覧ください.
- Haruka Kiyohara (Main Contributor)
- Ren Kishimoto (Tokyo Institute of Technology)
- Kosuke Kawakami (HAKUHODO Technologies Inc.)
- Ken Kobayashi (Tokyo Institute of Technology)
- Kazuhide Nakata (Tokyo Institute of Technology)
- Yuta Saito (Cornell University)
論文やソフトウェアに関する質問がある場合は,[email protected]までお気軽にお問い合わせください.
論文 (クリックして展開)
-
Greg Brockman, Vicki Cheung, Ludwig Pettersson, Jonas Schneider, John Schulman, Jie Tang, and Wojciech Zaremba. OpenAI Gym. arXiv preprint arXiv:1606.01540, 2016.
-
Takuma Seno and Michita Imai. d3rlpy: An Offline Deep Reinforcement Library, arXiv preprint arXiv:2111.03788, 2021.
-
Di Wu, Xiujun Chen, Xun Yang, Hao Wang, Qing Tan, Xiaoxun Zhang, Jian Xu, and Kun Gai. Budget Constrained Bidding by Model-free Reinforcement Learning in Display Advertising. In Proceedings of the 27th ACM International Conference on Information and Knowledge Management, 1443-1451, 2018.
-
Jun Zhao, Guang Qiu, Ziyu Guan, Wei Zhao, and Xiaofei He. Deep Reinforcement Learning for Sponsored Search Real-time Bidding. In Proceedings of the 24th ACM SIGKDD International Conference on Knowledge Discovery and Data Mining, 1021-1030, 2018.
-
Wen-Yuan Zhu, Wen-Yueh Shih, Ying-Hsuan Lee, Wen-Chih Peng, and Jiun-Long Huang. A Gamma-based Regression for Winning Price Estimation in Real-Time Bidding Advertising. In IEEE International Conference on Big Data, 1610-1619, 2017.
プロジェクト (クリックして展開)
このプロジェクトは,以下の4つのパッケージを参考にしています.