def test_horizontal_projection(self): mat = self.bundle.random_point() vec = self.bundle.random_point() horizontal_vec = self.bundle.horizontal_projection(vec, mat) product = Matrices.mul(horizontal_vec, GeneralLinear.inverse(mat)) is_horizontal = Matrices.is_symmetric(product) self.assertTrue(is_horizontal)
def is_tangent(self, vector, base_point=None, atol=gs.atol): r"""Check if a vector is tangent to the manifold at the base point. Check if the (n,n)-matrix :math: `Y` is symmetric and verifies the relation :math: PY + YP = Y where :math: `P` represents the base point and :math: `Y` the vector. Parameters ---------- vector : array-like, shape=[..., n, n] Matrix to be checked. base_point : array-like, shape=[..., n, n] Base point. atol : int Optional, default: 1e-5. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if `vector` is tangent to the Grassmannian at `base_point`. """ diff = Matrices.mul(base_point, vector) + Matrices.mul( vector, base_point) - vector is_close = gs.all(gs.isclose(diff, 0., atol=atol)) return gs.logical_and(Matrices.is_symmetric(vector), is_close)
def belongs(self, point, atol=gs.atol): """Check if the point belongs to the manifold. Check if an (n,n)-matrix is an orthogonal projector onto a subspace of rank k. Parameters ---------- point : array-like, shape=[..., n, n] Point to be checked. atol : int Optional, default: 1e-5. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the Grassmannian. """ if not gs.all(self._check_square(point)): raise ValueError('all points must be square.') symm = Matrices.is_symmetric(point) idem = self._check_idempotent(point, atol) rank = self._check_rank(point, self.k, atol) belongs = gs.all(gs.stack([symm, idem, rank], axis=0), axis=0) return belongs
def inverse_transform(self, X): """Low-dimensional reconstruction of X. The reconstruction will match X_original whose transform would be X if `n_components=min(n_samples, n_features)`. Parameters ---------- X : array-like, shape=[n_samples, n_components] New data, where n_samples is the number of samples and n_components is the number of components. Returns ------- X_original array-like, shape=[n_samples, n_features] """ scores = self.mean_ + gs.matmul(X, self.components_) if self.point_type == 'matrix': if Matrices.is_symmetric(self.base_point_fit).all(): scores = SymmetricMatrices( self.base_point_fit.shape[-1]).from_vector(scores) else: dim = self.base_point_fit.shape[-1] scores = gs.reshape(scores, (len(scores), dim, dim)) return self.metric.exp(scores, self.base_point_fit)
def transform(self, X, y=None): """Project X on the principal components. Parameters ---------- X : array-like, shape=[..., n_features] Data, where n_samples is the number of samples and n_features is the number of features. y : Ignored (Compliance with scikit-learn interface) Returns ------- X_new : array-like, shape=[..., n_components] Projected data. """ tangent_vecs = self.metric.log(X, base_point=self.base_point_fit) if self.point_type == "matrix": if Matrices.is_symmetric(tangent_vecs).all(): X = SymmetricMatrices.to_vector(tangent_vecs) else: X = gs.reshape(tangent_vecs, (len(X), -1)) else: X = tangent_vecs X = X - self.mean_ X_transformed = gs.matmul(X, gs.transpose(self.components_)) return X_transformed
def test_alignment_is_symmetric(self, k_landmarks, m_ambient, point, base_point): space = self.space(k_landmarks, m_ambient) aligned = space.align(point, base_point) alignment = gs.matmul(Matrices.transpose(aligned), base_point) result = gs.all(Matrices.is_symmetric(alignment)) self.assertTrue(result)
def belongs(self, mat, atol=TOLERANCE): """Check if a matrix belongs to the manifold. Check if a matrix belongs to the manifold of symmetric positive definite matrices. """ return Matrices.is_symmetric(mat)
def transform(self, X, y=None): """Project X on the principal components. Parameters ---------- X : array-like, shape=[n_samples, n_features] Data, where n_samples is the number of samples and n_features is the number of features. y : Ignored (Compliance with scikit-learn interface) Returns ------- X_new : array-like, shape=[n_samples, n_components] """ tangent_vecs = self.metric.log(X, base_point=self.base_point_fit) if self.point_type == 'matrix': if Matrices.is_symmetric(tangent_vecs).all(): X = SymmetricMatrices.vector_from_symmetric_matrix( tangent_vecs) else: X = gs.reshape(tangent_vecs, (len(X), -1)) else: X = tangent_vecs return super(TangentPCA, self).transform(X)
def test_dist_pairwise_parallel(self): n_samples = 15 points = self.space.random_uniform(n_samples) result = self.metric.dist_pairwise(points, n_jobs=2, prefer="threads") is_sym = Matrices.is_symmetric(result) belongs = Matrices(n_samples, n_samples).belongs(result) self.assertTrue(is_sym) self.assertTrue(belongs)
def belongs(mat, atol=TOLERANCE): """Check if a matrix is symmetric and invertible.""" # TODO (opeltre): check positivity, implying invertibility. # # note : vectorized "and" on numpy works with: # [bool] * [bool] -> bool # but does not on tf. return Matrices.is_symmetric(mat)
def vector_from_symmetric_matrix(mat): """Convert the symmetric part of a symmetric matrix into a vector.""" if not gs.all(Matrices.is_symmetric(mat)): logging.warning('non-symmetric matrix encountered.') mat = Matrices.make_symmetric(mat) _, dim, _ = mat.shape i, j = gs.triu_indices(dim) vec = mat[:, i, j] return vec
def test_dist_pairwise_parallel(self): gs.random.seed(0) n_samples = 2 group = self.matrix_so3 metric = InvariantMetric(group=group) points = group.random_uniform(n_samples) result = metric.dist_pairwise(points, n_jobs=2) is_sym = Matrices.is_symmetric(result) belongs = Matrices(n_samples, n_samples).belongs(result) self.assertTrue(is_sym) self.assertTrue(belongs)
def to_vector(mat): """Convert the symmetric part of a symmetric matrix into a vector.""" if not gs.all(Matrices.is_symmetric(mat)): logging.warning('non-symmetric matrix encountered.') mat = Matrices.to_symmetric(mat) _, dim, _ = mat.shape indices_i, indices_j = gs.triu_indices(dim) vec = [] for i, j in zip(indices_i, indices_j): vec.append(mat[:, i, j]) vec = gs.stack(vec, axis=1) return vec
def belongs(self, mat, atol=TOLERANCE): """Check if mat belongs to the vector space of symmetric matrices. Parameters ---------- mat : array-like, shape=[..., n, n] Matrix to check. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if mat is a symmetric matrix. """ check_shape = self.embedding_manifold.belongs(mat) return gs.logical_and(check_shape, Matrices.is_symmetric(mat, atol))
def to_vector(mat): """Convert a symmetric matrix into a vector. Parameters ---------- mat : array-like, shape=[..., n, n] Matrix. Returns ------- vec : array-like, shape=[..., n(n+1)/2] Vector. """ if not gs.all(Matrices.is_symmetric(mat)): logging.warning("non-symmetric matrix encountered.") mat = Matrices.to_symmetric(mat) return gs.triu_to_vec(mat)
def transform(self, X, base_point=None): """Lift data to a tangent space. Compute the logs of all data point and reshapes them to 1d vectors if necessary. By default the logs are taken at the mean but any other base point can be passed. Any machine learning algorithm can then be used with the output array. Parameters ---------- X : array-like, shape=[..., {dim, [n, n]}] Data to transform. y : Ignored (Compliance with scikit-learn interface) base_point : array-like, shape={dim, [n,n]}, optional (mean) Point on the manifold, the returned samples will be tangent vectors at the base point. Returns ------- X_new : array-like, shape=[..., dim] Lifted data. """ if base_point is None: base_point = self.estimator.estimate_ if self.estimator.estimate_ is None: raise RuntimeError( "fit needs to be called first or a " "base_point passed." ) tangent_vecs = self._used_geometry.log(X, base_point=base_point) if self.point_type == "vector": return tangent_vecs if gs.all(Matrices.is_symmetric(tangent_vecs)): X = SymmetricMatrices.to_vector(tangent_vecs) elif gs.all(Matrices.is_skew_symmetric(tangent_vecs)): X = SkewSymmetricMatrices(tangent_vecs.shape[-1]).basis_representation( tangent_vecs ) else: X = gs.reshape(tangent_vecs, (len(X), -1)) return X
def inverse_transform(self, X, base_point=None): """Reconstruction of X. The reconstruction will match X_original whose transform would be X. Parameters ---------- X : array-like, shape=[..., dim] New data, where dim is the dimension of the manifold data belong to. base_point : array-like, shape={dim, [n,n]}, optional (mean) Point on the manifold, where the input samples are tangent vectors. Returns ------- X_original : array-like, shape=[..., {dim, [n, n]} Data lying on the manifold. """ if base_point is None: base_point = self.estimator.estimate_ if self.estimator.estimate_ is None: raise RuntimeError('fit needs to be called first or a ' 'base_point passed.') if self.point_type == 'matrix': n_base_point = base_point.shape[-1] n_vecs = X.shape[-1] dim_sym = int(n_base_point * (n_base_point + 1) / 2) dim_skew = int(n_base_point * (n_base_point - 1) / 2) if gs.all(Matrices.is_symmetric(base_point)) and dim_sym == n_vecs: tangent_vecs = SymmetricMatrices( base_point.shape[-1]).from_vector(X) elif dim_skew == n_vecs: tangent_vecs = SkewSymmetricMatrices( dim_skew).matrix_representation(X) else: dim = base_point.shape[-1] tangent_vecs = gs.reshape(X, (len(X), dim, dim)) else: tangent_vecs = X return self._used_geometry.exp(tangent_vecs, base_point)
def belongs(self, point, atol=gs.atol): """Evaluate if a matrix is symmetric. Parameters ---------- point : array-like, shape=[.., n, n] Point to test. atol : float Tolerance to evaluate equality with the transpose. Returns ------- belongs : array-like, shape=[...,] Boolean evaluating if point belongs to the space. """ belongs = super(SymmetricMatrices, self).belongs(point) if gs.any(belongs): is_symmetric = Matrices.is_symmetric(point, atol) return gs.logical_and(belongs, is_symmetric) return belongs
def is_horizontal(self, tangent_vec, base_point, atol=gs.atol): """Check whether the tangent vector is horizontal at base_point. Parameters ---------- tangent_vec : array-like, shape=[..., k_landmarks, m_ambient] Tangent vector. base_point : array-like, shape=[..., k_landmarks, m_ambient] Point on the manifold. Optional, default: none. atol : float Absolute tolerance. Optional, default: backend atol. Returns ------- is_tangent : bool Boolean denoting if tangent vector is horizontal. """ product = gs.matmul(Matrices.transpose(tangent_vec), base_point) is_tangent = self.is_tangent(tangent_vec, base_point, atol) is_symmetric = Matrices.is_symmetric(product, atol) return gs.logical_and(is_tangent, is_symmetric)
def to_vector(mat): """Convert a symmetric matrix into a vector. Parameters ---------- mat : array-like, shape=[..., n, n] Matrix. Returns ------- vec : array-like, shape=[..., n(n+1)/2] Vector. """ if not gs.all(Matrices.is_symmetric(mat)): logging.warning('non-symmetric matrix encountered.') mat = Matrices.to_symmetric(mat) _, dim, _ = mat.shape indices_i, indices_j = gs.triu_indices(dim) vec = [] for i, j in zip(indices_i, indices_j): vec.append(mat[:, i, j]) vec = gs.stack(vec, axis=1) return vec
class TestMatrices(geomstats.tests.TestCase): def setUp(self): gs.random.seed(1234) self.m = 2 self.n = 3 self.space = Matrices(m=self.n, n=self.n) self.space_nonsquare = Matrices(m=self.m, n=self.n) self.metric = self.space.metric self.n_samples = 2 @geomstats.tests.np_only def test_mul(self): a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) c = gs.array([ [1., 0., 0.], [0., 1., 0.], [0., 0., 0.]]) d = gs.array([ [0., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) result = self.space.mul([a, b], [b, a]) expected = gs.array([c, d]) self.assertAllClose(result, expected) result = self.space.mul(a, [a, b]) expected = gs.array([gs.eye(3, 3, 2), c]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_bracket(self): x = gs.array([ [0., 0., 0.], [0., 0., -1.], [0., 1., 0.]]) y = gs.array([ [0., 0., 1.], [0., 0., 0.], [-1., 0., 0.]]) z = gs.array([ [0., -1., 0.], [1., 0., 0.], [0., 0., 0.]]) result = self.space.bracket([x, y], [y, z]) expected = gs.array([z, x]) self.assertAllClose(result, expected) result = self.space.bracket(x, [x, y, z]) expected = gs.array([gs.zeros((3, 3)), z, -y]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_transpose(self): tr = self.space.transpose ar = gs.array a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) self.assertAllClose(tr(a), b) self.assertAllClose(tr(ar([a, b])), ar([b, a])) def test_is_symmetric(self): not_squared = gs.array([[1., 2.], [2., 1.], [3., 1.]]) result = self.space.is_symmetric(not_squared) expected = False self.assertAllClose(result, expected) sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.is_symmetric(sym_mat) expected = gs.array(True) self.assertAllClose(result, expected) not_a_sym_mat = gs.array([[1., 0.6, -3.], [6., -7., 0.], [0., 7., 8.]]) result = self.space.is_symmetric(not_a_sym_mat) expected = gs.array(False) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_is_skew_symmetric(self): skew_mat = gs.array([[0, - 2.], [2., 0]]) result = self.space.is_skew_symmetric(skew_mat) expected = gs.array(True) self.assertAllClose(result, expected) not_a_sym_mat = gs.array([[1., 0.6, -3.], [6., -7., 0.], [0., 7., 8.]]) result = self.space.is_skew_symmetric(not_a_sym_mat) expected = gs.array(False) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_is_symmetric_vectorization(self): points = gs.array([ [[1., 2.], [2., 1.]], [[3., 4.], [4., 5.]], [[1., 2.], [3., 4.]]]) result = self.space.is_symmetric(points) expected = [True, True, False] self.assertAllClose(result, expected) @geomstats.tests.np_and_pytorch_only def test_make_symmetric(self): sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.to_symmetric(sym_mat) expected = sym_mat self.assertAllClose(result, expected) mat = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.to_symmetric(mat) expected = gs.array([[1., 1., 3.], [1., 0., 0.5], [3., 0.5, 1.]]) self.assertAllClose(result, expected) mat = gs.array([[1e100, 1e-100, 1e100], [1e100, 1e-100, 1e100], [1e-100, 1e-100, 1e100]]) result = self.space.to_symmetric(mat) res = 0.5 * (1e100 + 1e-100) expected = gs.array([[1e100, res, res], [res, 1e-100, res], [res, res, 1e100]]) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_make_symmetric_and_is_symmetric_vectorization(self): points = gs.array([ [[1., 2.], [3., 4.]], [[5., 6.], [4., 9.]]]) sym_points = self.space.to_symmetric(points) result = gs.all(self.space.is_symmetric(sym_points)) expected = True self.assertAllClose(result, expected) def test_inner_product(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector_1 = gs.array([ [1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) tangent_vector_2 = gs.array([ [1., 4., 3.], [5., 0., 0.], [3., 1., 1.]]) result = self.metric.inner_product( tangent_vector_1, tangent_vector_2, base_point=base_point) expected = gs.trace( gs.matmul( gs.transpose(tangent_vector_1), tangent_vector_2)) self.assertAllClose(result, expected) def test_cong(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector = gs.array([ [1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) result = self.space.congruent(tangent_vector, base_point) expected = gs.matmul( tangent_vector, gs.transpose(base_point)) expected = gs.matmul(base_point, expected) self.assertAllClose(result, expected) def test_belongs(self): base_point_square = gs.zeros((self.n, self.n)) base_point_nonsquare = gs.zeros((self.m, self.n)) result = self.space.belongs(base_point_square) expected = True self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_square) expected = False self.assertAllClose(result, expected) result = self.space.belongs(base_point_nonsquare) expected = False self.assertAllClose(result, expected) result = self.space_nonsquare.belongs(base_point_nonsquare) expected = True self.assertAllClose(result, expected) result = self.space.belongs(gs.zeros((2, 2, 3))) self.assertFalse(gs.all(result)) result = self.space.belongs(gs.zeros((2, 3, 3))) self.assertTrue(gs.all(result)) def test_is_diagonal(self): base_point = gs.array([ [1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.is_diagonal(base_point) expected = False self.assertAllClose(result, expected) diagonal = gs.eye(3) result = self.space.is_diagonal(diagonal) self.assertTrue(result) base_point = gs.stack([base_point, diagonal]) result = self.space.is_diagonal(base_point) expected = gs.array([False, True]) self.assertAllClose(result, expected) base_point = gs.stack([diagonal] * 2) result = self.space.is_diagonal(base_point) self.assertTrue(gs.all(result)) base_point = gs.reshape(gs.arange(6), (2, 3)) result = self.space.is_diagonal(base_point) self.assertTrue(~result) def test_norm(self): for n_samples in [1, 2]: mat = self.space.random_point(n_samples) result = self.metric.norm(mat) expected = self.space.frobenius_product(mat, mat) ** .5 self.assertAllClose(result, expected)
def test_horizontal_projection(self, n, mat, vec): bundle = self.bundle(n) horizontal_vec = bundle.horizontal_projection(vec, mat) product = Matrices.mul(horizontal_vec, GeneralLinear.inverse(mat)) is_horizontal = Matrices.is_symmetric(product) self.assertTrue(is_horizontal)
def test_align(self): point, base_point = self.space.random_point(2) aligned = self.space.align(point, base_point) alignment = gs.matmul(Matrices.transpose(aligned), base_point) result = Matrices.is_symmetric(alignment) self.assertTrue(result)
def belongs(self, mat, atol=TOLERANCE): """Check if mat belongs to the vector space of symmetric matrices.""" check_shape = self.embedding_manifold.belongs(mat) return gs.logical_and(check_shape, Matrices.is_symmetric(mat, atol))
class TestMatricesMethods(geomstats.tests.TestCase): def setUp(self): gs.random.seed(1234) self.n = 3 self.space = Matrices(m=self.n, n=self.n) self.metric = self.space.metric self.n_samples = 2 @geomstats.tests.np_only def test_mul(self): a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) c = gs.array([[1., 0., 0.], [0., 1., 0.], [0., 0., 0.]]) d = gs.array([[0., 0., 0.], [0., 1., 0.], [0., 0., 1.]]) result = self.space.mul([a, b], [b, a]) expected = gs.array([c, d]) self.assertAllClose(result, expected) result = self.space.mul(a, [a, b]) expected = gs.array([gs.eye(3, 3, 2), c]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_bracket(self): x = gs.array([[0., 0., 0.], [0., 0., -1.], [0., 1., 0.]]) y = gs.array([[0., 0., 1.], [0., 0., 0.], [-1., 0., 0.]]) z = gs.array([[0., -1., 0.], [1., 0., 0.], [0., 0., 0.]]) result = self.space.bracket([x, y], [y, z]) expected = gs.array([z, x]) self.assertAllClose(result, expected) result = self.space.bracket(x, [x, y, z]) expected = gs.array([gs.zeros((3, 3)), z, -y]) self.assertAllClose(result, expected) @geomstats.tests.np_only def test_transpose(self): tr = self.space.transpose ar = gs.array a = gs.eye(3, 3, 1) b = gs.eye(3, 3, -1) self.assertAllClose(tr(a), b) self.assertAllClose(tr(ar([a, b])), ar([b, a])) @geomstats.tests.np_only def test_is_symmetric(self): sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.is_symmetric(sym_mat) expected = gs.array(True) self.assertAllClose(result, expected) not_a_sym_mat = gs.array([[1., 0.6, -3.], [6., -7., 0.], [0., 7., 8.]]) result = self.space.is_symmetric(not_a_sym_mat) expected = gs.array(False) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_is_symmetric_vectorization(self): points = gs.array([[[1., 2.], [2., 1.]], [[3., 4.], [4., 5.]]]) result = gs.all(self.space.is_symmetric(points)) expected = True self.assertAllClose(result, expected) @geomstats.tests.np_and_pytorch_only def test_make_symmetric(self): sym_mat = gs.array([[1., 2.], [2., 1.]]) result = self.space.make_symmetric(sym_mat) expected = sym_mat self.assertAllClose(result, expected) mat = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) result = self.space.make_symmetric(mat) expected = gs.array([[1., 1., 3.], [1., 0., 0.5], [3., 0.5, 1.]]) self.assertAllClose(result, expected) mat = gs.array([[1e100, 1e-100, 1e100], [1e100, 1e-100, 1e100], [1e-100, 1e-100, 1e100]]) result = self.space.make_symmetric(mat) res = 0.5 * (1e100 + 1e-100) expected = gs.array([[1e100, res, res], [res, 1e-100, res], [res, res, 1e100]]) self.assertAllClose(result, expected) @geomstats.tests.np_and_tf_only def test_make_symmetric_and_is_symmetric_vectorization(self): points = gs.array([[[1., 2.], [3., 4.]], [[5., 6.], [4., 9.]]]) sym_points = self.space.make_symmetric(points) result = gs.all(self.space.is_symmetric(sym_points)) expected = True self.assertAllClose(result, expected) def test_inner_product(self): base_point = gs.array([[1., 2., 3.], [0., 0., 0.], [3., 1., 1.]]) tangent_vector_1 = gs.array([[1., 2., 3.], [0., -10., 0.], [30., 1., 1.]]) tangent_vector_2 = gs.array([[1., 4., 3.], [5., 0., 0.], [3., 1., 1.]]) result = self.metric.inner_product(tangent_vector_1, tangent_vector_2, base_point=base_point) expected = gs.trace( gs.matmul(gs.transpose(tangent_vector_1), tangent_vector_2)) expected = helper.to_scalar(expected) self.assertAllClose(result, expected)
def _fit(self, X, base_point=None): """Fit the model by computing full SVD on X. Parameters ---------- X : array-like, shape=[..., n_features] Training data, where n_samples is the number of samples and n_features is the number of features. y : Ignored (Compliance with scikit-learn interface) base_point : array-like, shape=[..., n_features] Point at which to perform the tangent PCA. Optional, default to Frechet mean if None. Returns ------- U, S, V : array-like Matrices of the SVD decomposition """ if base_point is None: mean = FrechetMean(metric=self.metric, point_type=self.point_type) mean.fit(X) base_point = mean.estimate_ tangent_vecs = self.metric.log(X, base_point=base_point) if self.point_type == 'matrix': if Matrices.is_symmetric(tangent_vecs).all(): X = SymmetricMatrices.to_vector(tangent_vecs) else: X = gs.reshape(tangent_vecs, (len(X), -1)) else: X = tangent_vecs if self.n_components is None: n_components = min(X.shape) else: n_components = self.n_components n_samples, n_features = X.shape if n_components == 'mle': if n_samples < n_features: raise ValueError("n_components='mle' is only supported " "if n_samples >= n_features") elif not 0 <= n_components <= min(n_samples, n_features): raise ValueError("n_components=%r must be between 0 and " "min(n_samples, n_features)=%r with " "svd_solver='full'" % (n_components, min(n_samples, n_features))) elif n_components >= 1: if not isinstance(n_components, numbers.Integral): raise ValueError("n_components=%r must be of type int " "when greater than or equal to 1, " "was of type=%r" % (n_components, type(n_components))) # Center data - the mean should be 0 if base_point is the Frechet mean self.mean_ = gs.mean(X, axis=0) X -= self.mean_ U, S, V = gs.linalg.svd(X, full_matrices=False) # flip eigenvectors' sign to enforce deterministic output U, V = svd_flip(U, V) components_ = V # Get variance explained by singular values explained_variance_ = (S**2) / (n_samples - 1) total_var = explained_variance_.sum() explained_variance_ratio_ = explained_variance_ / total_var singular_values_ = gs.copy(S) # Store the singular values. # Postprocess the number of components required if n_components == 'mle': n_components = \ _infer_dimension_(explained_variance_, n_samples, n_features) elif 0 < n_components < 1.0: # number of components for which the cumulated explained # variance percentage is superior to the desired threshold ratio_cumsum = stable_cumsum(explained_variance_ratio_) n_components = gs.searchsorted(ratio_cumsum, n_components) + 1 # Compute noise covariance using Probabilistic PCA model # The sigma2 maximum likelihood (cf. eq. 12.46) if n_components < min(n_features, n_samples): self.noise_variance_ = explained_variance_[n_components:].mean() else: self.noise_variance_ = 0. self.base_point_fit = base_point self.n_samples_, self.n_features_ = n_samples, n_features self.components_ = components_[:n_components] self.n_components_ = int(n_components) self.explained_variance_ = explained_variance_[:n_components] self.explained_variance_ratio_ = \ explained_variance_ratio_[:n_components] self.singular_values_ = singular_values_[:n_components] return U, S, V