Skip to content

Commit fcbdee5

Browse files
committed
Address Copilot review comments on MAB PR
- Cast rng.integers() results to Python int in EpsilonGreedy and RandomStrategy select_arm, fixing doctest flakiness from np.int64 - Fix grammar in module docstring and RandomStrategy docstring - Add missing doctest for Bandit.__init__ - Split test_mab_strategies into a real deterministic assertion-based test and a separate demo_mab_strategies for the stochastic plot - Revert DIRECTORY.md to upstream (auto-generated, out of scope here)
1 parent e61e32b commit fcbdee5

2 files changed

Lines changed: 67 additions & 16 deletions

File tree

DIRECTORY.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -712,18 +712,14 @@
712712
* [Loss Functions](machine_learning/loss_functions.py)
713713
* Lstm
714714
* [Lstm Prediction](machine_learning/lstm/lstm_prediction.py)
715-
* [Mab](machine_learning/mab.py)
716-
* [Mean Shift](machine_learning/mean_shift.py)
717715
* [Mfcc](machine_learning/mfcc.py)
718716
* [Mini Batch Gradient Descent](machine_learning/mini_batch_gradient_descent.py)
719717
* [Multilayer Perceptron Classifier](machine_learning/multilayer_perceptron_classifier.py)
720-
* [Naive Bayes Text Classification](machine_learning/naive_bayes_text_classification.py)
721718
* [Polynomial Regression](machine_learning/polynomial_regression.py)
722719
* [Principle Component Analysis](machine_learning/principle_component_analysis.py)
723720
* [Q Learning](machine_learning/q_learning.py)
724721
* [Random Forest Classifier](machine_learning/random_forest_classifier.py)
725722
* [Random Forest Regressor](machine_learning/random_forest_regressor.py)
726-
* [Rmsprop](machine_learning/rmsprop.py)
727723
* [Scoring Functions](machine_learning/scoring_functions.py)
728724
* [Self Organizing Map](machine_learning/self_organizing_map.py)
729725
* [Sequential Minimum Optimization](machine_learning/sequential_minimum_optimization.py)
@@ -740,7 +736,6 @@
740736
* [Arc Length](maths/arc_length.py)
741737
* [Area](maths/area.py)
742738
* [Area Under Curve](maths/area_under_curve.py)
743-
* [Autocorrelation](maths/autocorrelation.py)
744739
* [Average Absolute Deviation](maths/average_absolute_deviation.py)
745740
* [Average Mean](maths/average_mean.py)
746741
* [Average Median](maths/average_median.py)
@@ -786,7 +781,6 @@
786781
* [Fibonacci](maths/fibonacci.py)
787782
* [Find Max](maths/find_max.py)
788783
* [Find Min](maths/find_min.py)
789-
* [First Fundamental Form](maths/first_fundamental_form.py)
790784
* [Floor](maths/floor.py)
791785
* [Gamma](maths/gamma.py)
792786
* [Gaussian](maths/gaussian.py)
@@ -849,7 +843,6 @@
849843
* [Square Root](maths/numerical_analysis/square_root.py)
850844
* [Weierstrass Method](maths/numerical_analysis/weierstrass_method.py)
851845
* [Odd Sieve](maths/odd_sieve.py)
852-
* [Padovan Sequence](maths/padovan_sequence.py)
853846
* [Pell Number](maths/pell_number.py)
854847
* [Perfect Cube](maths/perfect_cube.py)
855848
* [Perfect Number](maths/perfect_number.py)
@@ -879,8 +872,6 @@
879872
* [Reverse Factorial Recursive](maths/reverse_factorial_recursive.py)
880873
* [Segmented Sieve](maths/segmented_sieve.py)
881874
* Series
882-
* [Alternate Harmonic Series](maths/series/alternate_harmonic_series.py)
883-
* [Alternating Harmonic Series](maths/series/alternating_harmonic_series.py)
884875
* [Arithmetic](maths/series/arithmetic.py)
885876
* [Geometric](maths/series/geometric.py)
886877
* [Geometric Series](maths/series/geometric_series.py)
@@ -920,7 +911,6 @@
920911
* [Polygonal Numbers](maths/special_numbers/polygonal_numbers.py)
921912
* [Pronic Number](maths/special_numbers/pronic_number.py)
922913
* [Proth Number](maths/special_numbers/proth_number.py)
923-
* [Spy Number](maths/special_numbers/spy_number.py)
924914
* [Triangular Numbers](maths/special_numbers/triangular_numbers.py)
925915
* [Trimorphic Number](maths/special_numbers/trimorphic_number.py)
926916
* [Ugly Numbers](maths/special_numbers/ugly_numbers.py)

machine_learning/mab.py

