def test_fit_riemannian_se2(self): init = (self.y_se2[0], gs.zeros_like(self.y_se2[0])) gr = GeodesicRegression( self.se2, metric=self.metric_se2, center_X=False, method="riemannian", max_iter=50, init_step_size=0.1, verbose=True, initialization=init, ) gr.fit(self.X_se2, self.y_se2, compute_training_score=True) intercept_hat, coef_hat = gr.intercept_, gr.coef_ training_score = gr.training_score_ self.assertAllClose(intercept_hat.shape, self.shape_se2) self.assertAllClose(coef_hat.shape, self.shape_se2) self.assertAllClose(training_score, 1.0, atol=1e-4) self.assertAllClose(intercept_hat, self.intercept_se2_true, atol=1e-4) tangent_vec_of_transport = self.se2.metric.log( self.intercept_se2_true, base_point=intercept_hat) transported_coef_hat = self.se2.metric.parallel_transport( tangent_vec=coef_hat, base_point=intercept_hat, direction=tangent_vec_of_transport, ) self.assertAllClose(transported_coef_hat, self.coef_se2_true, atol=0.6)
def test_fit_extrinsic_se2(self): gr = GeodesicRegression( self.se2, metric=self.metric_se2, center_X=False, method="extrinsic", max_iter=50, init_step_size=0.1, verbose=True, initialization="warm_start", ) gr.fit(self.X_se2, self.y_se2, compute_training_score=True) intercept_hat, coef_hat = gr.intercept_, gr.coef_ training_score = gr.training_score_ self.assertAllClose(intercept_hat.shape, self.shape_se2) self.assertAllClose(coef_hat.shape, self.shape_se2) self.assertTrue(gs.isclose(training_score, 1.0)) self.assertAllClose(intercept_hat, self.intercept_se2_true, atol=1e-4) tangent_vec_of_transport = self.se2.metric.log( self.intercept_se2_true, base_point=intercept_hat) transported_coef_hat = self.se2.metric.parallel_transport( tangent_vec=coef_hat, base_point=intercept_hat, direction=tangent_vec_of_transport, ) self.assertAllClose(transported_coef_hat, self.coef_se2_true, atol=0.6)
def test_fit_riemannian_hypersphere(self): gr = GeodesicRegression( self.sphere, metric=self.sphere.metric, center_X=False, method="riemannian", max_iter=50, init_step_size=0.1, verbose=True, ) gr.fit(self.X_sphere, self.y_sphere, compute_training_score=True) intercept_hat, coef_hat = gr.intercept_, gr.coef_ training_score = gr.training_score_ self.assertAllClose(intercept_hat.shape, self.shape_sphere) self.assertAllClose(coef_hat.shape, self.shape_sphere) self.assertAllClose(training_score, 1.0, atol=0.1) self.assertAllClose(intercept_hat, self.intercept_sphere_true, atol=1e-2) tangent_vec_of_transport = self.sphere.metric.log( self.intercept_sphere_true, base_point=intercept_hat) transported_coef_hat = self.sphere.metric.parallel_transport( tangent_vec=coef_hat, base_point=intercept_hat, direction=tangent_vec_of_transport, ) self.assertAllClose(transported_coef_hat, self.coef_sphere_true, atol=0.6)
def test_fit_extrinsic_euclidean(self): gr = GeodesicRegression( self.eucl, metric=self.eucl.metric, center_X=False, method="extrinsic", max_iter=50, init_step_size=0.1, verbose=True, initialization="random", regularization=0.9, ) gr.fit(self.X_eucl, self.y_eucl, compute_training_score=True) training_score = gr.training_score_ intercept_hat, coef_hat = gr.intercept_, gr.coef_ self.assertAllClose(intercept_hat.shape, self.shape_eucl) self.assertAllClose(coef_hat.shape, self.shape_eucl) self.assertAllClose(training_score, 1.0, atol=500 * gs.atol) self.assertAllClose(intercept_hat, self.intercept_eucl_true) tangent_vec_of_transport = self.eucl.metric.log( self.intercept_eucl_true, base_point=intercept_hat) transported_coef_hat = self.eucl.metric.parallel_transport( tangent_vec=coef_hat, base_point=intercept_hat, direction=tangent_vec_of_transport, ) self.assertAllClose(transported_coef_hat, self.coef_eucl_true)
def main(): r"""Compute a geodesic regression on Grassmann manifold (2, 3). The generative model of the data is: :math:`Z = Exp_{\beta_0}(\beta_1.X)` and :math:`Y = Exp_Z(\epsilon)` where: - :math:`Exp` denotes the Riemannian exponential, - :math:`\beta_0` is called the intercept, - :math:`\beta_1` is called the coefficient, - :math:`\epsilon \sim N(0, 1)` is a standard Gaussian noise, - :math:`X` is called the input, :math:`Y` is called the y. """ # Generate data n_samples = 10 data = gs.random.rand(n_samples) data -= gs.mean(data) intercept = SPACE.random_uniform() beta = SPACE.to_tangent(GeneralLinear(3).random_point(), intercept) target = METRIC.exp(tangent_vec=gs.einsum("...,jk->...jk", data, beta), base_point=intercept) # Fit geodesic regression gr = GeodesicRegression( SPACE, metric=METRIC, center_X=False, method="riemannian", max_iter=50, init_step_size=0.1, verbose=True, ) gr.fit(data, target, compute_training_score=True) intercept_hat, beta_hat = gr.intercept_, gr.coef_ # Measure Mean Squared Error mse_intercept = METRIC.squared_dist(intercept_hat, intercept) mse_beta = METRIC.squared_norm( METRIC.parallel_transport(beta_hat, METRIC.log( intercept_hat, intercept), intercept_hat) - beta, intercept, ) # Measure goodness of fit r2_hat = gr.training_score_ print(f"MSE on the intercept: {mse_intercept:.2e}") print(f"MSE on the initial velocity beta: {mse_beta:.2e}") print(f"Determination coefficient: R^2={r2_hat:.2f}")
def test_loss_se2(self): """Test that the loss is 0 at the true parameters.""" gr = GeodesicRegression( self.se2, metric=self.metric_se2, center_X=False, method="extrinsic", max_iter=50, init_step_size=0.1, verbose=True, ) loss = gr._loss(self.X_se2, self.y_se2, self.param_se2_true, self.shape_se2) self.assertAllClose(loss.shape, ()) self.assertTrue(gs.isclose(loss, 0.0))
def test_loss_hypersphere(self): """Test that the loss is 0 at the true parameters.""" gr = GeodesicRegression( self.sphere, metric=self.sphere.metric, center_X=False, method="extrinsic", verbose=True, max_iter=50, learning_rate=0.1, ) loss = gr._loss( self.X_sphere, self.y_sphere, self.param_sphere_true, self.shape_sphere, ) self.assertAllClose(loss.shape, ()) self.assertTrue(gs.isclose(loss, 0.0))
def test_loss_minimization_extrinsic_se2(self): gr = GeodesicRegression( self.se2, metric=self.metric_se2, center_X=False, method="extrinsic", max_iter=50, init_step_size=0.1, verbose=True, ) def loss_of_param(param): return gr._loss(self.X_se2, self.y_se2, param, self.shape_se2) objective_with_grad = gs.autodiff.value_and_grad(loss_of_param, to_numpy=True) res = minimize( objective_with_grad, gs.flatten(self.param_se2_guess), method="CG", jac=True, options={ "disp": True, "maxiter": 50 }, ) self.assertAllClose(gs.array(res.x).shape, (18, )) self.assertAllClose(res.fun, 0.0, atol=1e-6) # Cast required because minimization happens in scipy in float64 param_hat = gs.cast(gs.array(res.x), self.param_se2_true.dtype) intercept_hat, coef_hat = gs.split(param_hat, 2) intercept_hat = gs.reshape(intercept_hat, self.shape_se2) coef_hat = gs.reshape(coef_hat, self.shape_se2) intercept_hat = self.se2.projection(intercept_hat) coef_hat = self.se2.to_tangent(coef_hat, intercept_hat) self.assertAllClose(intercept_hat, self.intercept_se2_true, atol=1e-4) tangent_vec_of_transport = self.se2.metric.log( self.intercept_se2_true, base_point=intercept_hat) transported_coef_hat = self.se2.metric.parallel_transport( tangent_vec=coef_hat, base_point=intercept_hat, direction=tangent_vec_of_transport, ) self.assertAllClose(transported_coef_hat, self.coef_se2_true, atol=0.6)
def test_value_and_grad_loss_se2(self): gr = GeodesicRegression( self.se2, metric=self.metric_se2, center_X=False, method="extrinsic", max_iter=50, init_step_size=0.1, verbose=True, ) def loss_of_param(param): return gr._loss(self.X_se2, self.y_se2, param, self.shape_se2) objective_with_grad = gs.autodiff.value_and_grad(loss_of_param) loss_value, loss_grad = objective_with_grad(self.param_se2_true) expected_grad_shape = ( 2, self.shape_se2[0] * self.shape_se2[1], ) self.assertTrue(gs.isclose(loss_value, 0.0)) loss_value, loss_grad = objective_with_grad(self.param_se2_guess) self.assertAllClose(loss_value.shape, ()) self.assertAllClose(loss_grad.shape, expected_grad_shape) self.assertFalse(gs.isclose(loss_value, 0.0)) self.assertFalse( gs.all(gs.isclose(loss_grad, gs.zeros(expected_grad_shape)))) self.assertTrue(gs.all(~gs.isnan(loss_grad))) objective_with_grad = gs.autodiff.value_and_grad(loss_of_param, to_numpy=True) loss_value, loss_grad = objective_with_grad(self.param_se2_guess) expected_grad_shape = ( 2, self.shape_se2[0] * self.shape_se2[1], ) self.assertAllClose(loss_value.shape, ()) self.assertAllClose(loss_grad.shape, expected_grad_shape) self.assertFalse(gs.isclose(loss_value, 0.0)) self.assertFalse(gs.isnan(loss_value)) self.assertFalse( gs.all(gs.isclose(loss_grad, gs.zeros(expected_grad_shape)))) self.assertTrue(gs.all(~gs.isnan(loss_grad)))
def test_fit_extrinsic_hypersphere(self): gr = GeodesicRegression( self.sphere, metric=self.sphere.metric, center_X=False, method="extrinsic", verbose=True, max_iter=50, learning_rate=0.1, initialization="random", regularization=0.9, ) gr.fit(self.X_sphere, self.y_sphere, compute_training_score=True) training_score = gr.training_score_ intercept_hat, coef_hat = gr.intercept_, gr.coef_ self.assertAllClose(intercept_hat.shape, self.shape_sphere) self.assertAllClose(coef_hat.shape, self.shape_sphere) self.assertAllClose(training_score, 1.0, atol=500 * gs.atol) self.assertAllClose(intercept_hat, self.intercept_sphere_true, atol=5e-3) tangent_vec_of_transport = self.sphere.metric.log( self.intercept_sphere_true, base_point=intercept_hat) transported_coef_hat = self.sphere.metric.parallel_transport( tangent_vec_a=coef_hat, tangent_vec_b=tangent_vec_of_transport, base_point=intercept_hat, ) self.assertAllClose(transported_coef_hat, self.coef_sphere_true, atol=0.6)
def test_loss_minimization_extrinsic_hypersphere(self): """Minimize loss from noiseless data.""" gr = GeodesicRegression(self.sphere, regularization=0) def loss_of_param(param): return gr._loss(self.X_sphere, self.y_sphere, param, self.shape_sphere) objective_with_grad = gs.autodiff.value_and_grad(loss_of_param, to_numpy=True) initial_guess = gs.flatten(self.param_sphere_guess) res = minimize( objective_with_grad, initial_guess, method="CG", jac=True, tol=10 * gs.atol, options={ "disp": True, "maxiter": 50 }, ) self.assertAllClose( gs.array(res.x).shape, ((self.dim_sphere + 1) * 2, )) self.assertAllClose(res.fun, 0.0, atol=5e-3) # Cast required because minimization happens in scipy in float64 param_hat = gs.cast(gs.array(res.x), self.param_sphere_true.dtype) intercept_hat, coef_hat = gs.split(param_hat, 2) intercept_hat = self.sphere.projection(intercept_hat) coef_hat = self.sphere.to_tangent(coef_hat, intercept_hat) self.assertAllClose(intercept_hat, self.intercept_sphere_true, atol=5e-2) tangent_vec_of_transport = self.sphere.metric.log( self.intercept_sphere_true, base_point=intercept_hat) transported_coef_hat = self.sphere.metric.parallel_transport( tangent_vec=coef_hat, base_point=intercept_hat, direction=tangent_vec_of_transport, ) self.assertAllClose(transported_coef_hat, self.coef_sphere_true, atol=0.6)
def test_value_and_grad_loss_hypersphere(self): gr = GeodesicRegression( self.sphere, metric=self.sphere.metric, center_X=False, method="extrinsic", max_iter=50, init_step_size=0.1, verbose=True, regularization=0, ) def loss_of_param(param): return gr._loss(self.X_sphere, self.y_sphere, param, self.shape_sphere) # Without numpy conversion objective_with_grad = gs.autodiff.value_and_grad(loss_of_param) loss_value, loss_grad = objective_with_grad(self.param_sphere_guess) expected_grad_shape = (2, self.dim_sphere + 1) self.assertAllClose(loss_value.shape, ()) self.assertAllClose(loss_grad.shape, expected_grad_shape) self.assertFalse(gs.isclose(loss_value, 0.0)) self.assertFalse(gs.isnan(loss_value)) self.assertFalse( gs.all(gs.isclose(loss_grad, gs.zeros(expected_grad_shape)))) self.assertTrue(gs.all(~gs.isnan(loss_grad))) # With numpy conversion objective_with_grad = gs.autodiff.value_and_grad(loss_of_param, to_numpy=True) loss_value, loss_grad = objective_with_grad(self.param_sphere_guess) # Convert back to arrays/tensors loss_value = gs.array(loss_value) loss_grad = gs.array(loss_grad) expected_grad_shape = (2, self.dim_sphere + 1) self.assertAllClose(loss_value.shape, ()) self.assertAllClose(loss_grad.shape, expected_grad_shape) self.assertFalse(gs.isclose(loss_value, 0.0)) self.assertFalse(gs.isnan(loss_value)) self.assertFalse( gs.all(gs.isclose(loss_grad, gs.zeros(expected_grad_shape)))) self.assertTrue(gs.all(~gs.isnan(loss_grad)))
def main(): r"""Compute and visualize a geodesic regression on the sphere. The generative model of the data is: :math:`Z = Exp_{\beta_0}(\beta_1.X)` and :math:`Y = Exp_Z(\epsilon)` where: - :math:`Exp` denotes the Riemannian exponential, - :math:`\beta_0` is called the intercept, - :math:`\beta_1` is called the coefficient, - :math:`\epsilon \sim N(0, 1)` is a standard Gaussian noise, - :math:`X` is the input, :math:`Y` is the target. """ # Generate noise-free data n_samples = 50 X = gs.random.rand(n_samples) X -= gs.mean(X) intercept = SPACE.random_uniform() coef = SPACE.to_tangent(5.0 * gs.random.rand(EMBEDDING_DIM), base_point=intercept) y = METRIC.exp(X[:, None] * coef, base_point=intercept) # Generate normal noise normal_noise = gs.random.normal(size=(n_samples, EMBEDDING_DIM)) noise = SPACE.to_tangent(normal_noise, base_point=y) / gs.pi / 2 rss = gs.sum(METRIC.squared_norm(noise, base_point=y)) / n_samples # Add noise y = METRIC.exp(noise, y) # True noise level and R2 estimator = FrechetMean(METRIC) estimator.fit(y) variance_ = variance(y, estimator.estimate_, metric=METRIC) r2 = 1 - rss / variance_ # Fit geodesic regression gr = GeodesicRegression(SPACE, center_X=False, method="extrinsic", verbose=True) gr.fit(X, y, compute_training_score=True) intercept_hat, coef_hat = gr.intercept_, gr.coef_ # Measure Mean Squared Error mse_intercept = METRIC.squared_dist(intercept_hat, intercept) tangent_vec_to_transport = coef_hat tangent_vec_of_transport = METRIC.log(intercept, base_point=intercept_hat) transported_coef_hat = METRIC.parallel_transport( tangent_vec=tangent_vec_to_transport, base_point=intercept_hat, direction=tangent_vec_of_transport, ) mse_coef = METRIC.squared_norm(transported_coef_hat - coef, base_point=intercept) # Measure goodness of fit r2_hat = gr.training_score_ print(f"MSE on the intercept: {mse_intercept:.2e}") print(f"MSE on the coef, i.e. initial velocity: {mse_coef:.2e}") print(f"Determination coefficient: R^2={r2_hat:.2f}") print(f"True R^2: {r2:.2f}") # Plot fitted_data = gr.predict(X) fig = plt.figure(figsize=(8, 8)) ax = fig.add_subplot(111, projection="3d") sphere_visu = visualization.Sphere(n_meridians=30) ax = sphere_visu.set_ax(ax=ax) path = METRIC.geodesic(initial_point=intercept_hat, initial_tangent_vec=coef_hat) regressed_geodesic = path( gs.linspace(0.0, 1.0, 100) * gs.pi * 2 / METRIC.norm(coef)) regressed_geodesic = gs.to_numpy(gs.autodiff.detach(regressed_geodesic)) size = 10 marker = "o" sphere_visu.draw_points(ax, gs.array([intercept_hat]), marker=marker, c="r", s=size) sphere_visu.draw_points(ax, y, marker=marker, c="b", s=size) sphere_visu.draw_points(ax, fitted_data, marker=marker, c="g", s=size) ax.plot( regressed_geodesic[:, 0], regressed_geodesic[:, 1], regressed_geodesic[:, 2], c="gray", ) sphere_visu.draw(ax, linewidth=1) ax.grid(False) plt.axis("off") plt.show()
def main(): r"""Compute and visualize a geodesic regression on the SE(2). The generative model of the data is: :math:`Z = Exp_{\beta_0}(\beta_1.X)` and :math:`Y = Exp_Z(\epsilon)` where: - :math:`Exp` denotes the Riemannian exponential, - :math:`\beta_0` is called the intercept, - :math:`\beta_1` is called the coefficient, - :math:`\epsilon \sim N(0, 1)` is a standard Gaussian noise, - :math:`X` is the input, :math:`Y` is the target. """ # Generate noise-free data n_samples = 20 X = gs.random.normal(size=(n_samples, )) X -= gs.mean(X) intercept = SPACE.random_point() coef = SPACE.to_tangent(5.0 * gs.random.rand(3, 3), intercept) y = METRIC.exp(X[:, None, None] * coef[None], intercept) # Generate normal noise in the Lie algebra normal_noise = gs.random.normal(size=(n_samples, 3)) normal_noise = SPACE.lie_algebra.matrix_representation(normal_noise) noise = SPACE.tangent_translation_map(y)(normal_noise) / gs.pi rss = gs.sum(METRIC.squared_norm(noise, y)) / n_samples # Add noise y = METRIC.exp(noise, y) # True noise level and R2 estimator = FrechetMean(METRIC) estimator.fit(y) variance_ = variance(y, estimator.estimate_, metric=METRIC) r2 = 1 - rss / variance_ # Fit geodesic regression gr = GeodesicRegression( SPACE, metric=METRIC, center_X=False, method="riemannian", max_iter=100, init_step_size=0.1, verbose=True, initialization="frechet", ) gr.fit(X, y, compute_training_score=True) intercept_hat, beta_hat = gr.intercept_, gr.coef_ # Measure Mean Squared Error mse_intercept = METRIC.squared_dist(intercept_hat, intercept) mse_beta = METRIC.squared_norm( METRIC.parallel_transport(beta_hat, intercept_hat, METRIC.log(intercept_hat, intercept)) - coef, intercept, ) # Measure goodness of fit r2_hat = gr.training_score_ print(f"MSE on the intercept: {mse_intercept:.2e}") print(f"MSE on the initial velocity beta: {mse_beta:.2e}") print(f"Determination coefficient: R^2={r2_hat:.2f}") print(f"True R^2: {r2:.2f}") # Plot fitted_data = gr.predict(X) fig = plt.figure(figsize=(8, 8)) ax = fig.add_subplot(111) sphere_visu = visualization.SpecialEuclidean2() ax = sphere_visu.set_ax(ax=ax) path = METRIC.geodesic(initial_point=intercept_hat, initial_tangent_vec=beta_hat) regressed_geodesic = path(gs.linspace(min(X), max(X), 100)) sphere_visu.draw_points(ax, y, marker="o", c="black") sphere_visu.draw_points(ax, fitted_data, marker="o", c="gray") sphere_visu.draw_points(ax, gs.array([intercept]), marker="x", c="r") sphere_visu.draw_points(ax, gs.array([intercept_hat]), marker="o", c="green") ax.plot(regressed_geodesic[:, 0, 2], regressed_geodesic[:, 1, 2], c="gray") plt.show()