def update(self, design, response): r""" Update the posterior :math:`p(\theta | y_\text{obs}(t), d^*)` for all discretized values of :math:`\theta`. .. math:: p(\theta | y_\text{obs}(t), d^*) = \frac{ p( y_\text{obs}(t) | \theta, d^*) p_t(\theta) } { p( y_\text{obs}(t) | d^* ) } Parameters ---------- design Design vector for given response response Any kinds of observed response """ if not isinstance(design, pd.Series): design = pd.Series(design, index=self.task.designs) idx_design = get_nearest_grid_index(design, self.grid_design) idx_response = get_nearest_grid_index(pd.Series(response), self.grid_response) self.log_post += self.log_lik[idx_design, :, idx_response].flatten() self.log_post -= logsumexp(self.log_post) if self.lambda_et: self.eligibility_trace *= self.lambda_et self.eligibility_trace[idx_design] += 1 self.flag_update_mutual_info = True
def test_get_nearest_grid_index(): X = np.array([ [1, 2, 3, 4], [2, 3, 4, 1], [3, 4, 1, 2], [4, 1, 2, 3], ]) x0 = X[0].reshape(1, -1) x1 = X[1].reshape(1, -1) x2 = X[2].reshape(1, -1) x3 = X[3].reshape(1, -1) assert get_nearest_grid_index(x0, X) == 0 assert get_nearest_grid_index(x1, X) == 1 assert get_nearest_grid_index(x2, X) == 2 assert get_nearest_grid_index(x3, X) == 3
def get_design(self, kind='optimal'): r"""Choose a design with a given type. - :code:`optimal`: an optimal design :math:`d^*` that maximizes the information for fitting model parameters. - :code:`staircase`: Choose the stimulus :math:`s` as below: .. math:: x_t = \begin{cases} x_{t-1} - \Delta & \text{if } y_{t-1} = 1 \\ x_{t-1} + 2 \Delta & \text{if } y_{t-1} = 0 \end{cases} where :math:`\Delta` is determined by ``d_step`` which is the step-size change on the grid index. - :code:`random`: a design randomly chosen. Parameters ---------- kind : {'optimal', 'staircase', 'random'}, optional Type of a design to choose Returns ------- design A chosen design vector """ assert kind in {'optimal', 'staircase', 'random'} self._update_mutual_info() if kind == 'optimal': ret = self.grid_design.iloc[np.argmax(self.mutual_info)] elif kind == 'staircase': if self.y_obs_prev == 1: idx = max(0, self.idx_opt - self.d_step) else: idx = min( len(self.grid_design) - 1, self.idx_opt + (self.d_step * 2)) ret = self.grid_design.iloc[np.int(idx)] elif kind == 'random': idx = np.random.randint(self.n_d) ret = self.grid_design.iloc[idx] else: raise RuntimeError('An invalid kind of design: "{}".'.format(type)) self.idx_opt = get_nearest_grid_index(ret.values, self.grid_design.values) return ret
def update(self, design, response): r""" Update the posterior probabilities :math:`p(\theta | y, d^*)` for all discretized values of :math:`\theta`. .. math:: p(\theta | y, d^*) \sim p( y | \theta, d^*) p(\theta) .. code-block:: python # Given design and resposne as `design` and `response`, # the engine can update probability with the following line: engine.update(design, response) Also, it can takes multiple observations for updating posterior probabilities. Multiple pairs of design and response should be given as a list of designs and a list of responses, into :code:`design` and :code:`response` argument, respectively. .. math:: \begin{aligned} p\big(\theta | y_1, \ldots, y_n, d_1^*, \ldots, d_n^*\big) &\sim p\big( y_1, \ldots, y_n | \theta, d_1^*, \ldots, d_n^* \big) p(\theta) \\ &= p(y_1 | \theta, d_1^*) \cdot \ldots \cdot p(y_n | \theta, d_n^*) p(\theta) \end{aligned} .. code-block:: python # Given a list of designs and corresponding responses as below: designs = [design1, design2, design3] responses = [response1, response2, response3] # the engine can update with multiple observations: engine.update(designs, responses) Parameters ---------- design : dict or :code:`pandas.Series` or list of designs Design vector for given response response : dict or :code:`pandas.Series` or list of responses Any kinds of observed response """ if isinstance(design, list) != isinstance(response, list): raise ValueError( 'The number of observations (pairs of design and response) ' 'should be matched in the design and response arguments.') if isinstance(design, list) and isinstance(response, list): if len(design) != len(response): raise ValueError( 'The length of designs and responses should be the same.') _designs = [ pd.Series(d, index=self.task.designs, dtype=self.dtype) for d in design ] _responses = [ pd.Series(r, index=self.task.responses, dtype=self.dtype) for r in response ] else: _designs = [ pd.Series(design, index=self.task.designs, dtype=self.dtype) ] _responses = [ pd.Series(response, index=self.task.responses, dtype=self.dtype) ] for d, r in zip(_designs, _responses): i_d = get_nearest_grid_index(d.values, self.grid_design.values) i_y = get_nearest_grid_index(r.values, self.grid_response.values) self.log_post += self.log_lik[i_d, :, i_y] self.log_post -= logsumexp(self.log_post) self._marg_log_lik = None self._ent_marg = None self._ent_cond = None self._mutual_info = None self._update_mutual_info()