def setUp(self):
        np.random.seed(30732)

        self.n_nodes = 3
        self.n_realizations = 2

        self.decay = np.random.rand()

        self.timestamps_list = [
            [np.cumsum(np.random.random(np.random.randint(3, 7)))
             for _ in range(self.n_nodes)]
            for _ in range(self.n_realizations)]

        self.end_time = 10

        self.baseline = np.random.rand(self.n_nodes)
        self.adjacency = np.random.rand(self.n_nodes, self.n_nodes)
        self.coeffs = np.hstack((self.baseline, self.adjacency.ravel()))

        self.realization = 0
        self.model = ModelHawkesExpKernLogLik(self.decay)
        self.model.fit(self.timestamps_list[self.realization],
                       end_times=self.end_time)

        self.model_list = ModelHawkesExpKernLogLik(self.decay)
        self.model_list.fit(self.timestamps_list)
 def _model_init(self, approx_type, epsilon, decay_neg, gamma):
     # Init the model for the Hawkes parameters
     self.hawkes_model = ModelHawkesExpKernLogLik(self.decay,
                                                  self.n_threads)
     # Set the type of approximation
     self.approx_type = approx_type
     if self.approx_type == 'findiff':
         # Epsilon used for the fin.-diff. approx. of the noise gradient
         self.epsilon = epsilon
         if self.epsilon <= 0:
             raise ValueError('`epsilon` must be positive')
     elif self.approx_type == 'smooth':
         # Exponential Decay in negative time, used for the smooth appprox
         # of the noise gradient
         if decay_neg <= 0:
             raise ValueError('`decay_neg` must be positive')
         # Rate of transition between negative and positive exponenetial
         # rate used for the smooth appprox of the noise gradient
         if gamma <= 0:
             raise ValueError('`gamma` must be positive')
         self.smooth_noise_model = SyncNoiseModelHawkesSmoothSigmoidExpKern(
             decay=self.decay,
             decay_neg=decay_neg,
             gamma=gamma,
             approx=SMOOTH_MODEL_APPROX_CUTOFF)
     else:
         raise ValueError(
             '`approx_type` is `{:s}`, but must be in: {:s}'.format(
                 str(approx_type), ', '.join(ALLOWED_APPROX_TYPE)))
 def _fit_hawkes_model(self, events, end_time):
     try:
         self.hawkes_model.fit(events, end_time)
     except RuntimeError:
         # Fix tick bug in version 0.4.0.0
         self.hawkes_model = ModelHawkesExpKernLogLik(
             self.decay, self.n_threads)
         self.hawkes_model.fit(events, end_time)
    def test_model_hawkes_loglik_incremental_fit(self):
        """...Test that multiple events list for ModelHawkesExpKernLogLik
        are correctly handle with incremental_fit
        """
        model_incremental_fit = ModelHawkesExpKernLogLik(decay=self.decay)

        for timestamps in self.timestamps_list:
            model_incremental_fit.incremental_fit(timestamps)

        self.assertEqual(model_incremental_fit.loss(self.coeffs),
                         self.model_list.loss(self.coeffs))
    def test_model_hawkes_loglik_change_decays(self):
        """...Test that loss is still consistent after decays modification in
        ModelHawkesExpKernLogLik
        """
        decay = np.random.rand()

        self.assertNotEqual(decay, self.decay)

        model_change_decay = ModelHawkesExpKernLogLik(decay=decay)
        model_change_decay.fit(self.timestamps_list)
        loss_old_decay = model_change_decay.loss(self.coeffs)

        model_change_decay.decay = self.decay

        self.assertNotEqual(loss_old_decay,
                            model_change_decay.loss(self.coeffs))

        self.assertEqual(self.model_list.loss(self.coeffs),
                         model_change_decay.loss(self.coeffs))
