Example #1
0
    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
Example #2
0
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
Example #3
0
    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
Example #4
0
    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()