多腕バンディット問題とは?仕組み・アルゴリズムとA/Bテストの違いをPythonで解説

多腕バンディット問題(multi-armed bandit problem)は、「どの選択肢が一番良いか分からないなかで、試しながら成果を最大化する」ための意思決定の枠組みです。Webサイトのボタン色、広告の配信先、プッシュ通知の内容など、正解を事前に知らないまま何度も選び続ける場面に当てはまります。鍵になるのは、まだ試していない選択肢を確かめる「探索」と、いま良いと分かっている選択肢を使う「活用」のバランスです。この記事では、問題の定義から代表的な4つのアルゴリズムのPython実装、A/Bテストとの違い、実務での応用と落とし穴までを一つずつ整理します。

まとめ:この記事の要点

  • 多腕バンディット問題は、報酬が未知の複数の選択肢(アーム)から選び続け、累積報酬を最大化する問題。強化学習の最も単純な設定にあたる。
  • 本質は「探索」と「活用」のトレードオフ。性能は最適アームとの差であるリグレット(後悔)で測る。
  • 代表アルゴリズムはε-greedy(一定確率でランダム探索)、UCB1(信頼区間の上限で選ぶ)、トンプソン抽出(事後分布からサンプリング)、Exp3(敵対的・非定常向け)。実務ではトンプソン抽出が扱いやすく高性能。
  • A/Bテストが「検証期間を固定して後で判定」するのに対し、バンディットは「配信しながら良い方へ寄せる」ため、機会損失(劣る案への配信)を減らせる。
  • 報酬が時間で変わる非定常環境や、ユーザー属性で最適解が変わる場面では、Exp3や文脈付きバンディットへの拡張を検討する。

以下で、それぞれのアルゴリズムを実装コードと判断基準つきで見ていきます。

多腕バンディット問題とは(定義・語源・強化学習との関係)

多腕バンディット問題とは、報酬の確率分布が未知である複数の選択肢(アーム)から毎回1つを選び、得られる報酬の合計を最大化する逐次的な意思決定問題です。各アームを引くたびに報酬が返るので、その結果から「どのアームが良いか」を推定しながら、同時に成果も稼がなければなりません。試行回数には上限があり、悪いアームを引いた分だけ成果を取り逃す点が難しさの核心です。

「多腕バンディット」という名前の由来

バンディット(bandit)は「盗賊」の意味で、スロットマシンの俗称です。レバー(アーム)を1本引いて金を奪っていく様子から、スロットマシンは英語で「one-armed bandit(片腕の盗賊)」と呼ばれます。腕(=選択肢)が複数あるスロットマシンに見立てたのが「multi-armed bandit=多腕バンディット」で、日本語では「マルチアームバンディット」とも表記します。プレイヤーは各台の当たりやすさを知らないまま、限られたコインでどの台を引くかを決め続ける——これが問題設定そのままのアナロジーです。

強化学習における多腕バンディット問題の位置づけ

多腕バンディット問題は、強化学習の最も単純な設定と見なせます。通常の強化学習は「状態が行動によって次々に遷移する」ことを扱いますが、バンディット問題では状態遷移がなく、毎回同じ状況で選択を繰り返します。つまり「行動→即時報酬」だけを考える1状態の強化学習です。このシンプルさゆえに、探索と活用のトレードオフという強化学習の中心課題を、理論・実装の両面で見通しよく学べます。より複雑な状態遷移を扱う強化学習ツールとしては柔軟性と拡張性に優れた強化学習ツールML-Agentsのような環境も使われます。

探索と活用のトレードオフ(exploration-exploitation)

探索(exploration)は、まだ試行回数が少なく評価が定まっていないアームを引いて情報を集める行動です。活用(exploitation)は、現時点で推定報酬が最も高いアームを引いて成果を稼ぐ行動です。探索ばかりでは良いアームが分かっても使わずに損をし、活用ばかりでは「たまたま初期に良く見えたアーム」に固執して本当の最適を見逃します。両者は同じ試行回数を奪い合う関係にあり、どう配分するかがアルゴリズムの設計そのものになります。

リグレット(後悔)で性能を測る

