-
Notifications
You must be signed in to change notification settings - Fork 0
Added Thompson Sampling Strategy for Gaussian with Unknown Mean + Variance #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| from strategy.strategy import * | ||
| from strategy.thompson_sampling_strategy import * |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| """ | ||
|
|
||
| ThompsonSampling Strategy | ||
|
|
||
| """ | ||
|
|
||
| import numpy as np | ||
| import scipy.stats | ||
| from copy import deepcopy | ||
| from strategy.strategy import Strategy | ||
|
|
||
|
|
||
| class ThompsonSampling(Strategy): | ||
| """ Thompson Sampling Strategy | ||
|
|
||
| Args: | ||
| bandit (Bandit): bandit | ||
| **kwargs : prior parameters | ||
|
|
||
| Attributes: | ||
| num_arms (int) | ||
| posterior_params (list of list): posterior parameters | ||
| estimated_arm_means (ndarray): posterior predictive mean | ||
| estimated_arm_sds (ndarray): posterior predictive standard deviation | ||
| """ | ||
| def __init__(self, bandit, **kwargs): | ||
| self.bandit = bandit | ||
| self.num_arms = bandit.num_arms | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'd like to make this into a property to keep style consistent. |
||
| self.prior_params = [deepcopy(kwargs) for _ in range(self.num_arms)] | ||
| self.posterior_params = deepcopy(self.prior_params) | ||
|
|
||
| def fit(self, iterations, restart=False): | ||
| if restart == True: | ||
| self._restart() | ||
|
|
||
| index_arms_pulled = [None] * iterations | ||
| observed_rewards = [None] * iterations | ||
| estimated_arm_means = [None] * iterations | ||
| estimated_arm_sds = [None] * iterations | ||
| for i in range(iterations): | ||
| index_arms_pulled[i] = self._choose_arm() | ||
| observed_rewards[i] = self.pull_arm(index_arms_pulled[i]) | ||
| self._update_posterior(index_arms_pulled[i], observed_rewards[i]) | ||
| estimated_arm_means[i] = self.estimated_arm_means | ||
| estimated_arm_sds[i] = self.estimated_arm_sds | ||
|
|
||
| out = { | ||
| 'rewards': np.array(observed_rewards), | ||
| 'arms_pulled': np.array(index_arms_pulled), | ||
| 'estimated_arm_means': np.array(estimated_arm_means), | ||
| 'estimated_arm_sds': np.array(estimated_arm_sds) | ||
| } | ||
| return out | ||
|
|
||
| def _restart(self): | ||
| self.posterior_params = deepcopy(self.prior_params) | ||
|
|
||
| def _choose_arm(self): | ||
| samples = [self._sample(**params) for params in self.posterior_params] | ||
| return np.argmax(samples) | ||
|
|
||
| def pull_arm(self, arm_index): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just realized this isn't private. Can you change that (or I can do it). Users shouldn't be interacting with bandit directly except through .fit()
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good to me. I can add that tonight |
||
| return self.bandit.pull_arm(arm_index) | ||
|
|
||
| def _sample(self, **kwargs): | ||
| raise NotImplementedError | ||
|
|
||
| def _update_posterior(self, arm_index, observed_reward): | ||
| raise NotImplementedError | ||
|
|
||
| @property | ||
| def estimated_arm_means(self): | ||
| raise NotImplementedError | ||
|
|
||
| @property | ||
| def estimated_arm_sds(self): | ||
| raise NotImplementedError | ||
|
|
||
|
|
||
| class ThompsonBernoulli(ThompsonSampling): | ||
| def __init__(self, bandit, alpha_prior, beta_prior): | ||
| ThompsonSampling.__init__(self, bandit, alpha=alpha_prior, beta=beta_prior) | ||
|
|
||
| def _sample(self, alpha, beta): | ||
| return scipy.stats.beta.rvs(alpha, beta, size=1) | ||
|
|
||
| def _update_posterior(self, arm_index, observed_reward): | ||
| if observed_reward == 1: | ||
| self.posterior_params[arm_index]['alpha'] += 1 | ||
| else: | ||
| self.posterior_params[arm_index]['beta'] += 1 | ||
|
|
||
| @property | ||
| def estimated_arm_means(self): | ||
| mean = lambda params: \ | ||
| params['alpha'] / (params['alpha'] + params['beta']) | ||
| return np.array([ mean(params) for params in self.posterior_params]) | ||
| @property | ||
| def estimated_arm_sds(self): | ||
| sd = lambda params: np.sqrt( | ||
| (params['alpha'] / (params['alpha'] + params['beta'])) * | ||
| (1 - params['alpha'] / (params['alpha'] + params['beta'])) | ||
| ) | ||
| return np.array([sd(params) for params in self.posterior_params]) | ||
|
|
||
| class ThompsonGaussianKnownSigma(ThompsonSampling): | ||
| def __init__(self, bandit, sigma, mu_prior, sigma_prior, | ||
| memory_multiplier=0.9): | ||
| ThompsonSampling.__init__(self, bandit, mu=mu_prior, | ||
| sigma2=sigma_prior ** 2) | ||
| self.sigma2 = sigma ** 2 | ||
| self.sufficient_statistics = [{'n': 0, 'xsum': 0} for _ in range(self.num_arms)] | ||
| self.memory_multiplier = memory_multiplier | ||
|
|
||
| def _sample(self, mu, sigma2): | ||
| return np.random.normal(loc=mu, scale=np.sqrt(sigma2)) | ||
|
|
||
| def _update_posterior(self, arm_index, observed_reward): | ||
| sigma2 = self.sigma2 | ||
|
|
||
| for index in range(self.num_arms): | ||
| old_n = self.sufficient_statistics[index]['n'] | ||
| old_xsum = self.sufficient_statistics[index]['xsum'] | ||
| mu_prior = self.prior_params[index]['mu'] | ||
| sigma2_prior = self.prior_params[index]['sigma2'] | ||
|
|
||
| if index == arm_index: | ||
| new_n = self.memory_multiplier * old_n + 1 | ||
| new_xsum = self.memory_multiplier * old_xsum + observed_reward | ||
| else: | ||
| new_n = self.memory_multiplier * old_n | ||
| new_xsum = self.memory_multiplier * old_xsum | ||
|
|
||
| new_sigma2_posterior = 1 / (1 / sigma2_prior + new_n / sigma2) | ||
| new_mu_posterior = (mu_prior / sigma2_prior + new_xsum / sigma2) * new_sigma2_posterior | ||
|
|
||
| self.sufficient_statistics[index]['n'] = new_n | ||
| self.sufficient_statistics[index]['xsum'] = new_xsum | ||
| self.posterior_params[index]['mu'] = new_mu_posterior | ||
| self.posterior_params[index]['sigma2'] = new_sigma2_posterior | ||
|
|
||
| @property | ||
| def estimated_arm_means(self): | ||
| return np.array([params['mu'] for params in self.posterior_params]) | ||
|
|
||
| @property | ||
| def estimated_arm_sds(self): | ||
| return np.array([self.sigma2 + params['sigma2'] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is it the sum of these two?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this was in the first commit and fixed in the second. Don't forget there's a 'view all changes' (so you don't have to look at commits one-by-one) |
||
| for params in self.posterior_params]) | ||
|
|
||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's the posterior predictive mean; Isn't it just the posterior mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think they are the same in this case, since the posterior mean of the mean is also the posterior predictive mean. And we want to maximize the posterior predictive mean.
The variance/standard deviation is another story. The posterior variance of the mean is smaller than the posterior predictive variance
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ohhh yea i see. yea they were same since wes & i only considered returning the mean. hmm i dunno what's best here