def test_dist_log_exp_norm_vector(self): n_samples = 5 point = self.space_vector.random_point(n_samples) base_point = self.space_vector.random_point(n_samples) logs = self.space_vector.metric.log(point, base_point) normalized_logs = gs.einsum( '..., ...j->...j', 1. / self.space_vector.metric.norm(logs, base_point), logs) point = self.space_vector.metric.exp(normalized_logs, base_point) result = self.space_vector.metric.dist(point, base_point) expected = gs.ones(n_samples) self.assertAllClose(result, expected)
def _exp_translation_transform(self, rot_vec): base_1 = gs.eye(2) base_2 = self.rotations.skew_matrix_from_vector(gs.ones(1)) cos_coef = rot_vec * utils.taylor_exp_even_func( rot_vec**2, utils.cosc_close_0, order=3) sin_coef = utils.taylor_exp_even_func(rot_vec**2, utils.sinc_close_0, order=3) sin_term = gs.einsum('...i,...jk->...jk', sin_coef, base_1) cos_term = gs.einsum('...i,...jk->...jk', cos_coef, base_2) transform = sin_term + cos_term return transform
def coefficients(ind_k): param_k = base_point[..., ind_k] param_sum = gs.sum(base_point, -1) c1 = 1 / gs.polygamma(1, param_k) / ( 1 / gs.polygamma(1, param_sum) - gs.sum(1 / gs.polygamma(1, base_point), -1)) c2 = - c1 * gs.polygamma(2, param_sum) / gs.polygamma(1, param_sum) mat_ones = gs.ones((n_points, self.dim, self.dim)) mat_diag = from_vector_to_diagonal_matrix( - gs.polygamma(2, base_point) / gs.polygamma(1, base_point)) arrays = [gs.zeros((1, ind_k)), gs.ones((1, 1)), gs.zeros((1, self.dim - ind_k - 1))] vec_k = gs.tile(gs.hstack(arrays), (n_points, 1)) val_k = gs.polygamma(2, param_k) / gs.polygamma(1, param_k) vec_k = gs.einsum('i,ij->ij', val_k, vec_k) mat_k = from_vector_to_diagonal_matrix(vec_k) mat = gs.einsum('i,ijk->ijk', c2, mat_ones)\ - gs.einsum('i,ijk->ijk', c1, mat_diag) + mat_k return 1 / 2 * mat
def test_dist_log_exp_norm_matrix(self): n_samples = 10 point = self.space_matrix.random_point(n_samples) base_point = self.space_matrix.random_point(n_samples) logs = self.space_matrix.metric.log(point, base_point) normalized_logs = gs.einsum( "..., ...jl->...jl", 1.0 / self.space_matrix.metric.norm(logs, base_point), logs, ) point = self.space_matrix.metric.exp(normalized_logs, base_point) result = self.space_matrix.metric.dist(point, base_point) expected = gs.ones((n_samples, )) self.assertAllClose(result, expected)
def test_random_gaussian_rotation_orbit_noisy(self): """Test random_gaussian_rotation_orbit""" n = 2 self.setUp_alt(n) mean_spd = self.space.random_uniform() var_rotations = 1. var_eigenvalues = gs.ones(n) points = self.space.random_gaussian_rotation_orbit_noisy( mean_spd=mean_spd, var_rotations=var_rotations, var_eigenvalues=var_eigenvalues, n_samples=4) result = self.space.belongs(points) expected = gs.array([True] * 4) self.assertAllClose(result, expected)
def test_point_to_pdf_vectorization(self): """Test point_to_pdf. Check vectorization of the computation of the pdf. """ n_points = 2 points = self.dirichlet.random_point(n_points) pdfs = self.dirichlet.point_to_pdf(points) alpha = gs.ones(self.dim) samples = self.dirichlet.sample(alpha, self.n_samples) result = pdfs(samples) pdf1 = [dirichlet.pdf(x, points[0, :]) for x in samples] pdf2 = [dirichlet.pdf(x, points[1, :]) for x in samples] expected = gs.stack([gs.array(pdf1), gs.array(pdf2)], axis=0) self.assertAllClose(result, expected)
def regularize_tangent_vec_at_identity(self, tangent_vec, metric=None, point_type=None): """Regularize a tangent vector at the identity. Parameters ---------- tangent_vec: array-like, shape=[n_samples, {dim, [n + 1, n + 1]}] metric : RiemannianMetric, optional point_type : str, {'vector', 'matrix'}, optional default: self.default_point_type Returns ------- regularized_vec : the regularized tangent vector """ if point_type == 'vector': return self.regularize_tangent_vec(tangent_vec, self.identity, metric, point_type=point_type) if point_type == 'matrix': translation_mask = gs.hstack( [gs.ones((self.n, ) * 2), 2 * gs.ones((self.n, 1))]) translation_mask = gs.concatenate( [translation_mask, gs.zeros((1, self.n + 1))], axis=0) tangent_vec = tangent_vec * gs.where(translation_mask != 0., gs.array(1.), gs.array(0.)) tangent_vec = (tangent_vec - GeneralLinear.transpose(tangent_vec)) / 2. return tangent_vec * translation_mask raise ValueError('Invalid point_type, expected \'vector\' or ' '\'matrix\'.')
def is_centered_test_data(self): random_data = [ dict( k_landmarks=4, m_ambient=3, point=gs.ones((4, 3)), expected=gs.array(False), ), dict( k_landmarks=4, m_ambient=3, point=gs.zeros((4, 3)), expected=gs.array(True), ), ] return self.generate_tests([], random_data)
def predict(self, X): """Perform prediction. Parameters ---------- X : {array-like, sparse matrix}, shape (n_samples, n_features) The training input samples. Returns ------- y : ndarray, shape (n_samples,) Returns an array of ones. """ X = check_array(X, accept_sparse=True) check_is_fitted(self, "is_fitted_") return gs.ones(X.shape[0], dtype=gs.int64)
def from_vector(vec, dtype=gs.float32): """Convert a vector into a symmetric matrix.""" vec_dim = vec.shape[-1] mat_dim = (gs.sqrt(8. * vec_dim + 1) - 1) / 2 if mat_dim != int(mat_dim): raise ValueError('Invalid input dimension, it must be of the form' '(n_samples, n * (n + 1) / 2)') mat_dim = int(mat_dim) shape = (mat_dim, mat_dim) mask = 2 * gs.ones(shape) - gs.eye(mat_dim) indices = list(zip(*gs.triu_indices(mat_dim))) vec = gs.cast(vec, dtype) upper_triangular = gs.stack( [gs.array_from_sparse(indices, data, shape) for data in vec]) mat = Matrices.to_symmetric(upper_triangular) * mask return mat
def test_is_single_matrix_pd(self): pd = gs.eye(3) not_pd_1 = -1 * gs.eye(3) not_pd_2 = gs.ones((3, 3)) pd_result = gs.linalg.is_single_matrix_pd(pd) not_pd_1_result = gs.linalg.is_single_matrix_pd(not_pd_1) not_pd_2_result = gs.linalg.is_single_matrix_pd(not_pd_2) pd_expected = gs.array(True) not_pd_1_expected = gs.array(False) not_pd_2_expected = gs.array(False) self.assertAllClose(pd_expected, pd_result) self.assertAllClose(not_pd_1_expected, not_pd_1_result) self.assertAllClose(not_pd_2_expected, not_pd_2_result)
def draw_triangle(self, theta, phi, scale): """Draw the corresponding triangle on the sphere at theta, phi.""" u_theta = gs.cos(theta) * self.ua + gs.sin(theta) * self.na triangle = gs.cos(phi / 2.0) * self.pole + gs.sin(phi / 2.0) * u_theta triangle = scale * triangle triangle3d = gs.transpose( gs.stack((triangle[:, 0], triangle[:, 1], 0.5 * gs.ones(3))) ) triangle3d = self.rotation(theta, phi) @ gs.transpose(triangle3d) x = list(triangle3d[0]) + [triangle3d[0, 0]] y = list(triangle3d[1]) + [triangle3d[1, 0]] z = list(triangle3d[2]) + [triangle3d[2, 0]] self.ax.plot3D(x, y, z, "grey", zorder=1) c = ["red", "green", "blue"] for i in range(3): self.ax.scatter(x[i], y[i], z[i], color=c[i], s=10, alpha=1, zorder=1)
def variance(self, points, weights=None, base_point=None, point_type='vector'): """Compute variance of (weighted) points wrt a base point. Parameters ---------- points: array-like, shape=[n_samples, dimension] weights: array-like, shape=[n_samples, 1], optional Returns ------- variance """ if point_type == 'vector': points = gs.to_ndarray(points, to_ndim=2) if point_type == 'matrix': points = gs.to_ndarray(points, to_ndim=3) n_points = gs.shape(points)[0] if weights is None: weights = gs.ones((n_points, 1)) weights = gs.array(weights) weights = gs.to_ndarray(weights, to_ndim=2, axis=1) sum_weights = gs.sum(weights) if base_point is None: base_point = self.mean(points, weights) variance = 0. sq_dists = self.squared_dist(base_point, points) variance += gs.einsum('nk,nj->j', weights, sq_dists) variance = gs.array(variance) variance /= sum_weights variance = gs.to_ndarray(variance, to_ndim=1) variance = gs.to_ndarray(variance, to_ndim=2, axis=1) return variance
def align(self, point, base_point, **kwargs): """Align point to base_point. Find the optimal rotation R in SO(m) such that the base point and R.point are well positioned. Parameters ---------- point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. Returns ------- aligned : array-like, shape=[..., k_landmarks, m_ambient] R.point. """ mat = gs.matmul(Matrices.transpose(point), base_point) left, singular_values, right = gs.linalg.svd(mat) det = gs.linalg.det(mat) conditioning = ( (singular_values[..., -2] + gs.sign(det) * singular_values[..., -1]) / singular_values[..., 0]) if gs.any(conditioning < 5e-4): logging.warning(f'Singularity close, ill-conditioned matrix ' f'encountered: {conditioning}') if gs.any(gs.isclose(conditioning, 0.)): logging.warning("Alignment matrix is not unique.") if gs.any(det < 0): ones = gs.ones(self.m_ambient) reflection_vec = gs.concatenate( [ones[:-1], gs.array([-1.])], axis=0) mask = gs.cast(det < 0, gs.float32) sign = (mask[..., None] * reflection_vec + (1. - mask)[..., None] * ones) j_matrix = from_vector_to_diagonal_matrix(sign) rotation = Matrices.mul( Matrices.transpose(right), j_matrix, Matrices.transpose(left)) else: rotation = gs.matmul( Matrices.transpose(right), Matrices.transpose(left)) return gs.matmul(point, Matrices.transpose(rotation))
def normalize(self, vector, base_point): """Normalize tangent vector at a given point. Parameters ---------- vector : array-like, shape=[..., dim] Tangent vector at base_point. base_point : array-like, shape=[..., dim] Point. Returns ------- normalized_vector : array-like, shape=[..., dim] Unit tangent vector at base_point. """ norm = self.norm(vector, base_point) norm = gs.where(norm == 0, gs.ones(norm.shape), norm) normalized_vector = gs.einsum("...i,...->...i", vector, 1 / norm) return normalized_vector
def group_exponential_barycenter(self, points, weights=None): """ Compute the group exponential barycenter in SO(n), which is the Frechet mean of the canonical bi-invariant metric on SO(n). """ n_points = points.shape[0] assert n_points > 0 if weights is None: weights = gs.ones((n_points, 1)) n_weights = weights.shape[0] assert n_points == n_weights barycenter = self.bi_invariant_metric.mean(points, weights) barycenter = gs.to_ndarray(barycenter, to_ndim=2) assert barycenter.ndim == 2, barycenter.ndim return barycenter
def test_point_to_pdf(self, point, n_samples): point = gs.to_ndarray(point, 2) n_points = point.shape[0] pdf = self.space().point_to_pdf(point) alpha = gs.ones(2) samples = self.space().sample(alpha, n_samples) result = pdf(samples) pdf = [] for i in range(n_points): pdf.append( gs.array( [ gamma.pdf(x, a=point[i, 0], scale=point[i, 1] / point[i, 0]) for x in samples ] ) ) expected = gs.squeeze(gs.stack(pdf, axis=0)) self.assertAllClose(result, expected)
def mean(self, points, weights=None): """ The Frechet mean of (weighted) points is the weighted average of the points in the Minkowski space. """ if isinstance(points, list): points = gs.vstack(points) points = gs.to_ndarray(points, to_ndim=2) n_points = gs.shape(points)[0] if isinstance(weights, list): weights = gs.vstack(weights) elif weights is None: weights = gs.ones((n_points, )) weighted_points = gs.einsum('n,nj->nj', weights, points) mean = (gs.sum(weighted_points, axis=0) / gs.sum(weights)) mean = gs.to_ndarray(mean, to_ndim=2) return mean
def main(): """Plot the result of a KNN classification on the sphere.""" sphere = Hypersphere(dim=2) sphere_distance = sphere.metric.dist n_labels = 2 n_samples_per_dataset = 10 n_targets = 200 dataset_1 = sphere.random_von_mises_fisher(kappa=10, n_samples=n_samples_per_dataset) dataset_2 = -sphere.random_von_mises_fisher( kappa=10, n_samples=n_samples_per_dataset) training_dataset = gs.concatenate((dataset_1, dataset_2), axis=0) labels_dataset_1 = gs.zeros([n_samples_per_dataset], dtype=gs.int64) labels_dataset_2 = gs.ones([n_samples_per_dataset], dtype=gs.int64) labels = gs.concatenate((labels_dataset_1, labels_dataset_2)) target = sphere.random_uniform(n_samples=n_targets) neigh = KNearestNeighborsClassifier(n_neighbors=2, distance=sphere_distance) neigh.fit(training_dataset, labels) target_labels = neigh.predict(target) plt.figure(0) ax = plt.subplot(111, projection="3d") plt.title("Training set") sphere_plot = visualization.Sphere() sphere_plot.draw(ax=ax) for i_label in range(n_labels): points_label_i = training_dataset[labels == i_label, ...] sphere_plot.draw_points(ax=ax, points=points_label_i) plt.figure(1) ax = plt.subplot(111, projection="3d") plt.title("Classification") sphere_plot = visualization.Sphere() sphere_plot.draw(ax=ax) for i_label in range(n_labels): target_points_label_i = target[target_labels == i_label, ...] sphere_plot.draw_points(ax=ax, points=target_points_label_i) plt.show()
def test_intrinsic_and_extrinsic_coords(self): """ Test that the composition of intrinsic_to_extrinsic_coords and extrinsic_to_intrinsic_coords gives the identity. """ point_int = gs.ones(self.dimension) point_ext = self.space.intrinsic_to_extrinsic_coords(point_int) result = self.space.extrinsic_to_intrinsic_coords(point_ext) expected = point_int self.assertAllClose(result, expected) point_ext = gs.array([2.0, 1.0, 1.0, 1.0]) point_int = self.space.to_coordinates(point_ext, "intrinsic") result = self.space.from_coordinates(point_int, "intrinsic") expected = point_ext self.assertAllClose(result, expected)
def random_point(self, n_samples=1): """Generate parameters of categorical distributions. The Dirichlet distribution on the simplex is used to generate parameters. Parameters ---------- n_samples : int Number of samples. Optional, default: 1. Returns ------- samples : array-like, shape=[..., dim + 1] Sample of points representing categorical distributions. """ samples = dirichlet.rvs(gs.ones(self.dim + 1), size=n_samples) return gs.from_numpy(samples)
def linear_mean(points, weights=None, point_type='vector'): """Compute the weighted linear mean. The linear mean is the Frechet mean when points: - lie in a Euclidean space with Euclidean metric, - lie in a Minkowski space with Minkowski metric. Parameters ---------- points : array-like, shape=[n_samples, dim] Points to be averaged. weights : array-like, shape=[n_samples, 1], optional Weights associated to the points. Returns ------- mean : array-like, shape=[1, dim] Weighted linear mean of the points. """ # TODO(ninamiolane): Factorize this code to handle lists # in the whole codebase if isinstance(points, list): points = gs.stack(points, axis=0) if isinstance(weights, list): weights = gs.stack(weights, axis=0) n_points = geomstats.vectorization.get_n_points( points, point_type) if weights is None: weights = gs.ones((n_points,)) sum_weights = gs.sum(weights) einsum_str = '...,...j->...j' if point_type == 'matrix': einsum_str = '...,...jk->...jk' weighted_points = gs.einsum(einsum_str, weights, points) mean = gs.sum(weighted_points, axis=0) / sum_weights return mean
def sectional_curvature_test_data(self): dim_list = [1] n_samples_list = random.sample(range(1, 4), 2) random_data = [] for dim, n_samples in zip(dim_list, n_samples_list): sphere = Hypersphere(dim) base_point = sphere.random_uniform() tangent_vec_a = sphere.to_tangent( gs.random.rand(n_samples, sphere.dim + 1), base_point) tangent_vec_b = sphere.to_tangent( gs.random.rand(n_samples, sphere.dim + 1), base_point) expected = gs.ones(n_samples) # try shape here random_data.append( dict( dim=dim, tangent_vec_a=tangent_vec_a, tangent_vec_b=tangent_vec_b, base_point=base_point, expected=expected, ), ) return self.generate_tests(random_data)
def linear_mean(points, weights=None, point_type='vector'): """Compute the weighted linear mean. The linear mean is the Frechet mean when points: - lie in a Euclidean space with Euclidean metric, - lie in a Minkowski space with Minkowski metric. Parameters ---------- points : array-like, shape=[..., dim] Points to be averaged. weights : array-like, shape=[...,] Weights associated to the points. Optional, default: None. Returns ------- mean : array-like, shape=[dim,] Weighted linear mean of the points. """ if isinstance(points, list): points = gs.stack(points, axis=0) if isinstance(weights, list): weights = gs.stack(weights, axis=0) n_points = geomstats.vectorization.get_n_points( points, point_type) if weights is None: weights = gs.ones((n_points,)) sum_weights = gs.sum(weights) einsum_str = '...,...j->...j' if point_type == 'matrix': einsum_str = '...,...jk->...jk' weighted_points = gs.einsum(einsum_str, weights, points) mean = gs.sum(weighted_points, axis=0) / sum_weights return mean
def permute(self, graph_to_permute, permutation): r"""Permutation action applied to graph observation. Parameters ---------- graph_to_permute : array-like, shape=[..., n, n] Input graphs to be permuted. permutation: array-like, shape=[..., n] Node permutations where in position i we have the value j meaning the node i should be permuted with node j. Returns ------- graphs_permuted : array-like, shape=[..., n, n] Graphs permuted. """ nodes = self.nodes single_graph = len(graph_to_permute.shape) < 3 if single_graph: graph_to_permute = [graph_to_permute] permutation = [permutation] result = [] for i, p in enumerate(permutation): if gs.all(gs.array(nodes) == gs.array(p)): result.append(graph_to_permute[i]) else: gtype = graph_to_permute[i].dtype permutation_matrix = gs.array_from_sparse( data=gs.ones(nodes, dtype=gtype), indices=list(zip(list(range(nodes)), p)), target_shape=(nodes, nodes), ) result.append( self.adjmat.mul( permutation_matrix, graph_to_permute[i], gs.transpose(permutation_matrix), )) return result[0] if single_graph else gs.array(result)
def test_intrinsic_and_extrinsic_coords(self): """ Test that the composition of intrinsic_to_extrinsic_coords and extrinsic_to_intrinsic_coords gives the identity. """ point_int = gs.ones(self.dimension) point_ext = self.space.intrinsic_to_extrinsic_coords(point_int) result = self.space.extrinsic_to_intrinsic_coords(point_ext) expected = point_int expected = helper.to_vector(expected) gs.testing.assert_allclose(result, expected) point_ext = self.space.random_uniform() point_int = self.space.extrinsic_to_intrinsic_coords(point_ext) result = self.space.intrinsic_to_extrinsic_coords(point_int) expected = point_ext expected = helper.to_vector(expected) gs.testing.assert_allclose(result, expected)
def test_intrinsic_and_extrinsic_coords(self): """ Test that the composition of intrinsic_to_extrinsic_coords and extrinsic_to_intrinsic_coords gives the identity. """ point_int = gs.ones(self.dimension) point_ext = self.space.intrinsic_to_extrinsic_coords(point_int) result = self.space.extrinsic_to_intrinsic_coords(point_ext) expected = point_int expected = helper.to_vector(expected) with self.test_session(): self.assertAllClose(gs.eval(result), gs.eval(expected)) point_ext = tf.convert_to_tensor([2.0, 1.0, 1.0, 1.0]) point_int = self.space.extrinsic_to_intrinsic_coords(point_ext) result = self.space.intrinsic_to_extrinsic_coords(point_int) expected = point_ext expected = helper.to_vector(expected) with self.test_session(): self.assertAllClose(gs.eval(result), gs.eval(expected))
def variance(self, points, weights=None, base_point=None, point_type='vector'): """ Variance of (weighted) points wrt a base point. """ if point_type == 'vector': points = gs.to_ndarray(points, to_ndim=2) if point_type == 'matrix': points = gs.to_ndarray(points, to_ndim=3) n_points = gs.shape(points)[0] if weights is None: weights = gs.ones((n_points, 1)) weights = gs.array(weights) weights = gs.to_ndarray(weights, to_ndim=2, axis=1) sum_weights = gs.sum(weights) if base_point is None: base_point = self.mean(points, weights) variance = 0. sq_dists = self.squared_dist(base_point, points) variance += gs.einsum('nk,nj->j', weights, sq_dists) variance = gs.array(variance) variance /= sum_weights variance = gs.to_ndarray(variance, to_ndim=1) variance = gs.to_ndarray(variance, to_ndim=2, axis=1) return variance
def variance(points, base_point, metric, weights=None, point_type='vector'): """Variance of (weighted) points wrt a base point. Parameters ---------- points : array-like, shape=[n_samples, dimension] weights : array-like, shape=[n_samples, 1], optional """ if point_type == 'vector': points = gs.to_ndarray(points, to_ndim=2) base_point = gs.to_ndarray(base_point, to_ndim=2) if point_type == 'matrix': points = gs.to_ndarray(points, to_ndim=3) base_point = gs.to_ndarray(base_point, to_ndim=3) n_points = gs.shape(points)[0] if weights is None: weights = gs.ones((n_points, 1)) weights = gs.array(weights) weights = gs.to_ndarray(weights, to_ndim=2, axis=1) sum_weights = gs.sum(weights) var = 0. sq_dists = metric.squared_dist(base_point, points) var += gs.einsum('nk,nj->j', weights, sq_dists) var = gs.array(var) var /= sum_weights var = gs.to_ndarray(var, to_ndim=1) var = gs.to_ndarray(var, to_ndim=2, axis=1) return var
def fit(self, X, weights=None): r"""Compute the Geometric Median. Parameters ---------- X : array-like, shape=[n_samples, n_features] Training input samples. weights : array-like, shape=[...] Weights associated to the points. Optional, default: None, in which case it is equally weighted Returns ------- self : object Returns self. """ n_points = X.shape[0] current_median = X[-1] if self.init is None else self.init if weights is None: weights = gs.ones(n_points) / n_points for iteration in range(1, self.max_iter + 1): new_median = self._iterate_once(current_median, X, weights, self.lr) shift = self.metric.dist(new_median, current_median) if shift < gs.atol: break current_median = new_median if self.print_every and (iteration + 1) % self.print_every == 0: logging.info( f"iteration: {iteration} curr_median: {current_median}") self.estimate_ = current_median return self