バンディットアルゴリズムの良し悪しは、リグレット(regret、後悔)で評価します。リグレットとは「最初から最適アームだけを引き続けていれば得られたはずの累積報酬」と「実際に得た累積報酬」の差です。探索で悪いアームを引くたびに差が積み上がるため、この値が小さいほど賢い戦略と言えます。優れたアルゴリズムは、試行回数T が増えるにつれてリグレットの増加が対数オーダー(およそ ln T に比例)に抑えられることが理論的に示されています。ただしこれは報酬分布が一定の確率的設定での話で、後述のExp3が扱う敵対的設定ではリグレットは √T のオーダーになります。実務では「どれだけ早く良いアームへ配信を寄せられたか=取り逃した成果の小ささ」として現れます。

代表的なアルゴリズムとPython実装(ε-greedy・UCB・トンプソン抽出・Exp3)

探索と活用を配分する代表的な4手法を、そのまま動かせるPythonクラスとして示します。いずれもアームごとの試行回数と推定報酬を持ち、select() でアームを選び、update() で結果を反映する共通インターフェースにしています。実行にはnumpyのみを使います。

ε-greedy法(イプシロン・グリーディ)

最もシンプルな手法です。確率εでランダムに探索し、残りの確率1−εでは推定報酬が最大のアームを活用します。εを0.1にすれば「10回に1回はランダムに試す」という直感的な挙動になります。実装が容易で導入の第一歩に向く一方、εを固定すると良いアームが判明した後も一定割合で無駄な探索を続けます。εを試行とともに減衰させる「ε-decreasing」にすると、リグレットを対数オーダーへ改善できます。

import numpy as np

class EpsilonGreedy:
    def __init__(self, n_arms, epsilon=0.1):
        self.epsilon = epsilon
        self.counts = np.zeros(n_arms)   # 各アームを引いた回数
        self.values = np.zeros(n_arms)   # 各アームの推定平均報酬

    def select(self):
        if np.random.random() < self.epsilon:
            return np.random.randint(len(self.values))   # 探索
        return int(np.argmax(self.values))               # 活用

    def update(self, arm, reward):
        self.counts[arm] += 1
        n = self.counts[arm]
        # 逐次平均で推定値を更新(過去の全報酬を保存しなくてよい)
        self.values[arm] += (reward - self.values[arm]) / n

UCB1(Upper Confidence Bound)

UCB1は、推定報酬に「不確実性のボーナス」を足した値が最大のアームを選びます。試行回数が少ないアームほどボーナスが大きくなり、自動的に探索が促される仕組みです。ランダム性に頼らず「まだよく分かっていないアームを楽観的に評価する」ため、ε-greedyのような無駄打ちが起きにくいのが利点です。Auerらが2002年の論文「Finite-time Analysis of the Multiarmed Bandit Problem」で示した手法で、選択式は推定平均 x̄ᵢ に √(2 ln t / nᵢ) を加えたものです(t は総試行回数、nᵢ はアームi の試行回数)。

class UCB1:
    def __init__(self, n_arms):
        self.counts = np.zeros(n_arms)
        self.values = np.zeros(n_arms)

    def select(self):
        for i in range(len(self.counts)):
            if self.counts[i] == 0:
                return i          # 未選択のアームは必ず1度引く
        t = self.counts.sum()
        bonus = np.sqrt(2 * np.log(t) / self.counts)   # 不確実性ボーナス
        return int(np.argmax(self.values + bonus))

    def update(self, arm, reward):
        self.counts[arm] += 1
        n = self.counts[arm]
        self.values[arm] += (reward - self.values[arm]) / n

トンプソン抽出(Thompson Sampling)

トンプソン抽出は、各アームの報酬確率をベイズ的に確率分布として持ち、その事後分布から1つサンプリングして、値が最大のアームを選びます。クリックの有無のような0か1の報酬(ベルヌーイ試行)では、成功回数と失敗回数からベータ分布 Beta(成功+1, 失敗+1) を事後分布に使うのが定番です。1933年にWilliam R. Thompsonが提案した古典的手法ですが、実務ではε-greedyやUCB1より高い性能を示すことが多く、広告配信やレコメンドで広く使われています。分布からのサンプリングが探索の役割を自然に果たす点が特徴です。

class ThompsonSampling:
    def __init__(self, n_arms):
        self.alpha = np.ones(n_arms)   # 成功回数 + 1
        self.beta = np.ones(n_arms)    # 失敗回数 + 1

    def select(self):
        theta = np.random.beta(self.alpha, self.beta)   # 事後分布から抽出
        return int(np.argmax(theta))

    def update(self, arm, reward):
        if reward > 0:
            self.alpha[arm] += 1
        else:
            self.beta[arm] += 1

