This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,212 @@
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//************************************************
//******************** Matrix ********************
//************************************************
//---------------------------------------------------------------------------------------------------
Eigen::MatrixXd AffineTransformation(const Eigen::MatrixXd& ref, const Eigen::MatrixXd& matrix)
{
const Eigen::MatrixXd isR = ref.sqrt().inverse(); // Inverse Square root of Reference matrix => isR
return isR * matrix * isR.transpose(); // Affine transformation : isR * sample * isR^T
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardization(Eigen::MatrixXd& matrix, const EStandardization standard)
{
if (standard == EStandardization::Center) { return MatrixCenter(matrix); }
if (standard == EStandardization::StandardScale) { return MatrixStandardization(matrix); }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardization(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, const EStandardization standard)
{
out = in;
return MatrixStandardization(out, standard);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixCenter(Eigen::MatrixXd& matrix)
{
for (size_t i = 0, r = matrix.rows(), c = matrix.cols(); i < r; ++i)
{
const double mu = matrix.row(i).mean();
for (size_t j = 0; j < c; ++j) { matrix(i, j) -= mu; }
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixCenter(const Eigen::MatrixXd& in, Eigen::MatrixXd& out)
{
out = in;
return MatrixCenter(out);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(Eigen::MatrixXd& matrix)
{
Eigen::RowVectorXd dummyScale;
return MatrixStandardScaler(matrix, dummyScale);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(Eigen::MatrixXd& matrix, Eigen::RowVectorXd& scale)
{
const size_t r = matrix.rows(), c = matrix.cols();
std::vector<double> mu(r, 0), sigma(r, 0);
scale.resize(r);
for (size_t i = 0; i < r; ++i)
{
for (size_t j = 0; j < c; ++j)
{
const double value = matrix(i, j);
mu[i] += value;
sigma[i] += value * value;
}
mu[i] /= double(c);
sigma[i] = sigma[i] / double(c) - mu[i] * mu[i];
scale[i] = sigma[i] == 0 ? 1 : sqrt(sigma[i]);
for (size_t j = 0; j < c; ++j) { matrix(i, j) = (matrix(i, j) - mu[i]) / scale[i]; }
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, Eigen::RowVectorXd& scale)
{
out = in;
return MatrixStandardScaler(out, scale);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MatrixStandardScaler(const Eigen::MatrixXd& in, Eigen::MatrixXd& out)
{
Eigen::RowVectorXd dummyScale;
return MatrixStandardScaler(in, out, dummyScale);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
std::string MatrixPrint(const Eigen::MatrixXd& matrix)
{
std::stringstream sstream;
sstream << matrix;
return sstream.str();
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AreEquals(const Eigen::MatrixXd& matrix1, const Eigen::MatrixXd& matrix2, const double precision)
{
return matrix1.size() == matrix2.size() && (matrix1.size() == 0 || matrix1.isApprox(matrix2, precision));
}
//---------------------------------------------------------------------------------------------------
//************************************************
//************************************************
//************************************************
//*************************************************************
//******************** Index Manipulations ********************
//*************************************************************
//---------------------------------------------------------------------------------------------------
Eigen::RowVectorXd GetElements(const Eigen::RowVectorXd& row, const std::vector<size_t>& index)
{
const size_t k = index.size();
Eigen::RowVectorXd result(k);
for (size_t i = 0; i < k; ++i) { result[i] = row[index[i]]; }
return result;
}
//---------------------------------------------------------------------------------------------------
//*************************************************************
//*************************************************************
//*************************************************************
//***************************************************
//******************** Validates ********************
//***************************************************
//---------------------------------------------------------------------------------------------------
bool InRange(const double value, const double min, const double max) { return (min <= value && value <= max); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AreNotEmpty(const std::vector<Eigen::MatrixXd>& matrices)
{
if (matrices.empty()) { return false; }
for (const auto& m : matrices) { if (!IsNotEmpty(m)) { return false; } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool IsNotEmpty(const Eigen::MatrixXd& matrix) { return (matrix.size() != 0); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool HaveSameSize(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return (IsNotEmpty(a) && a.rows() == b.rows() && a.cols() == b.cols()); }
//---------------------------------------------------------------------------------------------------
bool IsSquare(const Eigen::MatrixXd& matrix) { return (IsNotEmpty(matrix) && matrix.rows() == matrix.cols()); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AreSquare(const std::vector<Eigen::MatrixXd>& matrices)
{
if (matrices.empty()) { return false; }
for (const auto& m : matrices) { if (!IsSquare(m)) { return false; } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool HaveSameSize(const std::vector<Eigen::MatrixXd>& matrices)
{
if (matrices.empty()) { return false; }
const size_t r = matrices[0].rows(), c = matrices[0].cols();
for (const auto& m : matrices) { if (size_t(m.rows()) != r || size_t(m.cols()) != c) { return false; } }
return true;
}
//---------------------------------------------------------------------------------------------------
//***************************************************
//***************************************************
//***************************************************
//********************************************************
//******************** CSV MANAGEMENT ********************
//********************************************************
//---------------------------------------------------------------------------------------------------
std::vector<std::string> Split(const std::string& s, const std::string& sep)
{
std::vector<std::string> result;
std::string::size_type i = 0, j;
const std::string::size_type n = sep.size();
while ((j = s.find(sep, i)) != std::string::npos)
{
result.emplace_back(s, i, j - i); // Add part
i = j + n; // Update pos
}
result.emplace_back(s, i, s.size() - 1 - i); // Last without \n
return result;
}
//---------------------------------------------------------------------------------------------------
//********************************************************
//********************************************************
//********************************************************
} // namespace Geometry
@@ -0,0 +1,93 @@
#include "geometry/Classification.hpp"
#include "geometry/Covariance.hpp"
#include "geometry/Basics.hpp"
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool LSQR(const std::vector<std::vector<Eigen::RowVectorXd>>& dataset, Eigen::MatrixXd& weight)
{
// Precomputation
if (dataset.empty()) { return false; }
const size_t nbClass = dataset.size(), nbFeatures = dataset[0][0].size();
std::vector<size_t> nbSample(nbClass);
size_t totalSample = 0;
for (size_t k = 0; k < nbClass; ++k)
{
if (dataset[k].empty()) { return false; }
nbSample[k] = dataset[k].size();
totalSample += nbSample[k];
}
// Compute Class Euclidian mean
Eigen::MatrixXd mean = Eigen::MatrixXd::Zero(nbClass, nbFeatures);
for (size_t k = 0; k < nbClass; ++k)
{
for (size_t i = 0; i < nbSample[k]; ++i) { mean.row(k) += dataset[k][i]; }
mean.row(k) /= double(nbSample[k]);
}
// Compute Class Covariance
Eigen::MatrixXd cov = Eigen::MatrixXd::Zero(nbFeatures, nbFeatures);
for (size_t k = 0; k < nbClass; ++k)
{
//Fit Data to existing covariance matrix method
Eigen::MatrixXd classData(nbFeatures, nbSample[k]);
for (size_t i = 0; i < nbSample[k]; ++i) { classData.col(i) = dataset[k][i]; }
// Standardize Features
Eigen::RowVectorXd scale;
MatrixStandardScaler(classData, scale);
//Compute Covariance of this class
Eigen::MatrixXd classCov;
if (!CovarianceMatrix(classData, classCov, EEstimator::LWF)) { return false; }
// Rescale
for (size_t i = 0; i < nbFeatures; ++i) { for (size_t j = 0; j < nbFeatures; ++j) { classCov(i, j) *= scale[i] * scale[j]; } }
//Add to cov with good weight
cov += (double(nbSample[k]) / double(totalSample)) * classCov;
}
// linear least squares systems solver
// Chosen solver with the performance table of this page : https://eigen.tuxfamily.org/dox/group__TutorialLinearAlgebra.html
weight = cov.colPivHouseholderQr().solve(mean.transpose()).transpose();
//weight = cov.completeOrthogonalDecomposition().solve(mean.transpose()).transpose();
//weight = cov.bdcSvd(ComputeThinU | ComputeThinV).solve(mean.transpose()).transpose();
// Treat binary case as a special case
if (nbClass == 2)
{
const Eigen::MatrixXd tmp = weight.row(1) - weight.row(0); // Need to use a tmp variable otherwise sometimes error
weight = tmp;
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool FgDACompute(const std::vector<std::vector<Eigen::RowVectorXd>>& dataset, Eigen::MatrixXd& weight)
{
// Compute LSQR Weight
Eigen::MatrixXd w;
if (!LSQR(dataset, w)) { return false; }
const size_t nbClass = w.rows();
// Transform to FgDA Weight
const Eigen::MatrixXd wT = w.transpose();
weight = (wT * (w * wT).colPivHouseholderQr().solve(Eigen::MatrixXd::Identity(nbClass, nbClass))) * w;
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool FgDAApply(const Eigen::RowVectorXd& in, Eigen::RowVectorXd& out, const Eigen::MatrixXd& weight)
{
if (in.cols() != weight.rows()) { return false; }
out = in * weight;
return true;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,172 @@
# include "geometry/Covariance.hpp"
#include <algorithm> // std::min/max
namespace Geometry {
//***********************************************************
//******************** COVARIANCES BASES ********************
//***********************************************************
//---------------------------------------------------------------------------------------------------
double Variance(const Eigen::RowVectorXd& x)
{
const size_t S = x.cols(); // Number of Samples => S
if (S == 0) { return 0; } // If false input
const double mu = x.mean();
return x.cwiseProduct(x).sum() / S - mu * mu;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double Covariance(const Eigen::RowVectorXd& x, const Eigen::RowVectorXd& y)
{
const size_t xS = x.cols(), yS = y.cols(); // Number of Samples => S
if (xS == 0 || xS != yS) { return 0; } // If false input
return (x.cwiseProduct(y).sum() - x.sum() * y.sum() / xS) / xS;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool ShrunkCovariance(Eigen::MatrixXd& cov, const double shrinkage)
{
if (!InRange(shrinkage, 0, 1)) { return false; } // Verification
const size_t n = cov.rows(); // Number of Features => N
const double coef = shrinkage * cov.trace() / n; // Diagonal Coefficient
cov = (1 - shrinkage) * cov; // Shrinkage
for (size_t i = 0; i < n; ++i) { cov(i, i) += coef; } // Add Diagonal Coefficient
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool ShrunkCovariance(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, const double shrinkage)
{
out = in;
return ShrunkCovariance(out, shrinkage);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrix(const Eigen::MatrixXd& in, Eigen::MatrixXd& out, const EEstimator estimator, const EStandardization standard)
{
if (!IsNotEmpty(in)) { return false; } // Verification
Eigen::MatrixXd sample;
MatrixStandardization(in, sample, standard); // Standardization
switch (estimator) // Switch Method
{
case EEstimator::COV: return CovarianceMatrixCOV(sample, out);
case EEstimator::SCM: return CovarianceMatrixSCM(sample, out);
case EEstimator::LWF: return CovarianceMatrixLWF(sample, out);
case EEstimator::OAS: return CovarianceMatrixOAS(sample, out);
case EEstimator::MCD: return CovarianceMatrixMCD(sample, out);
case EEstimator::COR: return CovarianceMatrixCOR(sample, out);
default: return CovarianceMatrixIDE(sample, out);
}
}
//---------------------------------------------------------------------------------------------------
//***********************************************************
//***********************************************************
//***********************************************************
//***********************************************************
//******************** COVARIANCES TYPES ********************
//***********************************************************
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixCOV(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(); // Number of Features => N
cov.resize(n, n); // Init size of matrix
for (size_t i = 0; i < n; ++i)
{
const Eigen::RowVectorXd ri = samples.row(i);
cov(i, i) = Variance(ri); // Diagonal Value
for (size_t j = i + 1; j < n; ++j)
{
const Eigen::RowVectorXd rj = samples.row(j);
cov(i, j) = cov(j, i) = Covariance(ri, rj); // Symetric covariance
}
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixSCM(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
cov = samples * samples.transpose(); // X*X^T
cov /= cov.trace(); // X*X^T / trace(X*X^T)
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixLWF(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(), S = samples.cols(); // Number of Features & Samples => N & S
CovarianceMatrixCOV(samples, cov); // Initial Covariance Matrix => Cov
const double mu = cov.trace() / n;
Eigen::MatrixXd mDelta = cov; // mDelta = cov - mu * I_n
for (size_t i = 0; i < n; ++i) { mDelta(i, i) -= mu; }
const Eigen::MatrixXd x2 = samples.cwiseProduct(samples), // Squared each sample => X^2
cov2 = cov.cwiseProduct(cov); // Squared each element of Cov => Cov^2
const double delta = mDelta.cwiseProduct(mDelta).sum() / n,
beta = 1. / double(n * S) * (x2 * x2.transpose() / double(S) - cov2).sum(),
shrinkage = std::min(beta, delta) / delta; // Assure shrinkage <= 1
return ShrunkCovariance(cov, shrinkage); // Shrinkage of the matrix
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixOAS(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(), S = samples.cols(); // Number of Features & Samples => N & S
CovarianceMatrixCOV(samples, cov); // Initial Covariance Matrix => Cov
// Compute Shrinkage : Formula from Chen et al.'s
const double mu = cov.trace() / n,
mu2 = mu * mu,
alpha = cov.cwiseProduct(cov).mean(),
num = alpha + mu2,
den = (S + 1) * (alpha - mu2 / n),
shrinkage = (den == 0) ? 1.0 : std::min(num / den, 1.0);
return ShrunkCovariance(cov, shrinkage); // Shrinkage of the matrix
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixMCD(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov) { return CovarianceMatrixIDE(samples, cov); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixCOR(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
const size_t n = samples.rows(); // Number of Features => N
CovarianceMatrixCOV(samples, cov); // Initial Covariance Matrix => Cov
const Eigen::MatrixXd d = cov.diagonal().cwiseSqrt(); // Squared root of diagonal
for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < n; ++j) { cov(i, j) /= d(i) * d(j); } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool CovarianceMatrixIDE(const Eigen::MatrixXd& samples, Eigen::MatrixXd& cov)
{
cov = Eigen::MatrixXd::Identity(samples.rows(), samples.rows());
return true;
}
//---------------------------------------------------------------------------------------------------
//***********************************************************
//***********************************************************
//***********************************************************
} // namespace Geometry
@@ -0,0 +1,68 @@
#include "geometry/Distance.hpp"
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions>
namespace Geometry {
//---------------------------------------------------------------------------------------------------
double Distance(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, const EMetric metric)
{
if (!HaveSameSize(a, b)) { return 0; }
switch (metric)
{
case EMetric::Riemann: return DistanceRiemann(a, b);
case EMetric::Euclidian: return DistanceEuclidian(a, b);
case EMetric::LogEuclidian: return DistanceLogEuclidian(a, b);
case EMetric::LogDet: return DistanceLogDet(a, b);
case EMetric::Kullback: return DistanceKullbackSym(a, b);
case EMetric::Wasserstein: return DistanceWasserstein(a, b);
case EMetric::Identity:
default: return 1.0;
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceRiemann(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
const Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es(a, b);
const Eigen::ArrayXd result = es.eigenvalues();
return sqrt(result.log().square().sum());
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return (b - a).norm(); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceLogEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return DistanceEuclidian(a.log(), b.log()); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceLogDet(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
return sqrt(log((0.5 * (a + b)).determinant()) - 0.5 * log(a.determinant() * b.determinant()));
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceKullback(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
return 0.5 * ((b.inverse() * a).trace() - a.rows() + log(b.determinant() / a.determinant()));
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceKullbackSym(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b) { return DistanceKullback(a, b) + DistanceKullback(b, a); }
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
double DistanceWasserstein(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b)
{
const Eigen::MatrixXd sB = b.sqrt();
return sqrt((a + b - 2 * (sB * a * sB).sqrt()).trace());
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,92 @@
#include "geometry/Featurization.hpp"
#include <unsupported/Eigen/MatrixFunctions>
#include "geometry/Basics.hpp"
namespace Geometry {
#ifndef M_SQRT2
#define M_SQRT2 1.4142135623730950488016887242097
#endif
//---------------------------------------------------------------------------------------------------
bool Featurization(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, const bool tangent, const Eigen::MatrixXd& ref)
{
if (tangent) { return TangentSpace(in, out, ref); }
return SqueezeUpperTriangle(in, out, true);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool UnFeaturization(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, const bool tangent, const Eigen::MatrixXd& ref)
{
if (tangent) { return UnTangentSpace(in, out, ref); }
return UnSqueezeUpperTriangle(in, out, true);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool SqueezeUpperTriangle(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, const bool rowMajor)
{
if (!IsSquare(in)) { return false; } // Verification
const size_t n = in.rows(); // Number of Features => N
out.resize(n * (n + 1) / 2); // Resize
size_t idx = 0; // Row Index => idx
// Row Major or Diagonal Method
if (rowMajor) { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out[idx++] = in(i, j); } } }
else { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out[idx++] = in(j, j - i); } } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool UnSqueezeUpperTriangle(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, const bool rowMajor)
{
const size_t nR = in.size(), // Size of Row => Nr
n = int((sqrt(1 + 8 * nR) - 1) / 2); // Number of Features => N
if (n == 0) { return false; } // Verification
out.setZero(n, n); // Init
size_t idx = 0; // Row Index => idx
// Row Major or Diagonal Method
if (rowMajor) { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out(j, i) = out(i, j) = in[idx++]; } } }
else { for (size_t i = 0; i < n; ++i) { for (size_t j = i; j < n; ++j) { out(j - i, j) = out(j, j - i) = in[idx++]; } } }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool TangentSpace(const Eigen::MatrixXd& in, Eigen::RowVectorXd& out, const Eigen::MatrixXd& ref)
{
if (!IsSquare(in)) { return false; } // Verification
const size_t n = in.rows(); // Number of Features => N
const Eigen::MatrixXd sC = (ref.size() == 0) ? Eigen::MatrixXd::Identity(n, n) : Eigen::MatrixXd(ref.sqrt()),
isC = sC.inverse(), // Inverse Square root of ref => isC
mJ = (isC * in * isC).log(), // Transformation Matrix => mJ
mCoeffs = M_SQRT2 * Eigen::MatrixXd(Eigen::MatrixXd::Ones(n, n).triangularView<Eigen::StrictlyUpper>())
+ Eigen::MatrixXd::Identity(n, n);
Eigen::RowVectorXd vJ, vCoeffs;
if (!SqueezeUpperTriangle(mJ, vJ, true)) { return false; } // Get upper triangle of J => vJ
if (!SqueezeUpperTriangle(mCoeffs, vCoeffs, true)) { return false; } // ... of Coefs => vCoeffs
out = vCoeffs.cwiseProduct(vJ); // element-wise multiplication
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool UnTangentSpace(const Eigen::RowVectorXd& in, Eigen::MatrixXd& out, const Eigen::MatrixXd& ref)
{
const size_t n = out.rows(); // Number of Features => N
if (!UnSqueezeUpperTriangle(in, out)) { return false; }
const Eigen::MatrixXd sC = (ref.size() == 0) ? Eigen::MatrixXd::Identity(n, n) : Eigen::MatrixXd(ref.sqrt()),
coeffs = Eigen::MatrixXd(out.triangularView<Eigen::StrictlyUpper>()) / M_SQRT2;
out = sC * (Eigen::MatrixXd(out.diagonal().asDiagonal()) + coeffs + coeffs.transpose()).exp() * sC;
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,57 @@
#include "geometry/Geodesic.hpp"
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions>
namespace Geometry {
//---------------------------------------------------------------------------------------------------
bool Geodesic(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const EMetric metric, const double alpha)
{
if (!HaveSameSize(a, b)) { return false; } // Verification same size
if (!IsSquare(a)) { return false; } // Verification square matrix
if (!InRange(alpha, 0, 1)) { return false; } // Verification alpha in [0;1]
switch (metric) // Switch metric
{
case EMetric::Riemann: return GeodesicRiemann(a, b, g, alpha);
case EMetric::Euclidian: return GeodesicEuclidian(a, b, g, alpha);
case EMetric::LogEuclidian: return GeodesicLogEuclidian(a, b, g, alpha);
case EMetric::Identity:
default: return GeodesicIdentity(a, b, g, alpha);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicRiemann(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const double alpha)
{
const Eigen::MatrixXd sA = a.sqrt(), isA = sA.inverse();
g = sA * (isA * b * isA).pow(alpha) * sA;
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const double alpha)
{
g = (1 - alpha) * a + alpha * b;
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicLogEuclidian(const Eigen::MatrixXd& a, const Eigen::MatrixXd& b, Eigen::MatrixXd& g, const double alpha)
{
g = ((1 - alpha) * a.log() + alpha * b.log()).exp();
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool GeodesicIdentity(const Eigen::MatrixXd& a, const Eigen::MatrixXd& /*b*/, Eigen::MatrixXd& g, const double /*alpha*/)
{
g = Eigen::MatrixXd::Identity(a.rows(), a.rows());
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,227 @@
#include "geometry/Metrics.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Geodesic.hpp"
#include "geometry/Distance.hpp"
#include "geometry/Mean.hpp"
#include <unsupported/Eigen/MatrixFunctions>
#include <iostream>
namespace Geometry {
//static const double EPSILON = 0.000000001; // 10^{-9}
static const double EPSILON = 0.0001; // 10^{-4}
static const size_t ITER_MAX = 50;
//---------------------------------------------------------------------------------------------------
bool Mean(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean, const EMetric metric)
{
if (covs.empty()) { return false; } // If no matrix in vector
if (covs.size() == 1) // If just one matrix in vector
{
mean = covs[0];
return true;
}
if (!HaveSameSize(covs))
{
std::cout << "Matrices haven't same size." << std::endl;
return false;
}
// Force Square Matrix for non Euclidian and non Identity metric
if (!IsSquare(covs[0]) && (metric != EMetric::Euclidian && metric != EMetric::Identity))
{
std::cout << "Non Square Matrix is invalid with " << toString(metric) << " metric." << std::endl;
return false;
}
switch (metric) // Switch method
{
case EMetric::Riemann: return MeanRiemann(covs, mean);
case EMetric::Euclidian: return MeanEuclidian(covs, mean);
case EMetric::LogEuclidian: return MeanLogEuclidian(covs, mean);
case EMetric::LogDet: return MeanLogDet(covs, mean);
case EMetric::Kullback: return MeanKullback(covs, mean);
case EMetric::ALE: return MeanALE(covs, mean);
case EMetric::Harmonic: return MeanHarmonic(covs, mean);
case EMetric::Wasserstein: return MeanWasserstein(covs, mean);
case EMetric::Identity:
default: return MeanIdentity(covs, mean);
}
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool AJDPham(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& ajd, double /*epsilon*/, const int /*maxIter*/)
{
MeanIdentity(covs, ajd);
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanRiemann(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double nu = 1.0, // Coefficient change => nu
tau = std::numeric_limits<double>::max(), // Coefficient change criterion => tau
crit = std::numeric_limits<double>::max(); // Current change => crit
if (!MeanEuclidian(covs, mean)) { return false; } // Initial Mean
while (i < ITER_MAX && EPSILON < crit && EPSILON < nu) // Stopping criterion
{
i++; // Iteration Criterion
const Eigen::MatrixXd sC = mean.sqrt(), isC = sC.inverse(); // Square root & Inverse Square root of Mean => sC & isC
Eigen::MatrixXd mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (isC * cov * isC).log(); } // Sum of log(isC*Ci*isC)
mJ /= double(k); // Normalization
crit = mJ.norm(); // Current change criterion
mean = sC * (nu * mJ).exp() * sC; // Update Mean => M = sC * exp(nu*J) * sC
const double h = nu * crit; // Update Coefficient change
if (h < tau)
{
nu *= 0.95;
tau = h;
}
else { nu *= 0.5; }
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanEuclidian(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
mean = Eigen::MatrixXd::Zero(n, n); // Initial Mean
for (const auto& cov : covs) { mean += cov; } // Sum of Ci
mean /= double(k); // Normalization
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanLogEuclidian(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
mean = Eigen::MatrixXd::Zero(n, n); // Initial Mean
for (const auto& cov : covs) { mean += cov.log(); } // Sum of log(Ci)
mean = (mean / double(k)).exp(); // Normalization
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanLogDet(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double crit = std::numeric_limits<double>::max(); // Current change => crit
if (!MeanEuclidian(covs, mean)) { return false; } // Initial Mean
while (i < ITER_MAX && EPSILON < crit) // Stopping criterion
{
i++; // Iteration Criterion
Eigen::MatrixXd mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (0.5 * (cov + mean)).inverse(); } // Sum of ((Ci+M)/2)^{-1}
mJ = (mJ / double(k)).inverse(); // Normalization
crit = (mJ - mean).norm(); // Current change criterion
mean = mJ; // Update mean
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanKullback(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
Eigen::MatrixXd m1, m2;
if (!MeanEuclidian(covs, m1)) { return false; }
if (!MeanHarmonic(covs, m2)) { return false; }
if (!GeodesicRiemann(m1, m2, mean, 0.5)) { return false; }
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanWasserstein(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double crit = std::numeric_limits<double>::max(); // Current change => crit
if (!MeanEuclidian(covs, mean)) { return false; } // Initial Mean
Eigen::MatrixXd sC = mean.sqrt(); // Square root of Mean => sC
while (i < ITER_MAX && EPSILON < crit) // Stopping criterion
{
i++; // Iteration Criterion
Eigen::MatrixXd mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (sC * cov * sC).sqrt(); } // Sum of sqrt(sC*Ci*sC)
mJ /= double(k); // Normalization
const Eigen::MatrixXd sJ = mJ.sqrt(); // Square root of change => sJ
crit = (sJ - sC).norm(); // Current change criterion
sC = sJ; // Update sC
}
mean = sC * sC; // Un-square root
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanALE(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
size_t i = 0; // Index of Covariance Matrix => i
double crit = std::numeric_limits<double>::max(); // Change criterion => crit
if (!AJDPham(covs, mean)) { return false; } // Initial Mean
Eigen::MatrixXd mJ; // Change
while (i < ITER_MAX && EPSILON < crit) // Stopping criterion
{
i++; // Iteration Criterion
mJ = Eigen::MatrixXd::Zero(n, n); // Change => J
for (const auto& cov : covs) { mJ += (mean.transpose() * cov * mean).log(); } // Sum of log(C^T*Ci*C)
mJ /= double(k); // Normalization
Eigen::MatrixXd update = mJ.exp().diagonal().asDiagonal(); // Update Form => U
mean = mean * update.sqrt().inverse(); // Update Mean M = M * U^{-1/2}
crit = DistanceRiemann(Eigen::MatrixXd::Identity(n, n), update);
}
mJ = Eigen::MatrixXd::Zero(n, n); // Last Change => J
for (const auto& cov : covs) { mJ += (mean.transpose() * cov * mean).log(); } // Sum of log(C^T*Ci*C)
mJ /= double(k); // Normalization
Eigen::MatrixXd mA = mean.inverse();
mean = mA.transpose() * mJ.exp() * mA;
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanHarmonic(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
const size_t k = covs.size(), n = covs[0].rows(); // Number of Matrix & Features => K & N
mean = Eigen::MatrixXd::Zero(n, n); // Initial Mean
for (const auto& cov : covs) { mean += cov.inverse(); } // Sum of Inverse
mean = (mean / double(k)).inverse(); // Normalization and inverse
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MeanIdentity(const std::vector<Eigen::MatrixXd>& covs, Eigen::MatrixXd& mean)
{
mean = Eigen::MatrixXd::Identity(covs[0].rows(), covs[0].cols());
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,151 @@
#include "geometry/Median.hpp"
#include <iostream>
#include "geometry/Basics.hpp"
#include "geometry/Featurization.hpp"
#include "geometry/Mean.hpp"
namespace Geometry {
//---------------------------------------------------------------------------------------------------
double Median(const Eigen::MatrixXd& m)
{
const std::vector<double> v(m.data(), m.data() + m.rows() * m.cols());
return Median(v);
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool Median(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon, const size_t maxIter, const EMetric& metric)
{
if (matrices.empty()) { return false; } // If no matrix in vector
if (matrices.size() == 1) // If just one matrix in vector
{
median = matrices[0];
return true;
}
if (!HaveSameSize(matrices)) // If different sizes
{
std::cout << "Matrices have different sizes." << std::endl;
return false;
}
if (!IsSquare(matrices[0]) && metric == EMetric::Riemann) // If non square for Riemann metric
{
std::cout << "Non Square Matrix is invalid with " << toString(metric) << " metric." << std::endl;
return false;
}
switch (metric)
{
case EMetric::Riemann: return MedianRiemann(matrices, median, epsilon, maxIter);
case EMetric::Euclidian: return MedianEuclidian(matrices, median, epsilon, maxIter);
case EMetric::Identity: return MedianIdentity(matrices, median);
case EMetric::LogEuclidian:
case EMetric::LogDet:
case EMetric::Kullback:
case EMetric::ALE:
case EMetric::Harmonic:
case EMetric::Wasserstein:
std::cout << toString(metric) << " metric not implemented." << std::endl;
return false;
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MedianEuclidian(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon, const size_t maxIter)
{
if (matrices.empty() || matrices[0].size() == 0) { return false; }
const size_t n = matrices.size(); // Number of sample
// Initial Median is the median of each channel in all matrix of dataset
median = matrices[0]; // to copy size
for (size_t i = 0; i < size_t(median.size()); ++i)
{
std::vector<double> tmp;
tmp.reserve(n); // Reserve to optimize (a little) the pushback memory access.
for (const auto& cov : matrices) { tmp.push_back(cov.data()[i]); } // Stack value number i of all matrix
median.data()[i] = Median(tmp);
}
size_t iter = 0; // number of iteration
double gain = epsilon; // Gain since last compute
while (iter < maxIter && gain >= epsilon)
{
Eigen::MatrixXd prev = median; // Keep old median
median.setZero(); // Reset median
double sumCoefs = 0; // Sum of Coefficient
for (const auto& cov : matrices)
{
//Eigen::MatrixXd difference = cov - prev;
//double coef = sqrt(difference.cwiseProduct(difference).sum());
if (cov.isApprox(prev)) { continue; } // In this case, Median is exactly this current matrix so we don't consider this matrix
double coef = (cov - prev).norm();
// Personnal hack and security
coef = 1.0 / coef;
sumCoefs += coef; // Sum for normalization
median += coef * cov; // Add to the new median
}
if (sumCoefs > 0.0) { median /= sumCoefs; } // Normalize
gain = (median - prev).norm() / median.norm(); // It's the Frobenius norm
iter++;
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MedianRiemann(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median, const double epsilon, const size_t maxIter)
{
if (matrices.empty() || !IsSquare(matrices[0])) { return false; }
const size_t n = matrices.size(); // Number of sample
const size_t nf = matrices[0].rows() * (matrices[0].rows() + 1) / 2; // Number of Features in tangent space
size_t iter = 0; // number of iteration
if (!MeanEuclidian(matrices, median)) { return false; } // Initialize Median
double gain = epsilon; // Gain since last compute
std::vector<Eigen::MatrixXd> mats;
mats.reserve(n);
for (const auto& m : matrices) { mats.push_back(m); }
while (iter < maxIter)
{
// Compute Tangent space of all matrices & sum of euclidian distance of each transposed matrix
std::vector<Eigen::RowVectorXd> ts(n);
double sum = 0.0;
for (size_t i = 0; i < n; ++i)
{
if (!TangentSpace(mats[i], ts[i], median)) { return false; }
sum += sqrt(ts[i].cwiseAbs2().sum());
}
if (std::abs((sum - gain) / gain) < epsilon) { break; } // std::abs call fabs to keep type
// Arithmetic median in tangent space
std::vector<std::vector<double>> transposeTs(nf, std::vector<double>(n));
Eigen::RowVectorXd featureMedian(nf);
for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < nf; ++j) { transposeTs[j][i] = ts[i][j]; } }
for (size_t j = 0; j < nf; ++j) { featureMedian[j] = Median(transposeTs[j]); }
// back to the manifold
Eigen::MatrixXd tmp;
if (!UnTangentSpace(featureMedian, tmp, median)) { return false; }
gain = sum; // Update gain
median = tmp; // Update Median
iter++;
}
return true;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool MedianIdentity(const std::vector<Eigen::MatrixXd>& matrices, Eigen::MatrixXd& median)
{
median = Eigen::MatrixXd::Identity(matrices[0].rows(), matrices[0].cols());
return true;
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,243 @@
#include "geometry/Misc.hpp"
#include "geometry/Featurization.hpp"
#include <boost/math/special_functions/gamma.hpp>
#include <numeric> // std::iota
namespace Geometry {
///-------------------------------------------------------------------------------------------------
/// <summary> Get the sign of the specified value. </summary>
/// <param name="x"> The value. </param>
/// <returns> <c>1</c> if <c>x > 0</c>, <c>0</c> if <c>x == 0</c>, <c>-1</c> if <c>x < 0</c>. </returns>
template <typename T>
int sgn(T x) { return (T(0) < x) - (x < T(0)); }
///-------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
/// <summary> Structure used for iota function for range of double. </summary>
struct SDoubleIota
{
explicit SDoubleIota(const double init = 0.0, const double inc = 1.0) : v(init), inc(inc) {}
operator double() const { return v; } // don't add explicit qualifier for iota functions (were template cast is used)
SDoubleIota& operator++()
{
v += inc;
return *this;
}
double v;
double inc;
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
/// <summary> Structure used for iota function for range of round index. </summary>
struct SRoundIndex
{
explicit SRoundIndex(const double init = 0.0, const double inc = 1.0) : v(init), inc(inc) {}
operator size_t() const { return size_t(std::round(v)); } // don't add explicit qualifier for iota functions (were template cast is used)
SRoundIndex& operator++()
{
v += inc;
return *this;
}
double v;
double inc;
};
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
std::vector<double> doubleRange(const double begin, const double end, const double step, const bool closed)
{
std::vector<double> res;
if (end < begin) { return res; }
const double size = (end - begin) / step;
// check if the end is inclued in range for size, we ceil for no modulo values and add the last value to the range
res.resize(size_t(std::ceil((closed && std::trunc(size) == size) ? size + 1 : size)));
std::iota(res.begin(), res.end(), SDoubleIota(begin, step));
return res;
}
//---------------------------------------------------------------------------------------------------
std::vector<size_t> RoundIndexRange(const double begin, const double end, const double step, const bool closed, const bool unique)
{
std::vector<size_t> res;
if (end < begin) { return res; }
const double size = (end - begin) / step;
// check if the end is inclued in range for size, we ceil for no modulo values and add the last value to the range
res.resize(size_t(std::ceil((closed && std::trunc(size) == size) ? size + 1 : size)));
std::iota(res.begin(), res.end(), SRoundIndex(begin, step));
if (unique)
{
const auto last = std::unique(res.begin(), res.end()); // Remove duplicate values (but after last, we have undefined value)
res.erase(last, res.end()); // Resize Vector (we erase after last)
}
return res;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
std::vector<size_t> BinHist(const std::vector<double>& dataset, const size_t n)
{
std::vector<size_t> res(n, 0);
const double max = *std::max_element(dataset.begin(), dataset.end());
if (max == 0) { return res; } // if max is 0, coef can't be compute
const double coef = n / max;
for (const auto& data : dataset)
{
const size_t bin = size_t(std::floor(data * coef));
if (bin < n) { res[bin]++; }
else if (bin == n) { res[n - 1]++; } // if this data is equal to max
}
return res;
}
//---------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------
bool FitDistribution(const std::vector<double>& values, double& mu, double& sigma, const std::vector<double>& betas, const double minQuant,
const double maxQuant, const double minClean, const double maxDropout, const double stepBound, const double stepScale)
{
if (values.empty() || betas.empty() || minQuant < 0 || minQuant > 1 || maxQuant < 0 || maxQuant > 1 || minClean < 0 || maxDropout < 0
|| stepBound < 0.0001 || stepBound > 0.1 || stepScale < 0.0001 || stepScale > 0.1) { return false; }
//========== Scales ==========
const size_t nBeta = betas.size();
// Scales is a vector for each beta as :
// scale = beta/(2*gamma(1/beta)) with gamma the function as gamma(n) = (n-1)! for all integer greater than 0
std::vector<double> scales;
scales.reserve(nBeta);
std::transform(betas.begin(), betas.end(), std::back_inserter(scales), [](const double beta) -> double { return beta / (2 * tgamma(1 / beta)); });
//========== zBounds ==========
// zBounds is a vector of lower and upper bounds for each beta as : sign(quants-1/2) * gammaincinv(sign(quants-1/2) * (2*quants-1), 1/beta)^(1/beta);
// with gammaincinv the Inverse incomplete gamma function, here quants are the quantiles limit (by default [0.022 0.6])
std::vector<std::vector<double>> zBounds(nBeta);
const int signMin = sgn(minQuant - 0.5), signMax = sgn(maxQuant - 0.5);
const double coefMin = signMin * (2 * minQuant - 1), coefMax = signMax * (2 * maxQuant - 1);
for (size_t i = 0; i < nBeta; ++i)
{
if (betas[i] == 0) { zBounds[i] = { 0, 0 }; }
else
{
const double beta = 1 / betas[i];
zBounds[i] = {
signMin * pow(boost::math::gamma_p_inv(beta, coefMin), beta),
signMax * pow(boost::math::gamma_p_inv(beta, coefMax), beta)
};
}
}
//========== Sort Values ==========
// We sort values to access quantiles directly
const size_t n = values.size();
std::vector<double> newValues = values;
std::sort(newValues.begin(), newValues.end());
//========== Compute Index range ==========
// Width are the limit if all data is clean or artifacted. It's usefull for the for loop limit and step for each width possible
// Bounds are the range of begining value used to compute mu and sigma. It's usefull for the for loop limit and step for first index of value to take
// We create Vector for widths and bounds to precompute all round and avoid duplicate indexes in widths or bounds
std::vector<size_t> widths = RoundIndexRange(n * (maxQuant - minQuant) * minClean, n * (maxQuant - minQuant), n * stepScale, true, false);
std::reverse(widths.begin(), widths.end());
const std::vector<size_t> bounds = RoundIndexRange(n * minQuant, n * (minQuant + maxDropout), n * stepBound, true, false);
const size_t maxWidth = std::max(widths.front(), widths.back()); // to prevent if widths is in descending or ascending order
const size_t nBound = bounds.size();
//========== Compute Grid (with index range) ==========
// Create the Biggest table of data with width in column and bound in row
std::vector<std::vector<double>> grid(nBound);
std::vector<double> firsts(nBound);
for (size_t i = 0; i < nBound; ++i)
{
grid[i].reserve(maxWidth);
const auto first = newValues.begin() + bounds[i];
std::copy_n(first, maxWidth, std::back_inserter(grid[i]));
firsts[i] = grid[i][0];
for (auto& e : grid[i]) { e -= firsts[i]; } // Substract first value on all element
}
//========== Width Loop ==========
double bestKl = std::numeric_limits<double>::max();
size_t bestBeta = 0, bestId = 0, bestWidth = 0;
// for each interval width...
for (const auto& w : widths)
{
const size_t nbins = size_t(std::round(3 * log2(1 + (double(w) / 2))));
//========== Compute Histogramm ==========
std::vector<std::vector<double>> hist(nBound);
for (size_t i = 0; i < nBound; ++i)
{
hist[i].reserve(nbins);
std::vector<size_t> tmp = BinHist(std::vector<double>(grid[i].begin(), grid[i].begin() + w), nbins);
std::transform(tmp.begin(), tmp.end(), std::back_inserter(hist[i]), [](const size_t e) -> double { return log(e + 0.01); });
}
//========== Beta Loop ==========
for (size_t b = 0; b < nBeta; ++b)
{
//========== Compute Probability ==========
std::vector<double> prob(nbins);
double sumprob = 0.0;
for (size_t i = 0; i < nbins; ++i)
{
prob[i] = std::exp(-std::pow(std::abs(zBounds[b][0] + (((i + 0.5) / nbins) * (zBounds[b][1] - zBounds[b][0]))), betas[b])) * scales[b];
sumprob += prob[i];
}
if (sumprob != 0) { for (auto& p : prob) { p /= sumprob; } }
//========== Compute the Kullback-Leibler divergences ==========
//kl = sum(prob * (log(prob) - hist)) + log(w));
std::vector<double> kl(nBound, log(w));
for (size_t i = 0; i < nBound; ++i) { for (size_t j = 0; j < nbins; ++j) { kl[i] += prob[j] * (log(prob[j]) - hist[i][j]); } }
// Update Parameters
auto minIt = std::min_element(kl.begin(), kl.end());
if (*minIt < bestKl)
{
bestKl = *minIt;
bestBeta = b;
bestId = minIt - kl.begin();
bestWidth = w - 1;
}
}
}
double alpha = grid[bestId][bestWidth] / (zBounds[bestBeta][1] - zBounds[bestBeta][0]);
double beta = betas[bestBeta];
mu = firsts[bestId] - zBounds[bestBeta][0] * alpha;
sigma = sqrt(alpha * alpha * std::tgamma(3 / beta) / std::tgamma(1 / beta));
return true;
}
//---------------------------------------------------------------------------------------------------
void sortedEigenVector(const Eigen::MatrixXd& matrix, Eigen::MatrixXd& vectors, std::vector<double>& values, const EMetric /*metric*/)
{
// Compute Eigen Vector/Values
const Eigen::EigenSolver<Eigen::MatrixXd> es(matrix);
const Eigen::MatrixXd tmpVec = es.eigenvectors().real(); // It's complex by default but all imaginary part are 0
const Eigen::MatrixXd tmpVal = es.eigenvalues().real(); // It's complex by default but all imaginary part are 0
values = std::vector<double>(tmpVal.data(), tmpVal.data() + tmpVal.size());
// Get order of eigen values.
std::vector<size_t> idx(values.size());
std::iota(idx.begin(), idx.end(), 0);
std::stable_sort(idx.begin(), idx.end(), [&values](const size_t i1, const size_t i2) { return values[i1] < values[i2]; });
// Sort Eigen Values
std::stable_sort(values.begin(), values.end());
// Sort Eigen Vector
vectors = tmpVec; // copy matrix to set size easily
for (size_t i = 0; i < size_t(tmpVec.cols()); ++i) { vectors.col(i) = tmpVec.col(idx[i]); }
}
//---------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,278 @@
#include "geometry/artifacts/CASR.hpp"
#include "geometry/Misc.hpp"
#include "geometry/Median.hpp"
#include "geometry/Covariance.hpp"
#include "geometry/Mean.hpp"
#include "geometry/classifier/IMatrixClassifier.hpp"
#include <boost/math/special_functions/detail/igamma_inverse.hpp>
#include <unsupported/Eigen/MatrixFunctions>
#include <cmath>
#include <numeric>
#include <iostream>
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool CASR::train(const std::vector<Eigen::MatrixXd>& dataset, const double rejectionLimit)
{
if (dataset.empty() || dataset[0].size() == 0) { return false; }
const size_t n = dataset.size(); // Number of samples
m_nChannel = dataset[0].rows(); // Number of channels
//========== Compute the covariance matrix ==========
std::vector<Eigen::MatrixXd> covs(n);
//for (size_t i = 0; i < n; ++i) { if (!CovarianceMatrixLWF(dataset[i], covs[i])) { return false; } } // We assume data is centered
for (size_t i = 0; i < n; ++i) { if (!CovarianceMatrix(dataset[i], covs[i], EEstimator::LWF, EStandardization::Center)) { return false; } }
//========== Compute Square Root of Median ==========
if (!Median(covs, m_median)) { return false; } // Geometric median independant of metric
m_median = m_median.sqrt();
//========== Compute Eigen vectors ==========
Eigen::MatrixXd eigVector;
std::vector<double> eigValues;
sortedEigenVector(m_median, eigVector, eigValues, m_metric); //Actually only Euclidian metric is implemented
//========== Compute the ponderate dataset ==========
std::vector<Eigen::MatrixXd> newDataset;
newDataset.reserve(n);
for (const auto& m : dataset) { newDataset.push_back((m.transpose() * eigVector)); } // Multiply by eigen vector (we transpose to have channels in column
for (auto& m : newDataset) { m = m.cwiseProduct(m); } // Square new signal
//========== Compute the "fit" distribution ==========
// Compute the RMS of each channel for each sample
std::vector<std::vector<double>> rms(m_nChannel, std::vector<double>(n));
for (size_t i = 0; i < n; ++i) { for (size_t j = 0; j < m_nChannel; ++j) { rms[j][i] = sqrt(newDataset[i].col(j).mean()); } }
// Compute the "fit" distribution
std::vector<double> mu(m_nChannel, 0.0), sigma(m_nChannel, 0.0);
for (size_t i = 0; i < m_nChannel; ++i) { FitDistribution(rms[i], mu[i], sigma[i]); }
// Compute the threshold Matrix
m_threshold = Eigen::MatrixXd::Zero(m_nChannel, m_nChannel);
for (size_t i = 0; i < m_nChannel; ++i) { m_threshold(i, i) = mu[i] + rejectionLimit * sigma[i]; }
m_threshold *= eigVector.transpose();
// Initialize Reconstruction matrix and trivial
m_r = Eigen::MatrixXd::Identity(m_nChannel, m_nChannel);
m_trivial = true;
return true;
}
bool CASR::process(const Eigen::MatrixXd& in, Eigen::MatrixXd& out)
{
// Check if input data is compatible with training data and if we don't limit so much the reconstruction
out = in;
if (size_t(out.rows()) != m_nChannel) { return false; }
const size_t begin = size_t((1.0 - m_maxChannel) * double(m_nChannel)); // We define the number of channels to non reconstruct
if (begin == m_nChannel) { return true; }
if (m_r.size() == 0) { m_r = Eigen::MatrixXd::Identity(m_nChannel, m_nChannel); }
// Compute Covariance matrix
Eigen::MatrixXd cov;
if (!CovarianceMatrix(in, cov, EEstimator::LWF, EStandardization::Center)) { return false; }
if (m_cov.size() == 0) { m_cov = cov; } // if first time
else { if (!Mean({ m_cov, cov }, m_cov, m_metric)) { return false; } } // else mean of the both
// Compute Eigen vector & values
Eigen::MatrixXd eigVector;
std::vector<double> eigValues;
sortedEigenVector(m_cov, eigVector, eigValues, m_metric);
// Check if eigen values is over threshold computed during train (ponderated by eigen vector)
Eigen::MatrixXd threshold = (m_threshold * eigVector).cwiseAbs2();
bool trivial = true;
std::vector<bool> keep(m_nChannel, true);
for (size_t i = begin; i < m_nChannel; ++i)
{
if (eigValues[i] >= threshold.col(i).sum())
{
keep[i] = false;
trivial = false;
}
}
// Check if All channels are clean
if (trivial) { m_r = Eigen::MatrixXd::Identity(m_nChannel, m_nChannel); }
else // if not...
{
// Compute the reconstruction matrix with bad channels
Eigen::MatrixXd tmp = eigVector.transpose() * m_median;
for (size_t i = begin; i < m_nChannel; ++i) { if (!keep[i]) { tmp.row(i).setZero(); } }
const Eigen::MatrixXd newR = m_median * tmp.completeOrthogonalDecomposition().pseudoInverse() * eigVector.transpose();
if (!m_trivial)
{
// Compute blend values for the samples
const size_t nSample = in.cols();
std::vector<double> blend(nSample);
std::iota(blend.begin(), blend.end(), 1); // Range 1 to nSample (inclued)
for (auto& b : blend) { b = (1 - cos(M_PI * (b / double(nSample)))) / 2.0; }
// Apply reconstruction ponderate by the blend (we considere the old reconstruction matrix for the second part)
Eigen::MatrixXd t1 = newR * in;
Eigen::MatrixXd t2 = m_r * in;
for (size_t i = 0; i < nSample; ++i) { out.col(i) = (blend[i] * t1.col(i)) + ((1 - blend[i]) * t2.col(i)); }
}
m_r = newR; // Update the reconstruction matrix
}
m_trivial = trivial;
return true;
}
///-------------------------------------------------------------------------------------------------
bool CASR::setMatrices(const Eigen::MatrixXd& median, const Eigen::MatrixXd& threshold, const Eigen::MatrixXd& reconstruct,
const Eigen::MatrixXd& covariance)
{
if (!IsSquare(median) || !HaveSameSize(median, threshold)
|| (reconstruct.size() != 0 && !HaveSameSize(median, reconstruct))
|| (covariance.size() != 0 && !HaveSameSize(median, covariance)))
{
std::cout << "All matrices must be square with same size (or empty for reconstruct and covariance matrix" << std::endl;
return false;
}
m_nChannel = median.rows();
m_median = median;
m_threshold = threshold;
m_r = reconstruct.size() != 0 ? reconstruct : Eigen::MatrixXd::Identity(m_nChannel, m_nChannel);
m_cov = covariance;
m_trivial = true;
return true;
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CASR::saveXML(const std::string& filename) const
{
tinyxml2::XMLDocument doc;
// Create Root
tinyxml2::XMLNode* root = doc.NewElement("ASR"); // Create root node
doc.InsertFirstChild(root); // Add root to XML
tinyxml2::XMLElement* data = doc.NewElement("ASR-data"); // Create data node
data->SetAttribute("metric", toString(m_metric).c_str()); // Set attribute metric
data->SetAttribute("nChannel", int(m_nChannel)); // Set attribute nCHannel
data->SetAttribute("maxChannel", int(m_maxChannel)); // Set attribute nCHannel
data->SetAttribute("trivial", m_trivial); // Set attribute nCHannel
tinyxml2::XMLElement* median = doc.NewElement("Median"); // Create Median node
if (!IMatrixClassifier::saveMatrix(median, m_median)) { return false; } // Save Median Matrix
data->InsertEndChild(median); // Add Median node to data node
tinyxml2::XMLElement* threshold = doc.NewElement("Threshold"); // Create Median node
if (!IMatrixClassifier::saveMatrix(threshold, m_threshold)) { return false; } // Save Median Matrix
data->InsertEndChild(threshold); // Add Median node to data node
tinyxml2::XMLElement* r = doc.NewElement("R"); // Create Median node
if (!IMatrixClassifier::saveMatrix(r, m_r)) { return false; } // Save Median Matrix
data->InsertEndChild(r); // Add Median node to data node
tinyxml2::XMLElement* cov = doc.NewElement("Cov"); // Create Median node
if (!IMatrixClassifier::saveMatrix(cov, m_cov)) { return false; } // Save Median Matrix
data->InsertEndChild(cov); // Add Median node to data node
root->InsertEndChild(data); // Add data to root
return doc.SaveFile(filename.c_str()) == 0; // save XML (if != 0 it means error)
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CASR::loadXML(const std::string& filename)
{
// Load File
tinyxml2::XMLDocument xmlDoc;
if (xmlDoc.LoadFile(filename.c_str()) != 0) { return false; } // Check File Exist and Loading
// Load Root
tinyxml2::XMLNode* root = xmlDoc.FirstChild(); // Get Root Node
if (root == nullptr) { return false; } // Check Root Node Exist
// Load Data
tinyxml2::XMLElement* data = root->FirstChildElement("ASR-data"); // Get Data Node
if (data == nullptr) { return false; } // Check Root Node Exist
m_metric = StringToMetric(std::string(data->Attribute("metric")));
m_nChannel = data->IntAttribute("nChannel");
m_maxChannel = data->IntAttribute("maxChannel");
m_trivial = data->BoolAttribute("trivial");
tinyxml2::XMLElement* element = data->FirstChildElement("Median"); // Get Median Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_median)) { return false; } // Load Median Matrix
element = data->FirstChildElement("Threshold"); // Get Threshold Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_threshold)) { return false; } // Load Threshold Matrix
element = data->FirstChildElement("R"); // Get R Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_r)) { return false; } // Load R Matrix
element = data->FirstChildElement("Cov"); // Get Cov Node
if (element == nullptr) { return false; } // Check if Node Exist
if (!IMatrixClassifier::loadMatrix(element, m_cov)) { return false; } // Load Cov Matrix
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool CASR::isEqual(const CASR& obj, const double precision) const
{
return m_metric == obj.m_metric && m_nChannel == obj.m_nChannel
&& abs(m_maxChannel - obj.m_maxChannel) < precision && m_trivial == obj.m_trivial
&& AreEquals(m_median, obj.m_median, precision) && AreEquals(m_threshold, obj.m_threshold, precision)
&& AreEquals(m_r, obj.m_r, precision) && AreEquals(m_cov, obj.m_cov, precision);
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CASR::copy(const CASR& obj)
{
m_metric = obj.m_metric;
m_nChannel = obj.m_nChannel;
m_maxChannel = obj.m_maxChannel;
m_trivial = obj.m_trivial;
m_median = obj.m_median;
m_threshold = obj.m_threshold;
m_r = obj.m_r;
m_cov = obj.m_cov;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CASR::print() const
{
std::stringstream ss;
ss << "Metric : " << toString(m_metric) << std::endl;
if (m_nChannel == 0) { ss << "Training not done" << std::endl; }
else
{
ss << "Training done." << std::endl;
ss << size_t(m_maxChannel * double(m_nChannel)) << "/" << m_nChannel << " channels can be reconstruted." << std::endl;
ss << "Median matrix is : " << std::endl << m_median << std::endl;
ss << "Threshold matrix is : " << std::endl << m_threshold << std::endl;
if (m_cov.size() == 0) { ss << "No process launched yet." << std::endl; }
else
{
ss << "Last sample " << (m_trivial ? "was" : "wasn't") << " trivial." << std::endl;
ss << "Last Reconstruction Matrix : " << std::endl << m_r << std::endl;
ss << "Last Covariance Matrix : " << std::endl << m_cov << std::endl;
}
}
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,146 @@
#include "geometry/classifier/CBias.hpp"
#include "geometry/classifier/IMatrixClassifier.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Geodesic.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
#include <iostream>
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool CBias::computeBias(const std::vector<std::vector<Eigen::MatrixXd>>& dataset, const EMetric metric) { return computeBias(Vector2DTo1D(dataset), metric); }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::computeBias(const std::vector<Eigen::MatrixXd>& dataset, const EMetric metric)
{
if (!Mean(dataset, m_bias, metric)) { return false; } // Compute Bias reference
m_biasIS = m_bias.sqrt().inverse(); // Inverse Square root of Bias matrix => isR
m_n = 0;
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::applyBias(const std::vector<std::vector<Eigen::MatrixXd>>& in, std::vector<std::vector<Eigen::MatrixXd>>& out)
{
const size_t n = in.size();
out.resize(n);
for (size_t i = 0; i < n; ++i) { applyBias(in[i], out[i]); }
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::applyBias(const std::vector<Eigen::MatrixXd>& in, std::vector<Eigen::MatrixXd>& out)
{
const size_t n = in.size();
out.resize(n);
for (size_t i = 0; i < n; ++i) { applyBias(in[i], out[i]); }
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::applyBias(const Eigen::MatrixXd& in, Eigen::MatrixXd& out) { out = m_biasIS * in * m_biasIS.transpose(); }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::updateBias(const Eigen::MatrixXd& sample, const EMetric metric)
{
m_n++; // Update number of classify
if (m_n == 1) { m_bias = sample; } // At the first pass we reinitialize the Bias
else { Geodesic(m_bias, sample, m_bias, metric, 1.0 / m_n); }
m_biasIS = m_bias.sqrt().inverse(); // Inverse Square root of Bias matrix => isR
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::setBias(const Eigen::MatrixXd& bias)
{
m_bias = bias;
m_biasIS = m_bias.sqrt().inverse();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::saveXML(const std::string& filename) const
{
tinyxml2::XMLDocument xmlDoc;
// Create Root
tinyxml2::XMLNode* root = xmlDoc.NewElement("Bias"); // Create root node
xmlDoc.InsertFirstChild(root); // Add root to XML
tinyxml2::XMLElement* data = xmlDoc.NewElement("Bias-data"); // Create data node
if (!saveAdditional(xmlDoc, data)) { return false; } // Save Optionnal Informations
root->InsertEndChild(data); // Add data to root
return xmlDoc.SaveFile(filename.c_str()) == 0; // save XML (if != 0 it means error)
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::loadXML(const std::string& filename)
{
// Load File
tinyxml2::XMLDocument xmlDoc;
if (xmlDoc.LoadFile(filename.c_str()) != 0) { return false; } // Check File Exist and Loading
// Load Root
tinyxml2::XMLNode* root = xmlDoc.FirstChild(); // Get Root Node
if (root == nullptr) { return false; } // Check Root Node Exist
// Load Data
tinyxml2::XMLElement* data = root->FirstChildElement("Bias-data"); // Get Data Node
if (!loadAdditional(data)) { return false; } // Load Optionnal Informations
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
tinyxml2::XMLElement* bias = doc.NewElement("Bias"); // Create Bias node
bias->SetAttribute("n", int(m_n)); // Set attribute class number of trials
if (!IMatrixClassifier::saveMatrix(bias, m_bias)) { return false; } // Save class
data->InsertEndChild(bias); // Add class node to data node
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::loadAdditional(tinyxml2::XMLElement* data)
{
tinyxml2::XMLElement* bias = data->FirstChildElement("Bias"); // Get LDA Weight Node
m_n = bias->IntAttribute("n"); // Get the number of Trials for this class
if (!IMatrixClassifier::loadMatrix(bias, m_bias)) { return false; } // Load Reference Matrix
m_biasIS = m_bias.sqrt().inverse();
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CBias::isEqual(const CBias& obj, const double precision) const { return AreEquals(m_bias, obj.m_bias, precision) && m_n == obj.m_n; }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CBias::copy(const CBias& obj)
{
m_bias = obj.m_bias;
m_biasIS = obj.m_biasIS;
m_n = obj.m_n;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CBias::print() const
{
std::stringstream ss;
ss << "Number of Classification : " << m_n << std::endl;
ss << "Bias Matrix : ";
if (m_bias.size() != 0) { ss << std::endl << m_bias.format(MATRIX_FORMAT) << std::endl; }
else { ss << "Not Computed" << std::endl; }
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,41 @@
#include "geometry/classifier/CMatrixClassifierFgMDM.hpp"
#include "geometry/Mean.hpp"
namespace Geometry {
///-------------------------------------------------------------------------------------------------
CMatrixClassifierFgMDM::~CMatrixClassifierFgMDM()
{
for (auto& v : m_dataset) { v.clear(); }
m_dataset.clear();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDM::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
m_dataset = dataset;
return train();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDM::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!CMatrixClassifierFgMDMRT::classify(sample, classId, distance, probability, EAdaptations::None)) { return false; }
// Adaptation
if (adaptation == EAdaptations::None) { return true; }
// Get class id for adaptation and increase number of trials, expected if supervised, predicted if unsupervised
const size_t id = adaptation == EAdaptations::Supervised ? realClassId : classId;
if (id >= m_nbClass) { return false; } // Check id (if supervised and bad input)
m_nbTrials[id]++; // Update number of trials for the class id
m_dataset[id].push_back(sample); // Update the dataset
// Retrain
return train();
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,121 @@
#include "geometry/classifier/CMatrixClassifierFgMDMRT.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Featurization.hpp"
#include "geometry/Classification.hpp"
#include <iostream>
namespace Geometry {
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (dataset.empty()) { return false; }
if (!Mean(Vector2DTo1D(dataset), m_ref, EMetric::Riemann)) { return false; } // Compute Reference matrix
// Transform to the Tangent Space
const size_t nbClass = dataset.size();
std::vector<std::vector<Eigen::RowVectorXd>> tsSample(nbClass);
for (size_t k = 0; k < nbClass; ++k)
{
const size_t nbTrials = dataset[k].size();
tsSample[k].resize(nbTrials);
for (size_t i = 0; i < nbTrials; ++i) { if (!TangentSpace(dataset[k][i], tsSample[k][i], m_ref)) { return false; } }
}
// Compute FgDA Weight
if (!FgDACompute(tsSample, m_weight)) { return false; }
// Convert dataset
std::vector<std::vector<Eigen::MatrixXd>> newDataset(nbClass);
std::vector<std::vector<Eigen::RowVectorXd>> filtered(nbClass);
for (size_t k = 0; k < nbClass; ++k)
{
const size_t nbTrials = dataset[k].size();
newDataset[k].resize(nbTrials);
filtered[k].resize(nbTrials);
for (size_t i = 0; i < nbTrials; ++i)
{
if (!FgDAApply(tsSample[k][i], filtered[k][i], m_weight)) { return false; } // Apply Filter
if (!UnTangentSpace(filtered[k][i], newDataset[k][i], m_ref)) { return false; } // Return to Matrix Space
}
}
return CMatrixClassifierMDM::train(newDataset); // Train MDM
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
Eigen::RowVectorXd tsSample, filtered;
Eigen::MatrixXd newSample;
if (!TangentSpace(sample, tsSample, m_ref)) { return false; } // Transform to the Tangent Space
if (!FgDAApply(tsSample, filtered, m_weight)) { return false; } // Apply Filter
if (!UnTangentSpace(filtered, newSample, m_ref)) { return false; } // Return to Matrix Space
return CMatrixClassifierMDM::classify(newSample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::isEqual(const CMatrixClassifierFgMDMRT& obj, const double precision) const
{
if (!CMatrixClassifierMDM::isEqual(obj, precision)) { return false; } // Compare base members
if (!AreEquals(m_ref, obj.m_ref, precision)) { return false; } // Compare Reference
if (!AreEquals(m_weight, obj.m_weight, precision)) { return false; } // Compare Weight
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierFgMDMRT::copy(const CMatrixClassifierFgMDMRT& obj)
{
CMatrixClassifierMDM::copy(obj);
m_ref = obj.m_ref;
m_weight = obj.m_weight;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
// Save Reference
tinyxml2::XMLElement* reference = doc.NewElement("Reference"); // Create Reference node
if (!saveMatrix(reference, m_ref)) { return false; } // Save class
data->InsertEndChild(reference); // Add class node to data node
// Save Weight
tinyxml2::XMLElement* weight = doc.NewElement("Weight"); // Create LDA Weight node
if (!saveMatrix(weight, m_weight)) { return false; } // Save class
data->InsertEndChild(weight); // Add class node to data node
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRT::loadAdditional(tinyxml2::XMLElement* data)
{
// Load Reference
tinyxml2::XMLElement* ref = data->FirstChildElement("Reference"); // Get Reference Node
if (!loadMatrix(ref, m_ref)) { return false; } // Load Reference Matrix
// Load Weight
tinyxml2::XMLElement* weight = data->FirstChildElement("Weight"); // Get LDA Weight Node
return loadMatrix(weight, m_weight); // Load LDA Weight Matrix
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierFgMDMRT::printAdditional() const
{
std::stringstream ss;
ss << "Reference matrix : " << std::endl << m_ref.format(MATRIX_FORMAT) << std::endl; // Reference
ss << "Weight matrix : " << std::endl << m_weight.format(MATRIX_FORMAT) << std::endl; // Print Weight
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,85 @@
#include "geometry/classifier/CMatrixClassifierFgMDMRTRebias.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Covariance.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//**********************
//***** Classifier *****
//**********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (!m_bias.computeBias(dataset, m_metric)) { return false; }
std::vector<std::vector<Eigen::MatrixXd>> newDataset;
m_bias.applyBias(dataset, newDataset);
if (!CMatrixClassifierFgMDMRT::train(newDataset)) { return false; } // Train FgMDM
const Eigen::MatrixXd identity = Eigen::MatrixXd::Identity(m_ref.rows(), m_ref.cols()); // Identity matrix
if (AreEquals(m_ref, identity)) { m_ref = identity; } // Normally it's always the case with Identity matrix we simplify future operation
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!IsSquare(sample)) { return false; } // Verification if it's a square matrix
Eigen::MatrixXd newSample;
m_bias.applyBias(sample, newSample);
m_bias.updateBias(sample, m_metric);
return CMatrixClassifierFgMDMRT::classify(newSample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
if (!CMatrixClassifierFgMDMRT::saveAdditional(doc, data)) { return false; }
if (!m_bias.saveAdditional(doc, data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::loadAdditional(tinyxml2::XMLElement* data)
{
if (!CMatrixClassifierFgMDMRT::loadAdditional(data)) { return false; }
if (!m_bias.loadAdditional(data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierFgMDMRTRebias::isEqual(const CMatrixClassifierFgMDMRTRebias& obj, const double precision) const
{
return CMatrixClassifierFgMDMRT::isEqual(obj, precision) && m_bias == obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierFgMDMRTRebias::copy(const CMatrixClassifierFgMDMRTRebias& obj)
{
CMatrixClassifierFgMDMRT::copy(obj);
m_bias = obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierFgMDMRTRebias::printAdditional() const
{
std::stringstream ss = CMatrixClassifierFgMDMRT::printAdditional();
ss << m_bias;
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,177 @@
#include "geometry/classifier/CMatrixClassifierMDM.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Distance.hpp"
#include "geometry/Basics.hpp"
#include "geometry/Geodesic.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//***********************
//***** Constructor *****
//***********************
///-------------------------------------------------------------------------------------------------
CMatrixClassifierMDM::CMatrixClassifierMDM(const size_t nbClass, const EMetric metric)
{
CMatrixClassifierMDM::setClassCount(nbClass);
m_metric = metric;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
CMatrixClassifierMDM::~CMatrixClassifierMDM()
{
m_means.clear();
m_nbTrials.clear();
}
///-------------------------------------------------------------------------------------------------
//**********************
//***** Classifier *****
//**********************
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierMDM::setClassCount(const size_t nbClass)
{
if (m_nbClass != nbClass || m_means.size() != nbClass || m_nbTrials.size() != nbClass)
{
IMatrixClassifier::setClassCount(nbClass);
m_means.resize(m_nbClass);
m_nbTrials.resize(nbClass);
}
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (dataset.empty()) { return false; }
setClassCount(dataset.size()); // Change the number of classes if needed
for (size_t k = 0; k < m_nbClass; ++k) // for each class
{
if (!Mean(dataset[k], m_means[k], m_metric)) { return false; } // Compute the mean of each class
m_nbTrials[k] = dataset[k].size();
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!IsSquare(sample)) { return false; } // Verification if it's a square matrix
double distMin = std::numeric_limits<double>::max(); // Init of distance min
// Compute Distances
distance.resize(m_nbClass);
for (size_t k = 0; k < m_nbClass; ++k)
{
distance[k] = Distance(sample, m_means[k], m_metric);
if (distMin > distance[k])
{
classId = k;
distMin = distance[k];
}
}
// Compute Probabilities (personnal method)
probability.resize(m_nbClass);
double sumProbability = 0.0;
for (size_t k = 0; k < m_nbClass; ++k)
{
probability[k] = distMin / distance[k];
sumProbability += probability[k];
}
for (auto& p : probability) { p /= sumProbability; }
// Adaptation
if (adaptation == EAdaptations::None) { return true; }
// Get class id for adaptation and increase number of trials, expected if supervised, predicted if unsupervised
const size_t id = adaptation == EAdaptations::Supervised ? realClassId : classId;
if (id >= m_nbClass) { return false; } // Check id (if supervised and bad input)
m_nbTrials[id]++; // Update number of trials for the class id
return Geodesic(m_means[id], sample, m_means[id], m_metric, 1.0 / m_nbTrials[id]);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::saveClasses(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
for (size_t k = 0; k < m_nbClass; ++k) // for each class
{
tinyxml2::XMLElement* element = doc.NewElement("Class"); // Create class node
element->SetAttribute("class-id", int(k)); // Set attribute class id (0 to K)
element->SetAttribute("nb-trials", int(m_nbTrials[k])); // Set attribute class number of trials
if (!saveMatrix(element, m_means[k])) { return false; } // Save class Matrix Reference
data->InsertEndChild(element); // Add class node to data node
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::loadClasses(tinyxml2::XMLElement* data)
{
tinyxml2::XMLElement* element = data->FirstChildElement("Class"); // Get First Class Node
for (size_t k = 0; k < m_nbClass; ++k) // for each class
{
if (element == nullptr) { return false; } // Check if Node Exist
const size_t idx = element->IntAttribute("class-id"); // Get Id (normally idx == k)
if (idx != k) { return false; } // Check Id
m_nbTrials[k] = element->IntAttribute("nb-trials"); // Get the number of Trials for this class
if (!loadMatrix(element, m_means[k])) { return false; } // Load Class Matrix
element = element->NextSiblingElement("Class"); // Next Class
}
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierMDM::printClasses() const
{
std::stringstream ss;
for (size_t i = 0; i < m_nbClass; ++i)
{
ss << "Mean of class " << i << " (" << m_nbTrials[i] << " trials): ";
if (m_means[i].size() != 0) { ss << std::endl << m_means[i].format(MATRIX_FORMAT) << std::endl; }
else { ss << "Not Computed" << std::endl; }
}
return ss;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDM::isEqual(const CMatrixClassifierMDM& obj, const double precision) const
{
if (!IMatrixClassifier::isEqual(obj)) { return false; }
if (m_nbClass != obj.getClassCount()) { return false; }
for (size_t i = 0; i < m_nbClass; ++i)
{
if (!AreEquals(m_means[i], obj.m_means[i], precision)) { return false; }
if (m_nbTrials[i] != obj.m_nbTrials[i]) { return false; }
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierMDM::copy(const CMatrixClassifierMDM& obj)
{
IMatrixClassifier::copy(obj);
setClassCount(m_nbClass);
for (size_t i = 0; i < m_nbClass; ++i)
{
m_means[i] = obj.m_means[i];
m_nbTrials[i] = obj.m_nbTrials[i];
}
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,81 @@
#include "geometry/classifier/CMatrixClassifierMDMRebias.hpp"
#include "geometry/Mean.hpp"
#include "geometry/Basics.hpp"
#include <unsupported/Eigen/MatrixFunctions> // SQRT of Matrix
namespace Geometry {
//**********************
//***** Classifier *****
//**********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::train(const std::vector<std::vector<Eigen::MatrixXd>>& dataset)
{
if (!m_bias.computeBias(dataset, m_metric)) { return false; }
std::vector<std::vector<Eigen::MatrixXd>> newDataset;
m_bias.applyBias(dataset, newDataset);
return CMatrixClassifierMDM::train(newDataset); // Train MDM
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::classify(const Eigen::MatrixXd& sample, size_t& classId, std::vector<double>& distance,
std::vector<double>& probability, const EAdaptations adaptation, const size_t& realClassId)
{
if (!IsSquare(sample)) { return false; } // Verification if it's a square matrix
Eigen::MatrixXd newSample;
m_bias.applyBias(sample, newSample);
m_bias.updateBias(sample, m_metric);
return CMatrixClassifierMDM::classify(newSample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::saveAdditional(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* data) const
{
if (!CMatrixClassifierMDM::saveAdditional(doc, data)) { return false; }
if (!m_bias.saveAdditional(doc, data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::loadAdditional(tinyxml2::XMLElement* data)
{
if (!CMatrixClassifierMDM::loadAdditional(data)) { return false; }
if (!m_bias.loadAdditional(data)) { return false; }
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool CMatrixClassifierMDMRebias::isEqual(const CMatrixClassifierMDMRebias& obj, const double precision) const
{
return CMatrixClassifierMDM::isEqual(obj, precision) && m_bias == obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void CMatrixClassifierMDMRebias::copy(const CMatrixClassifierMDMRebias& obj)
{
CMatrixClassifierMDM::copy(obj);
m_bias = obj.m_bias;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
std::stringstream CMatrixClassifierMDMRebias::printAdditional() const
{
std::stringstream ss = CMatrixClassifierMDM::printAdditional();
ss << m_bias;
return ss;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry
@@ -0,0 +1,172 @@
#include "geometry/classifier/IMatrixClassifier.hpp"
#include <iostream>
namespace Geometry {
//***********************
//***** Constructor *****
//***********************
///-------------------------------------------------------------------------------------------------
IMatrixClassifier::IMatrixClassifier(const size_t nbClass, const EMetric metric)
{
IMatrixClassifier::setClassCount(nbClass);
m_metric = metric;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void IMatrixClassifier::setClassCount(const size_t nbClass) { m_nbClass = nbClass; }
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::classify(const Eigen::MatrixXd& sample, size_t& classId, const EAdaptations adaptation, const size_t& realClassId)
{
std::vector<double> distance, probability;
return classify(sample, classId, distance, probability, adaptation, realClassId);
}
///-------------------------------------------------------------------------------------------------
//***********************
//***** XML Manager *****
//***********************
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::saveXML(const std::string& filename) const
{
tinyxml2::XMLDocument xmlDoc;
// Create Root
tinyxml2::XMLNode* root = xmlDoc.NewElement("Classifier"); // Create root node
xmlDoc.InsertFirstChild(root); // Add root to XML
tinyxml2::XMLElement* data = xmlDoc.NewElement("Classifier-data"); // Create data node
if (!saveHeader(data)) { return false; } // Save Header attribute
if (!saveAdditional(xmlDoc, data)) { return false; } // Save Optionnal Informations
if (!saveClasses(xmlDoc, data)) { return false; } // Save Classes
root->InsertEndChild(data); // Add data to root
return xmlDoc.SaveFile(filename.c_str()) == 0; // save XML (if != 0 it means error)
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::loadXML(const std::string& filename)
{
// Load File
tinyxml2::XMLDocument xmlDoc;
if (xmlDoc.LoadFile(filename.c_str()) != 0) { return false; } // Check File Exist and Loading
// Load Root
tinyxml2::XMLNode* root = xmlDoc.FirstChild(); // Get Root Node
if (root == nullptr) { return false; } // Check Root Node Exist
// Load Data
tinyxml2::XMLElement* data = root->FirstChildElement("Classifier-data"); // Get Data Node
if (data == nullptr) { return false; } // Check Root Node Exist
if (!loadHeader(data)) { return false; } // Load Header attribute
if (!loadAdditional(data)) { return false; } // Load Optionnal Informations
if (!loadClasses(data)) { return false; } // Load Classes
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::convertMatrixToXMLFormat(const Eigen::MatrixXd& in, std::stringstream& out)
{
out << in.format(MATRIX_FORMAT);
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::convertXMLFormatToMatrix(std::stringstream& in, Eigen::MatrixXd& out, const size_t rows, const size_t cols)
{
out = Eigen::MatrixXd::Identity(rows, cols); // Init With Identity Matrix (in case of)
for (size_t i = 0; i < rows; ++i) // Fill Matrix
{
for (size_t j = 0; j < cols; ++j) { in >> out(i, j); }
}
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::saveMatrix(tinyxml2::XMLElement* element, const Eigen::MatrixXd& matrix)
{
element->SetAttribute("size", int(matrix.rows())); // Set Matrix size NxN
std::stringstream ss;
convertMatrixToXMLFormat(matrix, ss);
element->SetText(ss.str().c_str()); // Write Means Value
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::loadMatrix(tinyxml2::XMLElement* element, Eigen::MatrixXd& matrix)
{
const size_t size = element->IntAttribute("size"); // Get number of row/col
if (size == 0) { return true; }
std::stringstream ss(element->GetText()); // String stream to parse Matrix value
convertXMLFormatToMatrix(ss, matrix, size, size);
return true;
}
///-------------------------------------------------------------------------------------------------
//*****************************
//***** Override Operator *****
//*****************************
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::isEqual(const IMatrixClassifier& obj, const double /*precision*/) const
{
return m_metric == obj.m_metric && m_nbClass == obj.getClassCount();
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
void IMatrixClassifier::copy(const IMatrixClassifier& obj)
{
m_metric = obj.m_metric;
setClassCount(obj.getClassCount());
}
/// -------------------------------------------------------------------------------------------------
/// -------------------------------------------------------------------------------------------------
std::stringstream IMatrixClassifier::print() const { return std::stringstream(printHeader().str() + printAdditional().str() + printClasses().str()); }
/// -------------------------------------------------------------------------------------------------
/// -------------------------------------------------------------------------------------------------
std::stringstream IMatrixClassifier::printHeader() const
{
std::stringstream ss;
ss << getType() << " Classifier" << std::endl;
ss << "Metric : " << toString(m_metric) << std::endl;
ss << "Number of Classes : " << m_nbClass << std::endl;
return ss;
}
///-------------------------------------------------------------------------------------------------
//*******************************************
//***** XML Manager (Private Functions) *****
//*******************************************
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::saveHeader(tinyxml2::XMLElement* data) const
{
data->SetAttribute("type", getType().c_str()); // Set attribute classifier type
data->SetAttribute("class-count", int(m_nbClass)); // Set attribute class count
data->SetAttribute("metric", toString(m_metric).c_str()); // Set attribute metric
return true;
}
///-------------------------------------------------------------------------------------------------
///-------------------------------------------------------------------------------------------------
bool IMatrixClassifier::loadHeader(tinyxml2::XMLElement* data)
{
if (data == nullptr) { return false; } // Check if Node Exist
const std::string classifierType = data->Attribute("type"); // Get type
if (classifierType != getType()) { return false; } // Check Type
setClassCount(data->IntAttribute("class-count")); // Update Number of classes
m_metric = StringToMetric(data->Attribute("metric")); // Update Metric
return true;
}
///-------------------------------------------------------------------------------------------------
} // namespace Geometry