Exemplo n.º 6
0
    def test_hawkes_list_n_threads(self):
        """...Test that the number of used threads is as expected
        """
        model_list = ModelHawkesExpKernLogLik(decay=self.decay, n_threads=1)

        # 0 threads yet as no data has been given
        self.assertEqual(model_list._model.get_n_threads(), 0)

        # Now that it has been fitted it equals
        # min(n_threads, n_nodes * n_realizations)
        model_list.fit(self.timestamps_list)
        self.assertEqual(model_list._model.get_n_threads(), 1)

        model_list.n_threads = 8
        self.assertEqual(model_list._model.get_n_threads(), 6)

        realization_2_nodes = [np.array([3., 4.]), np.array([3.5, 6.])]

        model_list.fit(realization_2_nodes)
        self.assertEqual(model_list._model.get_n_threads(), 2)

        model_list.n_threads = 1
        self.assertEqual(model_list._model.get_n_threads(), 1)
class ModelHawkesExpKernCondLogLikSyncNoise(ModelHawkesCondLogLikSyncNoise):
    """
    Model a multivariate Hawkes process with Exponential kernels and
    Synchronization noise using the negative log-likelihood loss function. This
    object provides an interface to compute the value of the loss as well as
    its gradient.

    The parameters `coeffs` of the model are (`noise_coeffs`, `hawkes_coeffs`),
    where `noise_coeffs` corresponds to the noise assignment in each dimension
    and `hawkes_coeffs` corresponds to the Hawkes process parameters with first
    the baseline in each dimension, then the kernel weights (same convention as
    in the library `tick`).
    """

    ALLOWED_APPROX_TYPE = set(['findiff', 'smooth'])

    def __init__(self,
                 decay,
                 n_threads=1,
                 approx_type='smooth',
                 epsilon=1e-3,
                 decay_neg=1000,
                 gamma=5000):
        super().__init__(decay, n_threads)
        self._model_init(approx_type, epsilon, decay_neg, gamma)

    def _model_init(self, approx_type, epsilon, decay_neg, gamma):
        # Init the model for the Hawkes parameters
        self.hawkes_model = ModelHawkesExpKernLogLik(self.decay,
                                                     self.n_threads)
        # Set the type of approximation
        self.approx_type = approx_type
        if self.approx_type == 'findiff':
            # Epsilon used for the fin.-diff. approx. of the noise gradient
            self.epsilon = epsilon
            if self.epsilon <= 0:
                raise ValueError('`epsilon` must be positive')
        elif self.approx_type == 'smooth':
            # Exponential Decay in negative time, used for the smooth appprox
            # of the noise gradient
            if decay_neg <= 0:
                raise ValueError('`decay_neg` must be positive')
            # Rate of transition between negative and positive exponenetial
            # rate used for the smooth appprox of the noise gradient
            if gamma <= 0:
                raise ValueError('`gamma` must be positive')
            self.smooth_noise_model = SyncNoiseModelHawkesSmoothSigmoidExpKern(
                decay=self.decay,
                decay_neg=decay_neg,
                gamma=gamma,
                approx=SMOOTH_MODEL_APPROX_CUTOFF)
        else:
            raise ValueError(
                '`approx_type` is `{:s}`, but must be in: {:s}'.format(
                    str(approx_type), ', '.join(ALLOWED_APPROX_TYPE)))

    def _fit_hawkes_model(self, events, end_time):
        try:
            self.hawkes_model.fit(events, end_time)
        except RuntimeError:
            # Fix tick bug in version 0.4.0.0
            self.hawkes_model = ModelHawkesExpKernLogLik(
                self.decay, self.n_threads)
            self.hawkes_model.fit(events, end_time)

    @enforce_fitted
    def _loss_noise(self, noise_coeffs, hawkes_coeffs):
        """
        Internal function used to approximate the noise gradient. Returns the
        value of the loss function at `hawkes_coeffs` with noise assignment
        `noise_coeffs`.
        """
        # Condition the data on the given noise assignment
        self._condition_events_on_noise(noise_coeffs)
        # Compute the loss
        loss = self._loss(hawkes_coeffs)
        return loss

    @enforce_fitted
    def loss(self, coeffs):
        """
        Evaluate the negative log-likelihood at parameters `coeffs`.
        """
        noise_coeffs, hawkes_coeffs = self._split_coeffs(coeffs)
        # Condition the data on the given noise assignment
        self._condition_events_on_noise(noise_coeffs)
        try:
            # Compute the loss
            loss = self._loss(hawkes_coeffs)
        except RuntimeError:
            # In case some parameters are negative
            return np.inf
        return loss

    @enforce_fitted
    def grad_noise(self, coeffs):
        """
        Return the value of the noise gradient at `coeffs`.
        """
        noise_coeffs, hawkes_coeffs = self._split_coeffs(coeffs)
        if self.approx_type == 'findiff':
            return approx_fprime(
                noise_coeffs,  # xk
                self._loss_noise,  # f
                self.epsilon,  # epsilon
                hawkes_coeffs  # *args passed to `f`
            )
        elif self.approx_type == 'smooth':
            self._condition_events_on_noise(noise_coeffs)
            self.smooth_noise_model.fit(self.fitted_events,
                                        self.fitted_end_time)
            return self.smooth_noise_model.grad(hawkes_coeffs)

    @enforce_fitted
    def grad_hawkes(self, coeffs):
        """
        Return the value of the Hawkes gradient at `coeffs`.
        """
        noise_coeffs, hawkes_coeffs = self._split_coeffs(coeffs)
        self._condition_events_on_noise(noise_coeffs)
        hawkes_grad = self.hawkes_model.grad(hawkes_coeffs)
        return hawkes_grad

    @enforce_fitted
    def grad(self, coeffs):
        """
        Compute the gradient of the negative log-likelihood at parameters
        `coeffs`.
        """
        return np.hstack((self.grad_noise(coeffs), self.grad_hawkes(coeffs)))