Exp3(敵対的バンディット)

ここまでの3手法は「各アームの報酬分布は一定」という確率的設定を前提にしていました。Exp3(Exponential-weight algorithm for Exploration and Exploitation)は、報酬が敵対的に(あるいは時間とともに)変化しても機能するよう設計された手法です。各アームに重みを持たせ、得られた報酬に応じて指数関数的に重みを更新し、重みに比例した確率でアームを選びます。報酬分布が定常でない場面(トレンドが移り変わる、季節性があるなど)に強く、Auerらが2002年の論文「The Nonstochastic Multiarmed Bandit Problem」で提案しました。

class Exp3:
    def __init__(self, n_arms, gamma=0.1):
        self.gamma = gamma
        self.weights = np.ones(n_arms)

    def select(self):
        w = self.weights
        self.probs = (1 - self.gamma) * w / w.sum() + self.gamma / len(w)
        return int(np.random.choice(len(w), p=self.probs))

    def update(self, arm, reward):
        p = self.probs[arm]
        estimated = reward / p                       # 選ばれにくいアームほど重く補正
        self.weights[arm] *= np.exp(self.gamma * estimated / len(self.weights))

4アルゴリズムの比較と選び方

4手法は前提と探索の仕組みが異なり、「常にこれが最良」というものはありません。まずε-greedyで挙動を確かめ、報酬が0か1でオンライン配信するならトンプソン抽出、理論的な保証を重視するならUCB1、報酬分布が変化するならExp3、という順で検討すると選びやすくなります。

アルゴリズム 前提 探索の仕組み 実装難度 向く場面
ε-greedy 確率的 一定確率でランダム まず試す・小規模
UCB1 確率的 信頼区間の上限 理論保証を重視
トンプソン抽出 確率的(ベイズ) 事後分布から抽出 報酬が0か1・実務で高性能
Exp3 敵対的 指数重み 報酬が変化・非定常

実装難度はいずれも数十行に収まり、差は大きくありません。判断軸は「報酬分布が一定か変化するか」「オンラインで少しずつ配信するか」の2点が中心になります。

多腕バンディット問題とA/Bテストの違い(使い分け)

Web最適化ではA/Bテストと比較されることが多く、両者は「複数案から良い案を選ぶ」目的こそ同じですが、配分の仕方が根本的に違います。

A/Bテストとの構造的な違い

A/Bテストは、検証期間を固定してトラフィックを各案へ均等に割り当て、期間終了後に統計的な有意差を判定して勝者を1つ選びます。検証中は劣る案にも半分のトラフィックを流し続けるため、その分の機会損失が発生します。一方バンディットは、配信しながらリアルタイムで成績の良い案へ配分を寄せていくため、劣る案への配信を早く絞れます。A/Bテストの有意差判定の考え方は有意差・p値の意味と検定の種類をわかりやすく解説した記事で整理しています。

どちらを使うべきかの判断基準

「明確な結論(どちらが有意に優れるか)を残し、意思決定の根拠として記録したい」場合はA/Bテストが向きます。統計的検定に基づく判断が説明責任を果たしやすいためです。逆に「検証そのものより成果の最大化が目的で、劣る案への配信を最小化したい」場合や、選択肢が多く全ペアの比較が現実的でない場合はバンディットが有利です。キャンペーン期間が短く機会損失が痛い施策、常時稼働し続けるレコメンドや広告配信では、バンディットの逐次最適化が効いてきます。マネージドサービスでも同じ考え方が実装されており、Amazon Personalizeの探索の重み(既定0.3)がその一例です。

多腕バンディット問題の応用例(Web最適化・広告配信・推薦・プッシュ通知)

多腕バンディットは、正解が事前に分からず、結果を見ながら配信を最適化したい場面で広く使われています。Webサイト最適化では、見出し・ボタン・バナーの複数案をアームとして、クリック率やコンバージョン率を報酬に配分を最適化します。広告配信では、どのクリエイティブ・配信先を出すかをアームにし、CVRを報酬として運用します(CyberAgentの技術ブログで広告配信への適用事例が公開されています)。

プッシュ通知では、送信するメッセージ文面や配信タイミングの候補をアームにして開封率を最大化します(ZOZOの技術ブログでプッシュ配信の最適化施策が公開されています)。レコメンドでも、どのアイテムを推薦するかをバンディットで選ぶ手法があり、新着アイテムの探索と人気アイテムの活用を両立できます。レコメンド基盤そのものの選定はAmazon PersonalizeなどのレコメンドAIを比較した記事が参考になります。共通するのは「試行のたびに成果(報酬)が観測でき、その結果をすぐ次の配信へ反映できる」という条件です。

