0

I am learning a Bayesian A/B test course by myself. However in the following code, it has a Class Object within some functions. For the following code:bandits = [Bandit(p) for p in BANDIT_PROBABILITIES].

I know it applies 0.2,0.5 and 0.75 to the Bandit Class object, however what the outputs for the statement? Does it from the function: def pull(self) or def sample(self) in this Class, since both of them return some values in the Bandit Class. By understanding that, then I can know what the b loops though later in this code.

Any reference link or article is also appreciated. thanks

import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import beta


NUM_TRIALS = 2000
BANDIT_PROBABILITIES=[0.2,0.5,0.75]

class Bandit(object):
  def __init__(self, p): #p=winning
    self.p = p
    self.a = 1
    self.b = 1

  def pull(self):
    return np.random.random() < self.p

  def sample(self):
    return np.random.beta(self.a, self.b)

  def update(self, x):
    self.a =self.a+ x
    self.b =self.b+ 1 - x  #x is 0 or 1


def plot(bandits, trial):
  x = np.linspace(0, 1, 200)
  for b in bandits:
    y = beta.pdf(x, b.a, b.b)
    plt.plot(x, y, label="real p: %.4f" % b.p)
  plt.title("Bandit distributions after %s trials" % trial)
  plt.legend()
  plt.show()


def experiment():
  bandits = [Bandit(p) for p in BANDIT_PROBABILITIES]

  sample_points = [5,10,20,50,100,200,500,1000,1500,1999]
  for i in range(NUM_TRIALS):

    # take a sample from each bandit
    bestb = None
    maxsample = -1
    allsamples = [] # let's collect these just to print for debugging
    for b in bandits:
      sample = b.sample()
      allsamples.append("%.4f" % sample)
      if sample > maxsample:
        maxsample = sample
        bestb = b
    if i in sample_points:
      print("current samples: %s" % allsamples)
      plot(bandits, i)

    # pull the arm for the bandit with the largest sample
    x = bestb.pull()

    # update the distribution for the bandit whose arm we just pulled
    bestb.update(x)


if __name__ == "__main__":
  experiment()
petezurich
  • 9,280
  • 9
  • 43
  • 57
Rowling
  • 213
  • 1
  • 8
  • 20
  • 1
    "however what the outputs for the statement? " I'm not sure I understand your question, but that is a list-comprehension. Have you tried checking for yourself? It's going to be a list of `Bandit` objects. – juanpa.arrivillaga Oct 11 '18 at 06:46

3 Answers3

1

It return 3 object of Bandit class with initialisation with the different p probability.

Furthur down the line, sample = b.sample(), it will return the result of np.random.beta(self.a, self.b)

infinex
  • 11
  • 1
1

bandits = [Bandit(p) for p in BANDIT_PROBABILITIES] is a list comprehension. BANDIT_PROBABILITIES is a list of 3 float values so bandits is a list containing 3 objects of Bandit class created with 3 different values of p attribute:

print(type(bandit[0]))
print(bandits[0].p)

Output:

<class '__main__.Bandit'>
0.2

Only method __init__() was called so far to initiate objects. Every bandit in the list bandits has attributes p, a, b and methods pull(), sample(), update().

0

The statement

bandits = [Bandit(p) for p in BANDIT_PROBABILITIES]

is called list comprehension and it's basically a way to create a list. For example you can see this for a reference (there a lot of sources).

The basic idea is that uses a mechanism like:

[ expression for item in list if conditional ]

So, in your case BANDIT_PROBABILITIES is the list above containing 3 elements and the expression your list comprehension evaluates it's just instances of your Bandit class. In other words you will end up with a list containing 3 instances of your Bandit class each initialized with a different argument.

The pull(self) or sample(self) are just function belonging to the class and they are not used in this statement. They could be used if you had a call from your __init__(self, p) member function.

To make it simple, when you create a class instance only the __init__ is used. If inside the latter function are other calls to member functions like sample() etc then they are called also but the output in your list comprehension would be still a list with 3 Bandit instances.

Maybe you should check this answer (and any other since there are plenty of sources) about __init__ functionality.

Eypros
  • 5,370
  • 6
  • 42
  • 75
  • So, when we first call bandits = [Bandit(p) for p in BANDIT_PROBABILITIES], in fact only def __init__(self, p) is running, the rest of the function won't run until i call them later, like b.sample. – Rowling Oct 11 '18 at 07:06