How to simulate a contextual bandit in Python
Simulate a contextual multi-armed bandit with random features and epsilon-greedy action selection in Python.
Python code
35 linesimport random
class ContextualBandit:
def __init__(self, n_actions=3, n_features=4):
self.n_actions = n_actions
self.n_features = n_features
self.theta = [random.random() for _ in range(n_actions * n_features)]
def mock_context(self):
return [random.uniform(-1, 1) for _ in range(self.n_features)]
def arm_rewards(self, context):
rewards = []
for a in range(self.n_actions):
theta_a = self.theta[a * self.n_features:(a + 1) * self.n_features]
reward = sum(t * c for t, c in zip(theta_a, context))
rewards.append(round(reward, 3))
return rewards
def sample_arm(self, context, epsilon=0.1):
if random.random() < epsilon:
return random.randint(0, self.n_actions - 1)
rewards = self.arm_rewards(context)
return rewards.index(max(rewards))
if __name__ == "__main__":
bandit = ContextualBandit(n_actions=3, n_features=4)
ctx = bandit.mock_context()
chosen = bandit.sample_arm(ctx)
rewards = bandit.arm_rewards(ctx)
print(f"Context: {ctx}")
print(f"Rewards: {rewards}")
print(f"Chosen action (epsilon=0.1): {chosen}")
Output
Context: [0.542, -0.831, 0.204, 0.773]
Rewards: [0.215, -0.441, 0.587]
Chosen action (epsilon=0.1): 2
How it works
The ContextualBandit class stores a linear reward model theta that maps context features to arm rewards. mock_context generates random feature vectors to simulate user or environment states. arm_rewards computes the inner product between each arm's theta slice and the context, mimicking a linear reward function. sample_arm applies epsilon-greedy exploration: with probability 0.1 it picks a random arm, otherwise it exploits the arm with the highest predicted reward. This mirrors how real bandit systems decide between exploration and exploitation in A/B experiments.
Common mistakes
- Using the same random seed for both context and rewards, making the simulation non-deterministic.
- Forgetting to round rewards, leading to floating point noise in output.
- Not resetting the random state when reproducing experiments.
- Misaligning theta indices when slicing per-arm parameters.
Variations
- Use numpy arrays and vectorized operations for faster batch simulations.
- Implement Thompson sampling by sampling theta from a posterior distribution instead of fixed values.
Real-world use cases
- Testing multi-armed bandit algorithms before deploying to production recommendation systems.
- Simulating user engagement to validate online learning policies in marketing campaigns.
- Benchmarking epsilon-greedy against other exploration strategies in a controlled environment.
Sponsored
More from A/B testing & experimentation
Keep learning
Related tutorials and quizzes for this topic.