Lines changed: 67 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- There are N arms, each with a different probability of giving a reward.
1010
- The agent must learn to choose the best arm to pull in order to maximize its reward.
1111
12-
Here there are 3 optimising strategies have been implemented:
12+
Here 3 optimising strategies have been implemented:
1313
- Epsilon-Greedy
1414
- Upper Confidence Bound (UCB)
1515
- Thompson Sampling
@@ -41,6 +41,11 @@ def __init__(self, probabilities: list[float]) -> None:
4141
4242
Args:
4343
probabilities: List of probabilities for each arm.
44+
45+
Example:
46+
>>> bandit = Bandit([0.1, 0.5, 0.9])
47+
>>> bandit.num_arms
48+
3
4449
"""
4550
self.probabilities = probabilities
4651
self.num_arms = len(probabilities)
@@ -127,7 +132,7 @@ def select_arm(self) -> int:
127132
rng = np.random.default_rng()
128133

129134
if rng.random() < self.epsilon:
130-
return rng.integers(self.num_arms)
135+
return int(rng.integers(self.num_arms))
131136
else:
132137
return int(np.argmax(self.values))
133138

@@ -274,7 +279,7 @@ def update(self, arm_index: int, reward: int) -> None:
274279
# Random strategy (full exploration)
275280
class RandomStrategy(Strategy):
276281
"""
277-
A class for choosing totally random at each round to give
282+
A class for choosing an arm uniformly at random at each round to give
278283
a better comparison with the other optimised strategies.
279284
"""
280285

@@ -297,10 +302,10 @@ def select_arm(self) -> int:
297302
Example:
298303
>>> strategy = RandomStrategy(num_arms=3)
299304
>>> 0 <= strategy.select_arm() < 3
300-
np.True_
305+
True
301306
"""
302307
rng = np.random.default_rng()
303-
return rng.integers(self.num_arms)
308+
return int(rng.integers(self.num_arms))
304309

305310
def update(self, arm_index: int, reward: int) -> None:
306311
"""
@@ -371,7 +376,62 @@ def update(self, arm_index: int, reward: int) -> None:
371376

372377
def test_mab_strategies() -> None:
373378
"""
374-
Test the MAB strategies.
379+
Deterministic behavioural tests for the MAB strategies.
380+
381+
These checks feed each strategy a fixed sequence of rewards and assert
382+
on the resulting internal state and arm selection, so a regression in
383+
the update/select logic will fail the suite instead of only being
384+
visible in the (stochastic) plotted demo.
385+
"""
386+
num_arms = 3
387+
388+
# After repeatedly rewarding arm 2, a purely greedy strategy must
389+
# settle on arm 2.
390+
greedy = GreedyStrategy(num_arms=num_arms)
391+
for _ in range(10):
392+
greedy.update(2, 1)
393+
greedy.update(0, 0)
394+
greedy.update(1, 0)
395+
assert greedy.select_arm() == 2
396+
397+
# Epsilon-Greedy with epsilon=0 behaves like the greedy strategy.
398+
epsilon_greedy = EpsilonGreedy(epsilon=0.0, num_arms=num_arms)
399+
for _ in range(10):
400+
epsilon_greedy.update(1, 1)
401+
epsilon_greedy.update(0, 0)
402+
epsilon_greedy.update(2, 0)
403+
assert epsilon_greedy.select_arm() == 1
404+
405+
# UCB must exhaustively try every arm once before repeating any of them.
406+
ucb = UCB(num_arms=num_arms)
407+
first_round_arms = set()
408+
for _ in range(num_arms):
409+
arm = ucb.select_arm()
410+
first_round_arms.add(arm)
411+
ucb.update(arm, 1)
412+
assert first_round_arms == set(range(num_arms))
413+
414+
# Thompson Sampling should heavily favor an arm with only successes
415+
# over arms with only failures.
416+
thompson = ThompsonSampling(num_arms=num_arms)
417+
for _ in range(20):
418+
thompson.update(0, 1)
419+
thompson.update(1, 0)
420+
thompson.update(2, 0)
421+
selections = [thompson.select_arm() for _ in range(50)]
422+
assert selections.count(0) > len(selections) // 2
423+
424+
# RandomStrategy.update is a no-op and select_arm always returns a
425+
# valid arm index.
426+
random_strategy = RandomStrategy(num_arms=num_arms)
427+
random_strategy.update(0, 1)
428+
assert 0 <= random_strategy.select_arm() < num_arms
429+
430+
431+
def demo_mab_strategies() -> None:
432+
"""
433+
Run a stochastic simulation of the MAB strategies and plot their
434+
cumulative reward over time for visual comparison.
375435
"""
376436
# Simulation
377437
num_arms = 4
@@ -418,3 +478,4 @@ def test_mab_strategies() -> None:
418478

419479
doctest.testmod()
420480
test_mab_strategies()
481+
demo_mab_strategies()

0 commit comments

Comments
 (0)