実装・運用でつまずくポイント

アルゴリズム自体は短く書けますが、実運用では前提が崩れる場面が多く、そこで性能が出ないことがあります。ここでは教科書のコードには現れにくい実務上の注意点を挙げます。

非定常環境(報酬が時間で変わる)

ε-greedy・UCB1・トンプソン抽出は「各アームの報酬確率が一定」という前提に立ちます。ところが実サービスでは、季節・トレンド・在庫状況で最適な案が入れ替わります。過去の全データを等しく平均していると、環境が変わっても古い勝者に固執してしまいます。対策は、直近の報酬を重く見る「割引(discounting)」や「スライディングウィンドウ(一定期間だけを集計)」の導入、あるいは非定常を前提とするExp3の採用です。報酬が変化する疑いがあるなら、まず古い実績を減衰させる仕組みを入れます。

文脈付きバンディット(contextual bandit)への発展

基本のバンディットは「全ユーザーに共通の最適アーム」を探しますが、実際にはユーザーの属性や状況で最適案が変わります。文脈付きバンディットは、選択のたびにユーザー特徴などの文脈(context)を受け取り、その文脈に応じて最適なアームを選びます。LinUCBやロジスティック回帰を組み合わせた手法が代表的で、レコメンドや広告配信の多くは実質この文脈付きバンディットです。「全員に同じ案を出すのは不自然」と感じたら、基本形ではなく文脈付きへ進むサインです。方策を学習する強化学習との接続についてはDPOとPPOの違いを仕組み・性能・実装で比較した記事も参考になります。

多腕バンディットを使うべきでない場面

万能ではありません。まず、報酬の観測が大きく遅れる場合(コンバージョンが数日後に確定するなど)は、更新が追いつかず探索が空回りします。この場合はA/Bテストで期間を区切るか、遅延を前提とした設計が要ります。次に、施策の結果を「なぜその案が勝ったか」まで説明・記録する必要がある意思決定(重要なUI刷新など)では、配分が動き続けるバンディットより、条件を固定したA/Bテストのほうが根拠を残せます。また候補が2案しかなく試行回数も十分に取れるなら、わざわざバンディットを組むより素直なA/Bテストで足ります。「成果最大化そのものが目的か、判断の根拠を残すのが目的か」を先に決めることが、手法選定の分かれ目です。

よくある質問

多腕バンディット問題は英語で何と言いますか?

multi-armed bandit problem(マルチアームバンディット問題)と言います。「bandit」はスロットマシンの俗称で、腕(レバー)が複数あるスロットに見立てた呼び名です。腕が1本のスロットマシンは one-armed bandit と呼ばれます。

多腕バンディット問題と強化学習はどう違いますか?

多腕バンディット問題は強化学習の最も単純な特殊ケースです。通常の強化学習は行動によって状態が次々に遷移することを扱いますが、バンディット問題には状態遷移がなく、毎回同じ状況で「行動→即時報酬」を繰り返します。探索と活用のトレードオフという強化学習の核心を、状態遷移抜きで学べる入り口にあたります。

ε-greedyとUCB1はどちらを使うべきですか?

まず挙動を確かめたい・実装をとにかく簡単にしたいならε-greedy、無駄な探索を避けたい・理論的なリグレット保証を重視するならUCB1が向きます。ε-greedyはεを固定すると良いアーム判明後も探索を続けるため、実運用ではεを減衰させるか、より性能の出やすいトンプソン抽出を検討する価値があります。

多腕バンディットを使えばA/Bテストは不要になりますか?

いいえ、目的が異なるため使い分けます。バンディットは成果の最大化(劣る案への配信を最小化)に向き、A/Bテストは統計的な有意差という判断根拠を残すことに向きます。説明責任が求められる意思決定や、結果を記録として残したい検証にはA/Bテストが依然として有効です。

Exp3はどんなときに使いますか?

報酬の分布が一定でない敵対的・非定常な環境で使います。トレンドや季節性で最適案が入れ替わる、あるいは報酬の出方に規則性を仮定できない場面で、指数重みによって変化に追随します。報酬分布が安定しているならトンプソン抽出やUCB1のほうが効率的です。

関連記事

資料請求

RELATED POSTS 関連記事