class Test(unittest.TestCase):
    def setUp(self):
        np.random.seed(30732)

        self.n_nodes = 3
        self.n_realizations = 2

        self.decay = np.random.rand()

        self.timestamps_list = [
            [np.cumsum(np.random.random(np.random.randint(3, 7)))
             for _ in range(self.n_nodes)]
            for _ in range(self.n_realizations)]

        self.end_time = 10

        self.baseline = np.random.rand(self.n_nodes)
        self.adjacency = np.random.rand(self.n_nodes, self.n_nodes)
        self.coeffs = np.hstack((self.baseline, self.adjacency.ravel()))

        self.realization = 0
        self.model = ModelHawkesExpKernLogLik(self.decay)
        self.model.fit(self.timestamps_list[self.realization],
                       end_times=self.end_time)

        self.model_list = ModelHawkesExpKernLogLik(self.decay)
        self.model_list.fit(self.timestamps_list)

    def test_model_hawkes_losses(self):
        """...Test that computed losses are consistent with approximated
        theoretical values
        """
        timestamps = self.timestamps_list[self.realization]

        decays = np.ones((self.n_nodes, self.n_nodes)) * self.decay
        intensities = hawkes_exp_kernel_intensities(
            self.baseline, decays, self.adjacency, timestamps)

        precision = 3
        integral_approx = hawkes_log_likelihood(
            intensities, timestamps, self.end_time, precision=precision)
        integral_approx /= self.model.n_jumps

        self.assertAlmostEqual(integral_approx, - self.model.loss(self.coeffs),
                               places=precision)

    def test_model_hawkes_loglik_multiple_events(self):
        """...Test that multiple events list for ModelHawkesExpKernLogLik
        is consistent with direct integral estimation
        """
        end_times = np.array([max(map(max, e)) for e in self.timestamps_list])
        end_times += 1.
        self.model_list.fit(self.timestamps_list, end_times=end_times)

        decays = np.ones((self.n_nodes, self.n_nodes)) * self.decay
        intensities_list = [
            hawkes_exp_kernel_intensities(self.baseline, decays,
                                          self.adjacency, timestamps)
            for timestamps in self.timestamps_list
        ]

        integral_approx = sum([hawkes_log_likelihood(intensities,
                                                     timestamps, end_time)
                               for (intensities, timestamps, end_time) in zip(
                intensities_list, self.timestamps_list,
                self.model_list.end_times
            )])

        integral_approx /= self.model_list.n_jumps
        self.assertAlmostEqual(integral_approx,
                               - self.model_list.loss(self.coeffs),
                               places=2)

    def test_model_hawkes_loglik_incremental_fit(self):
        """...Test that multiple events list for ModelHawkesExpKernLogLik
        are correctly handle with incremental_fit
        """
        model_incremental_fit = ModelHawkesExpKernLogLik(decay=self.decay)

        for timestamps in self.timestamps_list:
            model_incremental_fit.incremental_fit(timestamps)

        self.assertEqual(model_incremental_fit.loss(self.coeffs),
                         self.model_list.loss(self.coeffs))

    def test_model_hawkes_loglik_grad(self):
        """...Test that ModelHawkesExpKernLeastSq gradient is consistent
        with loss
        """
        self.assertLess(check_grad(self.model.loss, self.model.grad,
                                   self.coeffs),
                        1e-5)

    def test_model_hawkes_loglik_hessian_norm(self):
        """...Test that ModelHawkesExpKernLeastSq hessian norm is
        consistent with gradient
        """
        self.assertLess(check_grad(self.model.loss, self.model.grad,
                                   self.coeffs),
                        1e-5)

    def test_hawkesgrad_hess_norm(self):
        """...Test if grad and log likelihood are correctly computed
        """
        hessian_point = np.random.rand(self.model.n_coeffs)
        vector = np.random.rand(self.model.n_coeffs)

        hessian_norm = self.model.hessian_norm(hessian_point, vector)

        delta = 1e-7
        grad_point_minus = self.model.grad(hessian_point + delta * vector)
        grad_point_plus = self.model.grad(hessian_point - delta * vector)
        finite_diff_result = vector.dot(grad_point_minus - grad_point_plus)
        finite_diff_result /= (2 * delta)
        self.assertAlmostEqual(finite_diff_result, hessian_norm)

        hessian_result = vector.T.dot(
            self.model.hessian(hessian_point).dot(vector))
        self.assertAlmostEqual(hessian_result, hessian_norm)

    def test_model_hawkes_loglik_change_decays(self):
        """...Test that loss is still consistent after decays modification in
        ModelHawkesExpKernLogLik
        """
        decay = np.random.rand()

        self.assertNotEqual(decay, self.decay)

        model_change_decay = ModelHawkesExpKernLogLik(decay=decay)
        model_change_decay.fit(self.timestamps_list)
        loss_old_decay = model_change_decay.loss(self.coeffs)

        model_change_decay.decay = self.decay

        self.assertNotEqual(loss_old_decay,
                            model_change_decay.loss(self.coeffs))

        self.assertEqual(self.model_list.loss(self.coeffs),
                         model_change_decay.loss(self.coeffs))

    def test_hawkes_list_n_threads(self):
        """...Test that the number of used threads is as expected
        """
        model_list = ModelHawkesExpKernLogLik(decay=self.decay,
                                              n_threads=1)

        # 0 threads yet as no data has been given
        self.assertEqual(model_list._model.get_n_threads(), 0)

        # Now that it has been fitted it equals
        # min(n_threads, n_nodes * n_realizations)
        model_list.fit(self.timestamps_list)
        self.assertEqual(model_list._model.get_n_threads(), 1)

        model_list.n_threads = 8
        self.assertEqual(model_list._model.get_n_threads(), 6)

        realization_2_nodes = [np.array([3., 4.]), np.array([3.5, 6.])]

        model_list.fit(realization_2_nodes)
        self.assertEqual(model_list._model.get_n_threads(), 2)

        model_list.n_threads = 1
        self.assertEqual(model_list._model.get_n_threads(), 1)

    def test_ModelHawkesExpKernLogLik_hessian(self):
        """...Numerical consistency check of hessian for Hawkes loglik
        """
        for model in [self.model]:
            hessian = model.hessian(self.coeffs).todense()
            # Check that hessian is equal to its transpose
            np.testing.assert_array_almost_equal(hessian, hessian.T,
                                                 decimal=10)

            # Check that for all dimension hessian row is consistent
            # with its corresponding gradient coordinate.
            for i in range(model.n_coeffs):
                def g_i(x):
                    return model.grad(x)[i]

                def h_i(x):
                    h = model.hessian(x).todense()
                    return np.asarray(h)[i, :]

                self.assertLess(check_grad(g_i, h_i, self.coeffs), 1e-5)
Exemplo n.º 9
0
 def test_ModelHawkesExpKernLogLik_refit(self):
     model = ModelHawkesExpKernLogLik(decay=1.0)
     model.fit(events=[np.array([0.0, 50.0])], end_times=100.0)
     model.fit(events=[np.array([0.0, 500.0])], end_times=1000.0)
     model.fit(events=[np.array([0.0, 50.0])], end_times=100.0)