eos 1.4.0
Loading...
Searching...
No Matches
pca.hpp
1/*
2 * eos - A 3D Morphable Model fitting library written in modern C++11/14.
3 *
4 * File: include/eos/pca/pca.hpp
5 *
6 * Copyright 2017 Patrik Huber
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20#pragma once
21
22#ifndef PCA_HPP_
23#define PCA_HPP_
24
25#include "eos/morphablemodel/PcaModel.hpp"
26
27#include "Eigen/Core"
28#include "Eigen/Eigenvalues"
29
30#include <array>
31#include <cassert>
32#include <utility>
33#include <vector>
34
35namespace eos {
36namespace pca {
37
41enum class Covariance {
42 AtA,
43 AAt
44};
45
76inline std::pair<Eigen::MatrixXf, Eigen::VectorXf> pca(const Eigen::Ref<const Eigen::MatrixXf> data,
77 Covariance covariance_type = Covariance::AtA)
78{
79 using Eigen::MatrixXf;
80 using Eigen::VectorXf;
81
82 MatrixXf cov;
83 if (covariance_type == Covariance::AtA)
84 {
85 cov = data.adjoint() * data;
86 } else if (covariance_type == Covariance::AAt)
87 {
88 cov = data * data.adjoint();
89 }
90
91 // The covariance is 1/(n-1) * AtA (or AAt), so divide by (num_samples - 1):
92 cov /= (data.rows() - 1);
93
94 const Eigen::SelfAdjointEigenSolver<MatrixXf> eig(cov);
95
96 const auto num_eigenvectors_to_keep = data.rows() - 1;
97
98 // Select the eigenvectors and eigenvalues that we want to keep, reverse them (from most significant to
99 // least): For 'n' data points, we get at most 'n - 1' non-zero eigenvalues.
100 VectorXf eigenvalues = eig.eigenvalues()
101 .bottomRows(num_eigenvectors_to_keep)
102 .reverse(); // eigenvalues() returns a column-vec
103 MatrixXf eigenvectors = eig.eigenvectors().rightCols(num_eigenvectors_to_keep).rowwise().reverse();
104
105 if (covariance_type == Covariance::AAt)
106 {
107 // Bring the AA^t variant in the right form by multiplying with A^t and 1/sqrt(eval):
108 // (see e.g. https://math.stackexchange.com/questions/787822/how-do-covariance-matrix-c-aat-justify-the-actual-ata-in-pca)
109 // (note the signs might be different from the AtA solution but that's not a problem as the sign of
110 // eigenvectors are arbitrary anyway)
111 eigenvectors = data.adjoint() * eigenvectors;
112
113 // Multiply each eigenvector (column) with one over the square root of its respective eigenvalue
114 // (1/sqrt(eigenvalue(i))): (this is a neat short-hand notation, see
115 // https://stackoverflow.com/a/42945996/1345959).
116 const VectorXf one_over_sqrt_eigenvalues = eigenvalues.array().rsqrt();
117 eigenvectors *= one_over_sqrt_eigenvalues.asDiagonal();
118
119 // Compensate for the covariance division by (n - 1) above:
120 eigenvectors /= std::sqrt(data.rows() - 1);
121 }
122
123 return { eigenvectors, eigenvalues };
124};
125
140inline std::pair<Eigen::MatrixXf, Eigen::VectorXf> pca(const Eigen::Ref<const Eigen::MatrixXf> data,
141 int num_eigenvectors_to_keep,
142 Covariance covariance_type = Covariance::AtA)
143{
144 using Eigen::MatrixXf;
145 using Eigen::VectorXf;
146
147 VectorXf eigenvalues;
148 MatrixXf eigenvectors;
149 std::tie(eigenvectors, eigenvalues) = pca(data, covariance_type);
150
151 // Reduce the basis and eigenvalues, and return:
152 assert(num_eigenvectors_to_keep <= eigenvectors.size());
153 return { eigenvectors.leftCols(num_eigenvectors_to_keep), eigenvalues.topRows(num_eigenvectors_to_keep) };
154};
155
170inline std::pair<Eigen::MatrixXf, Eigen::VectorXf> pca(const Eigen::Ref<const Eigen::MatrixXf> data,
171 float variance_to_keep,
172 Covariance covariance_type = Covariance::AtA)
173{
174 using Eigen::MatrixXf;
175 using Eigen::VectorXf;
176
177 assert(variance_to_keep >= 0.0f && variance_to_keep <= 1.0f);
178
179 VectorXf eigenvalues;
180 MatrixXf eigenvectors;
181 std::tie(eigenvectors, eigenvalues) = pca(data, covariance_type);
182
183 // Figure out how many coeffs to keep:
184 // variance_explained_by_first_comp = eigenval(1)/sum(eigenvalues)
185 // variance_explained_by_second_comp = eigenval(2)/sum(eigenvalues), etc.
186 auto num_eigenvectors_to_keep = eigenvalues.size(); // In the "worst" case we return all eigenvectors.
187 const auto total_sum = eigenvalues.sum();
188 float cum_sum = 0.0f;
189 for (int i = 0; i < eigenvalues.size(); ++i)
190 {
191 cum_sum += eigenvalues(i);
192 // If the current variation explained is larger or equal to the amount of variation that
193 // the user requested to keep, we're done:
194 if (cum_sum / total_sum >= variance_to_keep)
195 {
196 num_eigenvectors_to_keep = i + 1;
197 break;
198 }
199 }
200
201 // Reduce the basis and eigenvalues, and return:
202 assert(num_eigenvectors_to_keep <= eigenvectors.size());
203 return { eigenvectors.leftCols(num_eigenvectors_to_keep), eigenvalues.topRows(num_eigenvectors_to_keep) };
204};
205
221inline morphablemodel::PcaModel pca(const Eigen::Ref<const Eigen::MatrixXf> data,
222 std::vector<std::array<int, 3>> triangle_list,
223 Covariance covariance_type = Covariance::AtA)
224{
225 using Eigen::MatrixXf;
226 using Eigen::VectorXf;
227
228 // Compute the mean and mean-free data matrix:
229 // Each row is one instance of data (e.g. a 3D scan)
230 const VectorXf mean = data.colwise().mean();
231 const MatrixXf meanfree_data = data.rowwise() - mean.transpose();
232
233 // Carry out PCA and return the constructed model:
234 VectorXf eigenvalues;
235 MatrixXf eigenvectors;
236 std::tie(eigenvectors, eigenvalues) = pca(meanfree_data, covariance_type);
237
238 return morphablemodel::PcaModel(mean, eigenvectors, eigenvalues, triangle_list);
239};
240
241} /* namespace pca */
242} /* namespace eos */
243
244#endif /* PCA_HPP_ */
This class represents a PCA-model that consists of:
Definition: PcaModel.hpp:59
std::pair< Eigen::MatrixXf, Eigen::VectorXf > pca(const Eigen::Ref< const Eigen::MatrixXf > data, Covariance covariance_type=Covariance::AtA)
Compute PCA on a mean-centred data matrix, and return the eigenvectors and respective eigenvalues.
Definition: pca.hpp:76
Covariance
Definition: pca.hpp:41
@ AtA
Compute the traditional covariance matrix A^t*A.
@ AAt
Use the inner product, A*A^t, for the covariance matrix.
Namespace containing all of eos's 3D model fitting functionality.