def _update_medoid_indexes(self, distances, labels, medoid_indices): for cluster in range(self.n_clusters): cluster_index = gs.where(labels == cluster)[0] if len(cluster_index) == 0: logging.warning('One cluster is empty.') continue in_cluster_distances = distances[ cluster_index, gs.expand_dims(cluster_index, axis=-1)] in_cluster_all_costs = gs.sum(in_cluster_distances, axis=1) min_cost_index = gs.argmin(in_cluster_all_costs) min_cost = in_cluster_all_costs[min_cost_index] current_cost = in_cluster_all_costs[gs.argmax( cluster_index == medoid_indices[cluster])] if min_cost < current_cost: medoid_indices[cluster] = cluster_index[min_cost_index]
def sample(self, point, n_samples=1): """Sample from the categorical distribution. Sample from the categorical distribution with parameters provided by point. This gives samples in the simplex. Parameters ---------- point : array-like, shape=[..., dim + 1] Parameters of a categorical distribution, i.e. probabilities associated to dim + 1 outcomes. n_samples : int Number of points to sample with each set of parameters in point. Optional, default: 1. Returns ------- samples : array-like, shape=[..., n_samples] Samples from categorical distributions. """ geomstats.errors.check_belongs(point, self) point = gs.to_ndarray(point, to_ndim=2) samples = [] for param in point: counts = multinomial.rvs(1, param, size=n_samples) samples.append(gs.argmax(counts, axis=-1)) return samples[0] if len(point) == 1 else gs.stack(samples)
def split_data_in_classes(X, y, c): """Split a labelled dataset in sub-datasets of each label. Parameters ---------- X : array-like, shape=[n_samples, dim] if point_type='vector' shape=[n_samples, n, n] if point_type='matrix' Labelled dataset, where n_samples is the number of samples and n_features is the number of features. y : array-like, shape=[n_samples, n_classes] Labels, where n_classes is the number of classes. c : int Class index Returns ------- X_split : array-like, shape=[n_samples_in_class, dim] if point_type='vector' shape=[n_samples_in_class, n, n] if point_type='matrix' Labelled dataset, Where n_samples_in_class is the number of samples in class c """ return X[gs.argmax(y, axis=1) == c]
problem = Problem(manifold=sphere, cost=cost, egrad=egrad) solver = SteepestDescent() return solver.solve(problem) if __name__ == '__main__': if os.environ.get('GEOMSTATS_BACKEND') != 'numpy': raise SystemExit( 'This example currently only supports the numpy backend') ambient_dim = 128 mat = gs.random.normal(size=(ambient_dim, ambient_dim)) mat = 0.5 * (mat + mat.T) eigenvalues, eigenvectors = gs.linalg.eig(mat) dominant_eigenvector = eigenvectors[:, gs.argmax(eigenvalues)] dominant_eigenvector_estimate = estimate_dominant_eigenvector(mat) if (gs.sign(dominant_eigenvector[0]) != gs.sign(dominant_eigenvector_estimate[0])): dominant_eigenvector_estimate = -dominant_eigenvector_estimate logging.info( 'l2-norm of dominant eigenvector: %s', gs.linalg.norm(dominant_eigenvector)) logging.info( 'l2-norm of dominant eigenvector estimate: %s', gs.linalg.norm(dominant_eigenvector_estimate)) error_norm = gs.linalg.norm( dominant_eigenvector - dominant_eigenvector_estimate) logging.info('l2-norm of difference vector: %s', error_norm)
def rotation_vector_from_matrix(self, rot_mat): """ In 3D, convert rotation matrix to rotation vector (axis-angle representation). Get the angle through the trace of the rotation matrix: The eigenvalues are: 1, cos(angle) + i sin(angle), cos(angle) - i sin(angle) so that: trace = 1 + 2 cos(angle), -1 <= trace <= 3 Get the rotation vector through the formula: S_r = angle / ( 2 * sin(angle) ) (R - R^T) For the edge case where the angle is close to pi, the formulation is derived by going from rotation matrix to unit quaternion to axis-angle: r = angle * v / |v|, where (w, v) is a unit quaternion. In nD, the rotation vector stores the n(n-1)/2 values of the skew-symmetric matrix representing the rotation. """ rot_mat = gs.to_ndarray(rot_mat, to_ndim=3) n_rot_mats, mat_dim_1, mat_dim_2 = rot_mat.shape assert mat_dim_1 == mat_dim_2 == self.n rot_mat = closest_rotation_matrix(rot_mat) if self.n == 3: trace = gs.trace(rot_mat, axis1=1, axis2=2) trace = gs.to_ndarray(trace, to_ndim=2, axis=1) assert trace.shape == (n_rot_mats, 1), trace.shape cos_angle = .5 * (trace - 1) cos_angle = gs.clip(cos_angle, -1, 1) angle = gs.arccos(cos_angle) rot_mat_transpose = gs.transpose(rot_mat, axes=(0, 2, 1)) rot_vec = vector_from_skew_matrix(rot_mat - rot_mat_transpose) mask_0 = gs.isclose(angle, 0) mask_0 = gs.squeeze(mask_0, axis=1) rot_vec[mask_0] = (rot_vec[mask_0] * (.5 - (trace[mask_0] - 3.) / 12.)) mask_pi = gs.isclose(angle, gs.pi) mask_pi = gs.squeeze(mask_pi, axis=1) # choose the largest diagonal element # to avoid a square root of a negative number a = 0 if gs.any(mask_pi): a = gs.argmax(gs.diagonal(rot_mat[mask_pi], axis1=1, axis2=2)) b = gs.mod(a + 1, 3) c = gs.mod(a + 2, 3) # compute the axis vector sq_root = gs.sqrt( (rot_mat[mask_pi, a, a] - rot_mat[mask_pi, b, b] - rot_mat[mask_pi, c, c] + 1.)) rot_vec_pi = gs.zeros((sum(mask_pi), self.dimension)) rot_vec_pi[:, a] = sq_root / 2. rot_vec_pi[:, b] = ( (rot_mat[mask_pi, b, a] + rot_mat[mask_pi, a, b]) / (2. * sq_root)) rot_vec_pi[:, c] = ( (rot_mat[mask_pi, c, a] + rot_mat[mask_pi, a, c]) / (2. * sq_root)) rot_vec[mask_pi] = (angle[mask_pi] * rot_vec_pi / gs.linalg.norm(rot_vec_pi)) mask_else = ~mask_0 & ~mask_pi rot_vec[mask_else] = (angle[mask_else] / (2. * gs.sin(angle[mask_else])) * rot_vec[mask_else]) else: skew_mat = self.embedding_manifold.group_log_from_identity(rot_mat) rot_vec = vector_from_skew_matrix(skew_mat) return self.regularize(rot_vec)
def rotation_vector_from_matrix(self, rot_mat): r"""Convert rotation matrix (in 3D) to rotation vector (axis-angle). Get the angle through the trace of the rotation matrix: The eigenvalues are: :math:`\{1, \cos(angle) + i \sin(angle), \cos(angle) - i \sin(angle)\}` so that: :math:`trace = 1 + 2 \cos(angle), \{-1 \leq trace \leq 3\}` Get the rotation vector through the formula: :math:`S_r = \frac{angle}{(2 * \sin(angle) ) (R - R^T)}` For the edge case where the angle is close to pi, the formulation is derived by using the following equality (see the Axis-angle representation on Wikipedia): :math:`outer(r, r) = \frac{1}{2} (R + I_3)` In nD, the rotation vector stores the :math:`n(n-1)/2` values of the skew-symmetric matrix representing the rotation. Parameters ---------- rot_mat : array-like, shape=[..., n, n] Returns ------- regularized_rot_vec : array-like, shape=[..., 3] """ n_rot_mats, _, _ = rot_mat.shape trace = gs.trace(rot_mat, axis1=1, axis2=2) trace = gs.to_ndarray(trace, to_ndim=2, axis=1) trace_num = gs.clip(trace, -1, 3) angle = gs.arccos(0.5 * (trace_num - 1)) rot_mat_transpose = gs.transpose(rot_mat, axes=(0, 2, 1)) rot_vec_not_pi = self.vector_from_skew_matrix(rot_mat - rot_mat_transpose) mask_0 = gs.cast(gs.isclose(angle, 0.), gs.float32) mask_pi = gs.cast(gs.isclose(angle, gs.pi, atol=1e-2), gs.float32) mask_else = (1 - mask_0) * (1 - mask_pi) numerator = 0.5 * mask_0 + angle * mask_else denominator = (1 - angle**2 / 6) * mask_0 + 2 * gs.sin(angle) * mask_else + mask_pi rot_vec_not_pi = rot_vec_not_pi * numerator / denominator vector_outer = 0.5 * (gs.eye(3) + rot_mat) gs.set_diag( vector_outer, gs.maximum(0., gs.diagonal(vector_outer, axis1=1, axis2=2))) squared_diag_comp = gs.diagonal(vector_outer, axis1=1, axis2=2) diag_comp = gs.sqrt(squared_diag_comp) norm_line = gs.linalg.norm(vector_outer, axis=2) max_line_index = gs.argmax(norm_line, axis=1) selected_line = gs.get_slice(vector_outer, (range(n_rot_mats), max_line_index)) signs = gs.sign(selected_line) rot_vec_pi = angle * signs * diag_comp rot_vec = rot_vec_not_pi + mask_pi * rot_vec_pi return self.regularize(rot_vec)