init
This commit is contained in:
+423
@@ -0,0 +1,423 @@
|
||||
#include "ovpCAlgorithmClassifierLDA.h"
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include <xml/IXMLHandler.h>
|
||||
|
||||
#include <Eigen/Eigenvalues>
|
||||
|
||||
#include "../algorithms/ovpCAlgorithmConditionedCovariance.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "LDA";
|
||||
static const char* const CLASSES_NODE_NAME = "Classes";
|
||||
//static const char* const COEFFICIENTS_NODE_NAME = "Weights";
|
||||
//static const char* const BIAS_DISTANCE_NODE_NAME = "Bias-distance";
|
||||
//static const char* const COEFFICIENT_PROBABILITY_NODE_NAME = "Coefficient-probability";
|
||||
static const char* const COMPUTATION_HELPERS_CONFIGURATION_NODE = "Class-config-list";
|
||||
static const char* const LDA_CONFIG_FILE_VERSION_ATTRIBUTE_NAME = "version";
|
||||
|
||||
extern const char* const CLASSIFIER_ROOT;
|
||||
|
||||
int LDAClassificationCompare(CMatrix& first, CMatrix& second)
|
||||
{
|
||||
//We first need to find the best classification of each.
|
||||
double* buffer = first.getBuffer();
|
||||
const double maxFirst = *(std::max_element(buffer, buffer + first.getBufferElementCount()));
|
||||
|
||||
buffer = second.getBuffer();
|
||||
const double maxSecond = *(std::max_element(buffer, buffer + second.getBufferElementCount()));
|
||||
|
||||
//Then we just compared them
|
||||
if (OVFloatEqual(maxFirst, maxSecond)) { return 0; }
|
||||
if (maxFirst > maxSecond) { return -1; }
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#define LDA_DEBUG 0
|
||||
#if LDA_DEBUG
|
||||
void CAlgorithmClassifierLDA::dumpMatrix(ILogManager &rMgr, const MatrixXdRowMajor &mat, const CString &desc)
|
||||
{
|
||||
rMgr << Kernel::LogLevel_Info << desc << "\n";
|
||||
for (int i = 0 ; i < mat.rows() ; i++)
|
||||
{
|
||||
rMgr << Kernel::LogLevel_Info << "Row " << i << ": ";
|
||||
for (int j = 0 ; j < mat.cols() ; j++) { rMgr << mat(i,j) << " "; }
|
||||
rMgr << "\n";
|
||||
}
|
||||
}
|
||||
#else
|
||||
void CAlgorithmClassifierLDA::dumpMatrix(Kernel::ILogManager& /* rMgr */, const MatrixXdRowMajor& /*mat*/, const CString& /*desc*/) { }
|
||||
#endif
|
||||
|
||||
bool CAlgorithmClassifierLDA::initialize()
|
||||
{
|
||||
// Initialize the Conditioned Covariance Matrix algorithm
|
||||
m_covAlgorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_ConditionedCovariance));
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_covAlgorithm->initialize(), "Failed to initialize covariance algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
// This is the weight parameter local to this module and automatically exposed to the GUI. Its redirected to the corresponding parameter of the cov alg.
|
||||
Kernel::TParameterHandler<double> ip_shrinkage(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage));
|
||||
ip_shrinkage.setReferenceTarget(m_covAlgorithm->getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage));
|
||||
|
||||
Kernel::TParameterHandler<bool> ip_diagonalCov(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov));
|
||||
ip_diagonalCov = false;
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_configuration = nullptr;
|
||||
|
||||
return CAlgorithmClassifier::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::uninitialize()
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_covAlgorithm->uninitialize(), "Failed to uninitialize covariance algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_covAlgorithm);
|
||||
|
||||
return CAlgorithmClassifier::uninitialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::train(const Toolkit::IFeatureVectorSet& dataset)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(this->initializeExtraParameterMechanism(), "Failed to unitialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
//We need to clear list because a instance of this class should support more that one training.
|
||||
m_labels.clear();
|
||||
m_discriminantFunctions.clear();
|
||||
|
||||
const bool useShrinkage = this->getBooleanParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_UseShrinkage);
|
||||
|
||||
bool diagonalCov;
|
||||
if (useShrinkage)
|
||||
{
|
||||
this->getDoubleParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage);
|
||||
diagonalCov = this->getBooleanParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov);
|
||||
}
|
||||
else
|
||||
{
|
||||
//If we don't use shrinkage we need to set lambda to 0.
|
||||
Kernel::TParameterHandler<double> ip_shrinkage(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage));
|
||||
ip_shrinkage = 0.0;
|
||||
|
||||
Kernel::TParameterHandler<bool> ip_diagonalCov(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov));
|
||||
ip_diagonalCov = false;
|
||||
diagonalCov = false;
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->uninitializeExtraParameterMechanism(), "Failed to ininitialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
// IO to the covariance alg
|
||||
Kernel::TParameterHandler<CMatrix*> op_mean(m_covAlgorithm->getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean));
|
||||
Kernel::TParameterHandler<CMatrix*> op_covMatrix(m_covAlgorithm->getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix));
|
||||
Kernel::TParameterHandler<CMatrix*> ip_dataset(m_covAlgorithm->getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet));
|
||||
|
||||
const size_t nRows = dataset.getFeatureVectorCount();
|
||||
const size_t nCols = (nRows > 0 ? dataset[0].getSize() : 0);
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Feature set input dims [" << dataset.getFeatureVectorCount() << "x" << nCols << "]\n";
|
||||
|
||||
OV_ERROR_UNLESS_KRF(nRows != 0 && nCols != 0, "Input data has a zero-size dimension, dims = [" << nRows << "x" << nCols << "]",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
// The max amount of classes to be expected
|
||||
Kernel::TParameterHandler<uint64_t> ip_pNClasses(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
m_nClasses = size_t(ip_pNClasses);
|
||||
|
||||
// Count the classes actually present
|
||||
std::vector<size_t> nClasses;
|
||||
nClasses.resize(m_nClasses);
|
||||
|
||||
for (size_t i = 0; i < dataset.getFeatureVectorCount(); ++i)
|
||||
{
|
||||
size_t classIdx = size_t(dataset[i].getLabel());
|
||||
nClasses[classIdx]++;
|
||||
}
|
||||
|
||||
// Get class labels
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
m_labels.push_back(i);
|
||||
m_discriminantFunctions.push_back(CAlgorithmLDADiscriminantFunction());
|
||||
}
|
||||
|
||||
// Per-class means and a global covariance are used to form the LDA model
|
||||
std::vector<Eigen::MatrixXd> classMeans(m_nClasses);
|
||||
Eigen::MatrixXd globalCov = Eigen::MatrixXd::Zero(nCols, nCols);
|
||||
|
||||
// We need the means per class
|
||||
for (size_t classIdx = 0; classIdx < m_nClasses; classIdx++)
|
||||
{
|
||||
if (nClasses[classIdx] > 0)
|
||||
{
|
||||
// const double label = m_labels[l_classIdx];
|
||||
const size_t examplesInClass = nClasses[classIdx];
|
||||
|
||||
// Copy all the data of the class to a matrix
|
||||
CMatrix classData;
|
||||
classData.resize(examplesInClass, nCols);
|
||||
double* buffer = classData.getBuffer();
|
||||
for (size_t i = 0; i < nRows; ++i)
|
||||
{
|
||||
if (dataset[i].getLabel() == classIdx)
|
||||
{
|
||||
memcpy(buffer, dataset[i].getBuffer(), nCols * sizeof(double));
|
||||
buffer += nCols;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the mean out of it
|
||||
Eigen::Map<MatrixXdRowMajor> dataMapper(classData.getBuffer(), examplesInClass, nCols);
|
||||
const Eigen::MatrixXd classMean = dataMapper.colwise().mean().transpose();
|
||||
classMeans[classIdx] = classMean;
|
||||
}
|
||||
else
|
||||
{
|
||||
Eigen::MatrixXd tmp;
|
||||
tmp.resize(nCols, 1);
|
||||
tmp.setZero();
|
||||
classMeans[classIdx] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
// We need a global covariance, use the regularized cov algorithm
|
||||
{
|
||||
ip_dataset->resize(nRows, nCols);
|
||||
double* buffer = ip_dataset->getBuffer();
|
||||
|
||||
// Insert all data as the input of the cov algorithm
|
||||
for (size_t i = 0; i < nRows; ++i)
|
||||
{
|
||||
memcpy(buffer, dataset[i].getBuffer(), nCols * sizeof(double));
|
||||
buffer += nCols;
|
||||
}
|
||||
|
||||
// Compute cov
|
||||
if (!m_covAlgorithm->process()) { OV_ERROR_KRF("Global covariance computation failed", Kernel::ErrorType::Internal); }
|
||||
|
||||
// Get the results from the cov algorithm
|
||||
Eigen::Map<MatrixXdRowMajor> covMapper(op_covMatrix->getBuffer(), nCols, nCols);
|
||||
globalCov = covMapper;
|
||||
}
|
||||
|
||||
//dumpMatrix(this->getLogManager(), mean[l_classIdx], "Mean");
|
||||
//dumpMatrix(this->getLogManager(), globalCov, "Shrinked cov");
|
||||
|
||||
if (diagonalCov)
|
||||
{
|
||||
for (size_t i = 0; i < nCols; ++i)
|
||||
{
|
||||
for (size_t j = i + 1; j < nCols; ++j)
|
||||
{
|
||||
globalCov(i, j) = 0.0;
|
||||
globalCov(j, i) = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get the pseudoinverse of the global cov using eigen decomposition for self-adjoint matrices
|
||||
const double tolerance = 1e-10;
|
||||
Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver;
|
||||
solver.compute(globalCov);
|
||||
Eigen::VectorXd eigenValues = solver.eigenvalues();
|
||||
for (size_t i = 0; i < nCols; ++i) { if (eigenValues(i) >= tolerance) { eigenValues(i) = 1.0 / eigenValues(i); } }
|
||||
const Eigen::MatrixXd globalCovInv = solver.eigenvectors() * eigenValues.asDiagonal() * solver.eigenvectors().inverse();
|
||||
|
||||
// const MatrixXd globalCovInv = globalCov.inverse();
|
||||
//We send the bias and the weight of each class to ComputationHelper
|
||||
for (size_t i = 0; i < getClassCount(); ++i)
|
||||
{
|
||||
const double examplesInClass = nClasses[i];
|
||||
if (examplesInClass > 0)
|
||||
{
|
||||
const size_t totalExamples = dataset.getFeatureVectorCount();
|
||||
|
||||
// This formula e.g. in Hastie, Tibshirani & Friedman: "Elements...", 2nd ed., p. 109
|
||||
const Eigen::VectorXd weigth = (globalCovInv * classMeans[i]);
|
||||
const Eigen::MatrixXd inter = -0.5 * classMeans[i].transpose() * globalCovInv * classMeans[i];
|
||||
const double bias = inter(0, 0) + std::log(examplesInClass / totalExamples);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Bias for " << i << " is " << bias << ", from " << examplesInClass / totalExamples
|
||||
<< ", " << examplesInClass << "/" << totalExamples << ", int = " << inter(0, 0) << "\n";
|
||||
// dumpMatrix(this->getLogManager(), perClassMeans[i], "Means");
|
||||
|
||||
m_discriminantFunctions[i].setWeight(weigth);
|
||||
m_discriminantFunctions[i].setBias(bias);
|
||||
}
|
||||
else { this->getLogManager() << Kernel::LogLevel_Debug << "Class " << i << " has no examples\n"; }
|
||||
}
|
||||
|
||||
// Hack for classes with zero examples, give them valid models but such that will always lose
|
||||
size_t nonZeroClassIdx = 0;
|
||||
for (size_t i = 0; i < getClassCount(); ++i)
|
||||
{
|
||||
if (nClasses[i] > 0)
|
||||
{
|
||||
nonZeroClassIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < getClassCount(); ++i)
|
||||
{
|
||||
if (nClasses[i] == 0)
|
||||
{
|
||||
m_discriminantFunctions[i].setWeight(m_discriminantFunctions[nonZeroClassIdx].getWeight());
|
||||
m_discriminantFunctions[i].setBias(m_discriminantFunctions[nonZeroClassIdx].getBias() - 1.0); // Will always lose to the orig
|
||||
}
|
||||
}
|
||||
|
||||
m_nCols = nCols;
|
||||
|
||||
// Debug output
|
||||
//dumpMatrix(this->getLogManager(), globalCov, "Global cov");
|
||||
//dumpMatrix(this->getLogManager(), eigenValues, "Eigenvalues");
|
||||
//dumpMatrix(this->getLogManager(), eigenSolver.eigenvectors(), "Eigenvectors");
|
||||
//dumpMatrix(this->getLogManager(), globalCovInv, "Global cov inverse");
|
||||
//dumpMatrix(this->getLogManager(), m_coefficients, "Hyperplane weights");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(!m_discriminantFunctions.empty(), "LDA discriminant function list is empty", Kernel::ErrorType::BadConfig);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(sample.getSize() == m_discriminantFunctions[0].getNWeight(),
|
||||
"Classifier expected " << m_discriminantFunctions[0].getNWeight() << " features, got " << sample.getSize(),
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
const Eigen::Map<Eigen::VectorXd> featureVec(const_cast<double*>(sample.getBuffer()), sample.getSize());
|
||||
const Eigen::VectorXd weights = featureVec;
|
||||
const size_t nClass = getClassCount();
|
||||
|
||||
std::vector<double> buffer(nClass);
|
||||
std::vector<double> probabBuffer(nClass);
|
||||
//We ask for all computation helper to give the corresponding class value
|
||||
for (size_t i = 0; i < nClass; ++i) { buffer[i] = m_discriminantFunctions[i].getValue(weights); }
|
||||
|
||||
//p(Ck | x) = exp(ak) / sum[j](exp (aj))
|
||||
// with aj = (Weight for class j).transpose() * x + (Bias for class j)
|
||||
|
||||
//Exponential can lead to nan results, so we reduce the computation and instead compute
|
||||
// p(Ck | x) = 1 / sum[j](exp(aj - ak))
|
||||
|
||||
//All ak are given by computation helper
|
||||
errno = 0;
|
||||
for (size_t i = 0; i < nClass; ++i)
|
||||
{
|
||||
double expSum = 0.;
|
||||
for (size_t j = 0; j < nClass; ++j) { expSum += exp(buffer[j] - buffer[i]); }
|
||||
probabBuffer[i] = 1 / expSum;
|
||||
// std::cout << "p " << i << " = " << probabilityValue[i] << ", v=" << valueArray[i] << ", " << errno << "\n";
|
||||
}
|
||||
|
||||
//Then we just find the highest probability and take it as a result
|
||||
const size_t classIdx = size_t(std::distance(buffer.begin(), std::max_element(buffer.begin(), buffer.end())));
|
||||
|
||||
distance.setSize(nClass);
|
||||
probability.setSize(nClass);
|
||||
|
||||
for (size_t i = 0; i < nClass; ++i)
|
||||
{
|
||||
distance[i] = buffer[i];
|
||||
probability[i] = probabBuffer[i];
|
||||
}
|
||||
|
||||
classId = m_labels[classIdx];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierLDA::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* algorithmNode = XML::createNode(TYPE_NODE_NAME);
|
||||
algorithmNode->addAttribute(LDA_CONFIG_FILE_VERSION_ATTRIBUTE_NAME, "1");
|
||||
|
||||
// Write the classifier to an .xml
|
||||
std::stringstream classes;
|
||||
|
||||
for (size_t i = 0; i < getClassCount(); ++i) { classes << m_labels[i] << " "; }
|
||||
|
||||
//Only new version should be recorded so we don't need to test
|
||||
XML::IXMLNode* helpersConfig = XML::createNode(COMPUTATION_HELPERS_CONFIGURATION_NODE);
|
||||
for (size_t i = 0; i < m_discriminantFunctions.size(); ++i) { helpersConfig->addChild(m_discriminantFunctions[i].getConfiguration()); }
|
||||
|
||||
XML::IXMLNode* tmpNode = XML::createNode(CLASSES_NODE_NAME);
|
||||
tmpNode->setPCData(classes.str().c_str());
|
||||
algorithmNode->addChild(tmpNode);
|
||||
algorithmNode->addChild(helpersConfig);
|
||||
|
||||
return algorithmNode;
|
||||
}
|
||||
|
||||
|
||||
//Extract a double from the PCDATA of a node
|
||||
double getFloatFromNode(XML::IXMLNode* pNode)
|
||||
{
|
||||
std::stringstream ss(pNode->getPCData());
|
||||
double res;
|
||||
ss >> res;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierLDA::loadConfig(XML::IXMLNode* configNode)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(configNode->hasAttribute(LDA_CONFIG_FILE_VERSION_ATTRIBUTE_NAME),
|
||||
"Invalid model: model trained with an obsolete version of LDA", Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_labels.clear();
|
||||
m_discriminantFunctions.clear();
|
||||
|
||||
XML::IXMLNode* tmpNode = configNode->getChildByName(CLASSES_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmpNode != nullptr, "Failed to retrieve xml node", Kernel::ErrorType::BadParsing);
|
||||
|
||||
loadClassesFromNode(tmpNode);
|
||||
|
||||
|
||||
//We send corresponding data to the computation helper
|
||||
XML::IXMLNode* configsNode = configNode->getChildByName(COMPUTATION_HELPERS_CONFIGURATION_NODE);
|
||||
|
||||
for (size_t i = 0; i < configsNode->getChildCount(); ++i)
|
||||
{
|
||||
m_discriminantFunctions.push_back(CAlgorithmLDADiscriminantFunction());
|
||||
m_discriminantFunctions[i].loadConfig(configsNode->getChild(i));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAlgorithmClassifierLDA::loadClassesFromNode(XML::IXMLNode* node)
|
||||
{
|
||||
std::stringstream ss(node->getPCData());
|
||||
double value;
|
||||
while (ss >> value) { m_labels.push_back(value); }
|
||||
m_nClasses = m_labels.size();
|
||||
}
|
||||
|
||||
//Load the weight vector
|
||||
void CAlgorithmClassifierLDA::loadCoefsFromNode(XML::IXMLNode* node)
|
||||
{
|
||||
std::stringstream ss(node->getPCData());
|
||||
|
||||
std::vector<double> coefs;
|
||||
double value;
|
||||
while (ss >> value) { coefs.push_back(value); }
|
||||
|
||||
m_weights.resize(1, coefs.size());
|
||||
m_nCols = coefs.size();
|
||||
for (size_t i = 0; i < coefs.size(); ++i) { m_weights(0, i) = coefs[i]; }
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include "ovpCAlgorithmLDADiscriminantFunction.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <stack>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmLDADiscriminantFunction;
|
||||
|
||||
int LDAClassificationCompare(CMatrix& first, CMatrix& second);
|
||||
|
||||
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
|
||||
|
||||
class CAlgorithmClassifierLDA final : public Toolkit::CAlgorithmClassifier
|
||||
{
|
||||
public:
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& dataset) override;
|
||||
bool classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode* configNode) override;
|
||||
size_t getNProbabilities() override { return m_discriminantFunctions.size(); }
|
||||
size_t getNDistances() override { return m_discriminantFunctions.size(); }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifier, OVP_ClassId_Algorithm_ClassifierLDA)
|
||||
|
||||
protected:
|
||||
// Debug method. Prints the matrix to the logManager. May be disabled in implementation.
|
||||
static void dumpMatrix(Kernel::ILogManager& pMgr, const MatrixXdRowMajor& mat, const CString& desc);
|
||||
|
||||
std::vector<double> m_labels;
|
||||
std::vector<CAlgorithmLDADiscriminantFunction> m_discriminantFunctions;
|
||||
|
||||
Eigen::MatrixXd m_coefficients;
|
||||
Eigen::MatrixXd m_weights;
|
||||
double m_biasDistance = 0;
|
||||
double m_w0 = 0;
|
||||
|
||||
size_t m_nCols = 0;
|
||||
size_t m_nClasses = 0;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_covAlgorithm = nullptr;
|
||||
|
||||
private:
|
||||
void loadClassesFromNode(XML::IXMLNode* node);
|
||||
void loadCoefsFromNode(XML::IXMLNode* node);
|
||||
|
||||
size_t getClassCount() const { return m_nClasses; }
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierLDADesc final : public Toolkit::CAlgorithmClassifierDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("LDA Classifier"); }
|
||||
CString getAuthorName() const override { return CString("Jussi T. Lindgren / Guillaume Serrière"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria / Loria"); }
|
||||
CString getShortDescription() const override { return CString("Estimates LDA using regularized or classic covariances"); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("2.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierLDA; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierLDA; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_UseShrinkage, "Use shrinkage", Kernel::ParameterType_Boolean);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov, "Shrinkage: Force diagonal cov (DDA)",
|
||||
Kernel::ParameterType_Boolean);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage, "Shrinkage coefficient (-1 == auto)", Kernel::ParameterType_Float);
|
||||
|
||||
CAlgorithmClassifierDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifierDesc, OVP_ClassId_Algorithm_ClassifierLDADesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
#include "ovpCAlgorithmClassifierNULL.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
|
||||
bool CAlgorithmClassifierNULL::initialize()
|
||||
{
|
||||
Kernel::TParameterHandler<bool> ip_bParameter1(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1));
|
||||
Kernel::TParameterHandler<double> ip_Parameter2(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2));
|
||||
Kernel::TParameterHandler<uint64_t> ip_parameter3(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3));
|
||||
|
||||
ip_bParameter1 = true;
|
||||
ip_Parameter2 = 3.141592654;
|
||||
ip_parameter3 = OVTK_StimulationId_Label_00;
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_configuration = nullptr;
|
||||
|
||||
return CAlgorithmClassifier::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierNULL::train(const Toolkit::IFeatureVectorSet& /*featureVectorSet*/)
|
||||
{
|
||||
Kernel::TParameterHandler<bool> ip_bParameter1(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1));
|
||||
Kernel::TParameterHandler<double> ip_Parameter2(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2));
|
||||
Kernel::TParameterHandler<uint64_t> ip_parameter3(this->getInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3));
|
||||
|
||||
OV_WARNING_K("Parameter 1 : " << ip_bParameter1);
|
||||
OV_WARNING_K("Parameter 2 : " << ip_Parameter2);
|
||||
OV_WARNING_K("Parameter 3 : " << ip_parameter3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierNULL::classify(const Toolkit::IFeatureVector& /*featureVector*/, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
classId = 1 + (rand() % 3);
|
||||
|
||||
distance.setSize(1);
|
||||
probability.setSize(1);
|
||||
if (classId == 1)
|
||||
{
|
||||
distance[0] = -1;
|
||||
probability[0] = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
distance[0] = 1;
|
||||
probability[0] = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmClassifierNULL final : public Toolkit::CAlgorithmClassifier
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmClassifierNULL() { }
|
||||
bool initialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& featureVectorSet) override;
|
||||
bool classify(const Toolkit::IFeatureVector& featureVector, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
XML::IXMLNode* saveConfig() override { return nullptr; }
|
||||
bool loadConfig(XML::IXMLNode* /*configurationNode*/) override { return true; }
|
||||
size_t getNProbabilities() override { return 1; }
|
||||
size_t getNDistances() override { return 1; }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifier, OVP_ClassId_Algorithm_ClassifierNULL)
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierNULLDesc final : public Toolkit::CAlgorithmClassifierDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("NULL Classifier (does nothing)"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString("Samples"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierNULL; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierNULL; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmClassifierDesc::getAlgorithmPrototype(prototype);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1, "Parameter 1", Kernel::ParameterType_Boolean);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2, "Parameter 2", Kernel::ParameterType_Float);
|
||||
prototype.addInputParameter(OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3, "Parameter 3", Kernel::ParameterType_Enumeration,
|
||||
OV_TypeId_Stimulation);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmClassifierDesc, OVP_ClassId_Algorithm_ClassifierNULLDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
#include "ovpCAlgorithmClassifierOneVsAll.h"
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "OneVsAll";
|
||||
static const char* const SUB_CLASSIFIER_IDENTIFIER_NODE_NAME = "SubClassifierIdentifier";
|
||||
static const char* const ALGORITHM_ID_ATTRIBUTE = "algorithm-id";
|
||||
static const char* const SUB_CLASSIFIER_COUNT_NODE_NAME = "SubClassifierCount";
|
||||
static const char* const SUB_CLASSIFIERS_NODE_NAME = "SubClassifiers";
|
||||
//static const char* const SUB_CLASSIFIER_NODE_NAME = "SubClassifier";
|
||||
|
||||
typedef std::pair<CMatrix*, CMatrix*> CIMatrixPointerPair;
|
||||
typedef std::pair<double, CMatrix*> CClassifierOutput;
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::initialize()
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_Config(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_Config = nullptr;
|
||||
|
||||
return CAlgorithmPairingStrategy::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::uninitialize()
|
||||
{
|
||||
while (!m_subClassifiers.empty()) { this->removeClassifierAtBack(); }
|
||||
return CAlgorithmPairingStrategy::uninitialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::train(const Toolkit::IFeatureVectorSet& dataset)
|
||||
{
|
||||
const size_t nClass = m_subClassifiers.size();
|
||||
std::map<double, size_t> classLabels;
|
||||
|
||||
for (size_t i = 0; i < dataset.getFeatureVectorCount(); ++i)
|
||||
{
|
||||
if (!classLabels.count(dataset[i].getLabel())) { classLabels[dataset[i].getLabel()] = 0; }
|
||||
classLabels[dataset[i].getLabel()]++;
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(classLabels.size() == nClass,
|
||||
"Invalid samples count for [" << classLabels.size() << "] classes (expected samples for " << nClass << " classes)",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
//We set the CMatrix fo the first classifier
|
||||
const size_t size = dataset[0].getSize();
|
||||
Kernel::TParameterHandler<CMatrix*> reference(m_subClassifiers[0]->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
reference->resize(dataset.getFeatureVectorCount(), size + 1);
|
||||
|
||||
double* buffer = reference->getBuffer();
|
||||
for (size_t j = 0; j < dataset.getFeatureVectorCount(); ++j)
|
||||
{
|
||||
memcpy(buffer, dataset[j].getBuffer(), size * sizeof(double));
|
||||
//We let the space for the label
|
||||
buffer += (size + 1);
|
||||
}
|
||||
|
||||
//And then we just change adapt the label for each feature vector but we don't copy them anymore
|
||||
for (size_t c = 0; c < m_subClassifiers.size(); ++c)
|
||||
{
|
||||
Kernel::TParameterHandler<CMatrix*> ip_dataset(m_subClassifiers[c]->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
ip_dataset = static_cast<CMatrix*>(reference);
|
||||
|
||||
buffer = ip_dataset->getBuffer();
|
||||
for (size_t j = 0; j < dataset.getFeatureVectorCount(); ++j)
|
||||
{
|
||||
//Modify the class of each featureVector
|
||||
const double classLabel = dataset[j].getLabel();
|
||||
if (size_t(classLabel) == c) { buffer[size] = 0; }
|
||||
else { buffer[size] = 1; }
|
||||
buffer += (size + 1);
|
||||
}
|
||||
|
||||
m_subClassifiers[c]->process(OVTK_Algorithm_Classifier_InputTriggerId_Train);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
std::vector<CClassifierOutput> classification;
|
||||
|
||||
const size_t size = sample.getSize();
|
||||
|
||||
for (size_t i = 0; i < m_subClassifiers.size(); ++i)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = this->m_subClassifiers[i];
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector));
|
||||
Kernel::TParameterHandler<double> op_class(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
Kernel::TParameterHandler<CMatrix*> op_values(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
Kernel::TParameterHandler<CMatrix*> op_probabilities(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
ip_sample->resize(size);
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
memcpy(buffer, sample.getBuffer(), size * sizeof(double));
|
||||
subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify);
|
||||
|
||||
CMatrix* probabilities = static_cast<CMatrix*>(op_probabilities);
|
||||
//If the algorithm give a probability we take it, instead we take the first value
|
||||
if (probabilities->getDimensionCount() != 0) { classification.push_back(CClassifierOutput(double(op_class), probabilities)); }
|
||||
else { classification.push_back(CClassifierOutput(double(op_class), static_cast<CMatrix*>(op_values))); }
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << i << " " << double(op_class) << " " << double((*op_probabilities)[0]) << " " << double(
|
||||
(*op_probabilities)[1]) << "\n";
|
||||
}
|
||||
|
||||
//Now, we determine the best classification
|
||||
CClassifierOutput best = CClassifierOutput(-1.0, static_cast<CMatrix*>(nullptr));
|
||||
classId = -1;
|
||||
|
||||
for (size_t i = 0; i < classification.size(); ++i)
|
||||
{
|
||||
CClassifierOutput& tmp = classification[i];
|
||||
if (int(tmp.first) == 0) // Predicts its "own" class, class=0
|
||||
{
|
||||
if (best.second == nullptr)
|
||||
{
|
||||
best = tmp;
|
||||
classId = double(i);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((*m_fAlgorithmComparison)((*best.second), *(tmp.second)) > 0)
|
||||
{
|
||||
best = tmp;
|
||||
classId = double(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If no one recognize the class, let's take the more relevant
|
||||
if (int(classId) == -1)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Unable to find a class in first instance\n";
|
||||
for (size_t nClassification = 0; nClassification < classification.size(); ++nClassification)
|
||||
{
|
||||
CClassifierOutput& tmp = classification[nClassification];
|
||||
if (best.second == nullptr)
|
||||
{
|
||||
best = tmp;
|
||||
classId = (double(nClassification));
|
||||
}
|
||||
else
|
||||
{
|
||||
//We take the one that is the least like the second class
|
||||
if ((*m_fAlgorithmComparison)((*best.second), *(tmp.second)) < 0)
|
||||
{
|
||||
best = tmp;
|
||||
classId = nClassification;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(best.second != nullptr, "Unable to find a class for feature vector", Kernel::ErrorType::BadProcessing);
|
||||
|
||||
// Now that we made the calculation, we send the corresponding data
|
||||
|
||||
// For distances we just send the distance vector of the winner
|
||||
Kernel::IAlgorithmProxy* winner = this->m_subClassifiers[size_t(classId)];
|
||||
Kernel::TParameterHandler<CMatrix*> op_winnerValues(winner->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
CMatrix* tmpMatrix = static_cast<CMatrix*>(op_winnerValues);
|
||||
distance.setSize(tmpMatrix->getBufferElementCount());
|
||||
memcpy(distance.getBuffer(), tmpMatrix->getBuffer(), tmpMatrix->getBufferElementCount() * sizeof(double));
|
||||
|
||||
// We take the probabilities of the single class winning from each of the sub classifiers and normalize them
|
||||
double sum = 0;
|
||||
probability.setSize(m_subClassifiers.size());
|
||||
for (size_t i = 0; i < m_subClassifiers.size(); ++i)
|
||||
{
|
||||
Kernel::TParameterHandler<CMatrix*> op_Probabilities(m_subClassifiers[i]->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
probability[i] = op_Probabilities->getBuffer()[0];
|
||||
sum += probability[i];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < probability.getSize(); ++i) { probability[i] /= sum; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::addNewClassifierAtBack()
|
||||
{
|
||||
const CIdentifier subClassifierAlgorithm = this->getAlgorithmManager().createAlgorithm(this->m_subClassifierAlgorithmID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(subClassifierAlgorithm != CIdentifier::undefined(),
|
||||
"Invalid classifier identifier [" << this->m_subClassifierAlgorithmID.str() << "]", Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::IAlgorithmProxy* subClassifier = &this->getAlgorithmManager().getAlgorithm(subClassifierAlgorithm);
|
||||
subClassifier->initialize();
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClasses(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
ip_nClasses = 2;
|
||||
|
||||
//Set a references to the extra parameters input of the pairing strategy
|
||||
Kernel::TParameterHandler<std::map<CString, CString>*> ip_params(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
ip_params.setReferenceTarget(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
|
||||
this->m_subClassifiers.push_back(subClassifier);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CAlgorithmClassifierOneVsAll::removeClassifierAtBack()
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = m_subClassifiers.back();
|
||||
subClassifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*subClassifier);
|
||||
this->m_subClassifiers.pop_back();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::designArchitecture(const CIdentifier& id, const size_t nClass)
|
||||
{
|
||||
if (!this->setSubClassifierIdentifier(id)) { return false; }
|
||||
for (size_t i = 0; i < nClass; ++i) { if (!this->addNewClassifierAtBack()) { return false; } }
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsAll::getClassifierConfig(Kernel::IAlgorithmProxy* classifier)
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_SaveConfig);
|
||||
XML::IXMLNode* res = op_config;
|
||||
return res;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsAll::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* oneVsAllNode = XML::createNode(TYPE_NODE_NAME);
|
||||
|
||||
XML::IXMLNode* tempNode = XML::createNode(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
tempNode->addAttribute(ALGORITHM_ID_ATTRIBUTE, this->m_subClassifierAlgorithmID.str().c_str());
|
||||
tempNode->setPCData(
|
||||
this->getTypeManager().getEnumerationEntryNameFromValue(OVTK_TypeId_ClassificationAlgorithm, m_subClassifierAlgorithmID.id()).toASCIIString());
|
||||
oneVsAllNode->addChild(tempNode);
|
||||
|
||||
tempNode = XML::createNode(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
tempNode->setPCData(std::to_string(getClassCount()).c_str());
|
||||
oneVsAllNode->addChild(tempNode);
|
||||
|
||||
XML::IXMLNode* subClassifersNode = XML::createNode(SUB_CLASSIFIERS_NODE_NAME);
|
||||
|
||||
//We now add configuration of each subclassifiers
|
||||
for (size_t i = 0; i < m_subClassifiers.size(); ++i) { subClassifersNode->addChild(getClassifierConfig(m_subClassifiers[i])); }
|
||||
oneVsAllNode->addChild(subClassifersNode);
|
||||
|
||||
return oneVsAllNode;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::loadConfig(XML::IXMLNode* configNode)
|
||||
{
|
||||
XML::IXMLNode* tempNode = configNode->getChildByName(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
CIdentifier id;
|
||||
id.fromString(tempNode->getAttribute(ALGORITHM_ID_ATTRIBUTE));
|
||||
if (m_subClassifierAlgorithmID != id)
|
||||
{
|
||||
while (!m_subClassifiers.empty()) { this->removeClassifierAtBack(); }
|
||||
if (!this->setSubClassifierIdentifier(id))
|
||||
{
|
||||
//if the sub classifier doesn't have comparison function it is an error
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
tempNode = configNode->getChildByName(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
std::stringstream countData(tempNode->getPCData());
|
||||
uint64_t nClass;
|
||||
countData >> nClass;
|
||||
|
||||
while (nClass != getClassCount())
|
||||
{
|
||||
if (nClass < getClassCount()) { this->removeClassifierAtBack(); }
|
||||
else { if (!this->addNewClassifierAtBack()) { return false; } }
|
||||
}
|
||||
|
||||
return loadSubClassifierConfig(configNode->getChildByName(SUB_CLASSIFIERS_NODE_NAME));
|
||||
}
|
||||
|
||||
size_t CAlgorithmClassifierOneVsAll::getNDistances()
|
||||
{
|
||||
Kernel::TParameterHandler<CMatrix*> op_distances(m_subClassifiers[0]->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
return op_distances->getDimensionSize(0);
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::loadSubClassifierConfig(XML::IXMLNode* node)
|
||||
{
|
||||
for (size_t i = 0; i < node->getChildCount(); ++i)
|
||||
{
|
||||
XML::IXMLNode* subClassifierNode = node->getChild(i);
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(m_subClassifiers[i]->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_config = subClassifierNode;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_subClassifiers[i]->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig),
|
||||
"Unable to load the configuration of the classifier " << i + 1, Kernel::ErrorType::Internal);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsAll::setSubClassifierIdentifier(const CIdentifier& id)
|
||||
{
|
||||
m_subClassifierAlgorithmID = id;
|
||||
m_fAlgorithmComparison = Toolkit::getClassificationComparisonFunction(id);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_fAlgorithmComparison != nullptr,
|
||||
"No comparison function found for classifier [" << m_subClassifierAlgorithmID.str() << "]", Kernel::ErrorType::ResourceNotFound);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmClassifierOneVsAll final : public Toolkit::CAlgorithmPairingStrategy
|
||||
{
|
||||
public:
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& dataset) override;
|
||||
bool classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
bool designArchitecture(const CIdentifier& id, const size_t nClass) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode* configNode) override;
|
||||
size_t getNProbabilities() override { return m_subClassifiers.size(); }
|
||||
size_t getNDistances() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmPairingStrategy, OVP_ClassId_Algorithm_ClassifierOneVsAll)
|
||||
|
||||
|
||||
private:
|
||||
static XML::IXMLNode* getClassifierConfig(Kernel::IAlgorithmProxy* classifier);
|
||||
bool addNewClassifierAtBack();
|
||||
void removeClassifierAtBack();
|
||||
bool setSubClassifierIdentifier(const CIdentifier& id);
|
||||
size_t getClassCount() const { return m_subClassifiers.size(); }
|
||||
|
||||
bool loadSubClassifierConfig(XML::IXMLNode* node);
|
||||
|
||||
std::vector<Kernel::IAlgorithmProxy*> m_subClassifiers;
|
||||
fClassifierComparison m_fAlgorithmComparison = nullptr;
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierOneVsAllDesc final : public Toolkit::CAlgorithmPairingStrategyDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("OneVsAll pairing classifier"); }
|
||||
CString getAuthorName() const override { return CString("Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/Loria"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierOneVsAll; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierOneVsAll; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairingStrategyDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairingStrategyDesc, OVP_ClassId_Algorithm_ClassifierOneVsAllDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+437
@@ -0,0 +1,437 @@
|
||||
#include "ovpCAlgorithmClassifierOneVsOne.h"
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
#include <map>
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include <iostream>
|
||||
|
||||
extern const char* const CLASSIFIER_ROOT;
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "OneVsOne";
|
||||
static const char* const SUB_CLASSIFIER_IDENTIFIER_NODE_NAME = "SubClassifierIdentifier";
|
||||
static const char* const PAIRWISE_DECISION_NAME = "PairwiseDecision";
|
||||
static const char* const ALGORITHM_ID_ATTRIBUTE = "algorithm-id";
|
||||
static const char* const SUB_CLASSIFIER_COUNT_NODE_NAME = "SubClassifierCount";
|
||||
static const char* const SUB_CLASSIFIERS_NODE_NAME = "SubClassifiers";
|
||||
static const char* const SUB_CLASSIFIER_NODE_NAME = "SubClassifier";
|
||||
static const char* const FIRST_CLASS_ATRRIBUTE_NAME = "first-class";
|
||||
static const char* const SECOND_CLASS_ATTRIBUTE_NAME = "second-class";
|
||||
|
||||
//This map is used to record the decision strategies available for each algorithm
|
||||
//std::map<uint64_t, OpenViBE::CIdentifier> g_oDecisionMap;
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::initialize()
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
op_configuration = nullptr;
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_pPairwise(this->getInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType));
|
||||
ip_pPairwise = CIdentifier::undefined().id();
|
||||
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
m_pairwiseDecisionID = CIdentifier::undefined();
|
||||
|
||||
return CAlgorithmPairingStrategy::initialize();
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::uninitialize()
|
||||
{
|
||||
if (m_decisionStrategyAlgorithm != nullptr)
|
||||
{
|
||||
m_decisionStrategyAlgorithm->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_decisionStrategyAlgorithm);
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
}
|
||||
|
||||
for (auto& kv : m_subClassifiers)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = kv.second;
|
||||
subClassifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*subClassifier);
|
||||
}
|
||||
this->m_subClassifiers.clear();
|
||||
|
||||
return CAlgorithmPairingStrategy::uninitialize();
|
||||
}
|
||||
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::train(const Toolkit::IFeatureVectorSet& dataset)
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClasses(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
m_nClasses = size_t(ip_nClasses);
|
||||
|
||||
m_nSubClassifiers = m_nClasses * (m_nClasses - 1) / 2;
|
||||
|
||||
createSubClassifiers();
|
||||
|
||||
//Create the decision strategy
|
||||
OV_ERROR_UNLESS_KRF(this->initializeExtraParameterMechanism(), "Failed to initialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
m_pairwiseDecisionID = this->getEnumerationParameter(
|
||||
OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType, OVP_TypeId_ClassificationPairwiseStrategy);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_pairwiseDecisionID != CIdentifier::undefined(),
|
||||
"Invalid pairwise decision strategy [" << OVP_TypeId_ClassificationPairwiseStrategy.str() << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
if (m_decisionStrategyAlgorithm != nullptr)
|
||||
{
|
||||
m_decisionStrategyAlgorithm->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_decisionStrategyAlgorithm);
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
}
|
||||
m_decisionStrategyAlgorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(m_pairwiseDecisionID));
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->initialize(), "Failed to unitialize decision strategy algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<CIdentifier*> ip_classificationAlgorithm(
|
||||
m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier));
|
||||
ip_classificationAlgorithm = &m_subClassifierAlgorithmID;
|
||||
Kernel::TParameterHandler<uint64_t> ip_classCount(m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
ip_classCount = m_nClasses;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize),
|
||||
"Failed to run decision strategy algorithm", Kernel::ErrorType::Internal);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->uninitializeExtraParameterMechanism(), "Failed to uninitialize extra parameters", Kernel::ErrorType::Internal);
|
||||
|
||||
//Calculate the amount of sample for each class
|
||||
std::map<double, size_t> classLabels;
|
||||
for (size_t i = 0; i < dataset.getFeatureVectorCount(); ++i)
|
||||
{
|
||||
if (!classLabels.count(dataset[i].getLabel())) { classLabels[dataset[i].getLabel()] = 0; }
|
||||
classLabels[dataset[i].getLabel()]++;
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(
|
||||
classLabels.size() == m_nClasses,
|
||||
"There are samples for " << classLabels.size() << " classes but expected samples for " << m_nClasses << " classes.",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
//Now we create the corresponding repartition set
|
||||
Kernel::TParameterHandler<CMatrix*> ip_pRepartitionSet = m_decisionStrategyAlgorithm->getInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
ip_pRepartitionSet->resize(m_nClasses);
|
||||
|
||||
const size_t size = dataset[0].getSize();
|
||||
//Now let's train each classifier
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
ip_pRepartitionSet->getBuffer()[i] = double(classLabels[double(i)]);
|
||||
|
||||
for (size_t j = i + 1; j < m_nClasses; ++j)
|
||||
{
|
||||
const size_t nFeature = classLabels[double(i)] + classLabels[double(j)];
|
||||
Kernel::IAlgorithmProxy* subClassifier = m_subClassifiers[std::pair<size_t, size_t>(i, j)];
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_dataset(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
ip_dataset->resize(nFeature, size + 1);
|
||||
|
||||
double* buffer = ip_dataset->getBuffer();
|
||||
for (size_t k = 0; k < dataset.getFeatureVectorCount(); ++k)
|
||||
{
|
||||
const double tmp = dataset[k].getLabel();
|
||||
if (tmp == double(i) || tmp == double(j))
|
||||
{
|
||||
memcpy(buffer, dataset[k].getBuffer(), size * sizeof(double));
|
||||
|
||||
buffer[size] = size_t(tmp) == i ? 0 : 1;
|
||||
buffer += (size + 1);
|
||||
}
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(
|
||||
subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Train),
|
||||
"Failed to train subclassifier [1st class = " << i << ", 2nd class = " << j << "]",
|
||||
Kernel::ErrorType::Internal);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm, "No decision strategy algorithm set", Kernel::ErrorType::BadConfig);
|
||||
|
||||
const size_t size = sample.getSize();
|
||||
std::vector<classification_info_t> classificationList;
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_proba = m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_InputParameter_ProbabilityMatrix);
|
||||
CMatrix* matrix = static_cast<CMatrix*>(ip_proba);
|
||||
|
||||
matrix->resize(m_nClasses, m_nClasses);
|
||||
|
||||
for (size_t i = 0; i < matrix->getBufferElementCount(); ++i) { matrix->getBuffer()[i] = 0.0; }
|
||||
|
||||
//Let's generate the matrix of confidence score
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
for (size_t j = i + 1; j < m_nClasses; ++j)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* tmp = m_subClassifiers[std::pair<size_t, size_t>(i, j)];
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(tmp->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector));
|
||||
Kernel::TParameterHandler<CMatrix*> op_values(tmp->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
Kernel::TParameterHandler<double> op_label(tmp->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
ip_sample->resize(size);
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
memcpy(buffer, sample.getBuffer(), size * sizeof(double));
|
||||
tmp->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify);
|
||||
|
||||
classification_info_t classificationInfo = { double(i), double(j), op_label, op_values };
|
||||
classificationList.push_back(classificationInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// for (size_t i =0 ; i < nClass ; ++i )
|
||||
// {
|
||||
// for (size_t j = 0; j < nClass ; ++j) { std::cout << matrix->getBuffer()[i*nClass + j] << " " ; }
|
||||
// std::cout << std::endl;
|
||||
// }
|
||||
// std::cout << std::endl;
|
||||
|
||||
Kernel::TParameterHandler<std::vector<classification_info_t>*> ip_infos(
|
||||
m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs));
|
||||
ip_infos = &classificationList;
|
||||
|
||||
//Then ask to the strategy to make the decision
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute), "Failed to compute decision strategy",
|
||||
Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> op_proba = m_decisionStrategyAlgorithm->getOutputParameter(
|
||||
OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector);
|
||||
double maxProb = -1;
|
||||
int selectedClassIdx = -1;
|
||||
|
||||
distance.setSize(0);
|
||||
probability.setSize(m_nClasses);
|
||||
|
||||
//We just have to take the most relevant now.
|
||||
for (size_t i = 0; i < m_nClasses; ++i)
|
||||
{
|
||||
const double tmp = op_proba->getBuffer()[i];
|
||||
if (tmp > maxProb)
|
||||
{
|
||||
selectedClassIdx = i;
|
||||
maxProb = tmp;
|
||||
}
|
||||
probability[i] = tmp;
|
||||
}
|
||||
|
||||
classId = double(selectedClassIdx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::createSubClassifiers()
|
||||
{
|
||||
// Clear any previous ones
|
||||
for (auto& kv : m_subClassifiers)
|
||||
{
|
||||
Kernel::IAlgorithmProxy* subClassifier = kv.second;
|
||||
subClassifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*subClassifier);
|
||||
}
|
||||
this->m_subClassifiers.clear();
|
||||
|
||||
//Now let's instantiate all the sub classifiers
|
||||
for (size_t firstClass = 0; firstClass < m_nClasses; ++firstClass)
|
||||
{
|
||||
for (size_t secondClass = firstClass + 1; secondClass < m_nClasses; ++secondClass)
|
||||
{
|
||||
const CIdentifier subClassifierAlgorithm = this->getAlgorithmManager().createAlgorithm(this->m_subClassifierAlgorithmID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(
|
||||
subClassifierAlgorithm != CIdentifier::undefined(),
|
||||
"Unable to instantiate classifier for class [" << this->m_subClassifierAlgorithmID.str() << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::IAlgorithmProxy* subClassifier = &this->getAlgorithmManager().getAlgorithm(subClassifierAlgorithm);
|
||||
subClassifier->initialize();
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_pNClasses(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
ip_pNClasses = 2;
|
||||
|
||||
//Set a references to the extra parameters input of the pairing strategy
|
||||
Kernel::TParameterHandler<std::map<CString, CString>*> ip_params(
|
||||
subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
ip_params.setReferenceTarget(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
|
||||
m_subClassifiers[std::pair<size_t, size_t>(firstClass, secondClass)] = subClassifier;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::designArchitecture(const CIdentifier& id, const size_t classCount)
|
||||
{
|
||||
if (!setSubClassifierIdentifier(id)) { return false; }
|
||||
m_nClasses = classCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsOne::getClassifierConfig(const double firstClass, const double secondClass, Kernel::IAlgorithmProxy* subClassifier)
|
||||
{
|
||||
XML::IXMLNode* res = XML::createNode(SUB_CLASSIFIER_NODE_NAME);
|
||||
|
||||
std::stringstream ssFirstClass, ssSecondClass;
|
||||
ssFirstClass << firstClass;
|
||||
ssSecondClass << secondClass;
|
||||
res->addAttribute(FIRST_CLASS_ATRRIBUTE_NAME, ssFirstClass.str().c_str());
|
||||
res->addAttribute(SECOND_CLASS_ATTRIBUTE_NAME, ssSecondClass.str().c_str());
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(subClassifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_SaveConfig);
|
||||
res->addChild(static_cast<XML::IXMLNode*>(op_config));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsOne::getPairwiseDecisionConfiguration() const
|
||||
{
|
||||
if (!m_decisionStrategyAlgorithm) { return nullptr; }
|
||||
|
||||
XML::IXMLNode* tmp = XML::createNode(PAIRWISE_DECISION_NAME);
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(m_decisionStrategyAlgorithm->getOutputParameter(OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config));
|
||||
m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig);
|
||||
tmp->addChild(static_cast<XML::IXMLNode*>(op_config));
|
||||
|
||||
tmp->addAttribute(ALGORITHM_ID_ATTRIBUTE, m_pairwiseDecisionID.str().c_str());
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmClassifierOneVsOne::saveConfig()
|
||||
{
|
||||
std::stringstream nClassifier;
|
||||
nClassifier << m_nSubClassifiers;
|
||||
|
||||
XML::IXMLNode* oneVsOneNode = XML::createNode(TYPE_NODE_NAME);
|
||||
|
||||
XML::IXMLNode* tmp = XML::createNode(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
tmp->addAttribute(ALGORITHM_ID_ATTRIBUTE, this->m_subClassifierAlgorithmID.str().c_str());
|
||||
tmp->setPCData(
|
||||
this->getTypeManager().getEnumerationEntryNameFromValue(OVTK_TypeId_ClassificationAlgorithm, m_subClassifierAlgorithmID.id()).
|
||||
toASCIIString());
|
||||
oneVsOneNode->addChild(tmp);
|
||||
|
||||
tmp = XML::createNode(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
tmp->setPCData(nClassifier.str().c_str());
|
||||
oneVsOneNode->addChild(tmp);
|
||||
|
||||
oneVsOneNode->addChild(this->getPairwiseDecisionConfiguration());
|
||||
|
||||
XML::IXMLNode* subClassifersNode = XML::createNode(SUB_CLASSIFIERS_NODE_NAME);
|
||||
|
||||
for (auto& kv : m_subClassifiers) { subClassifersNode->addChild(getClassifierConfig(kv.first.first, kv.first.second, kv.second)); }
|
||||
oneVsOneNode->addChild(subClassifersNode);
|
||||
|
||||
return oneVsOneNode;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::loadConfig(XML::IXMLNode* configNode)
|
||||
{
|
||||
XML::IXMLNode* tempNode = configNode->getChildByName(SUB_CLASSIFIER_IDENTIFIER_NODE_NAME);
|
||||
|
||||
CIdentifier algorithmID;
|
||||
algorithmID.fromString(tempNode->getAttribute(ALGORITHM_ID_ATTRIBUTE));
|
||||
|
||||
if (!this->setSubClassifierIdentifier(algorithmID))
|
||||
{
|
||||
//if the sub classifier doesn't have comparison function it is an error
|
||||
return false;
|
||||
}
|
||||
|
||||
tempNode = configNode->getChildByName(PAIRWISE_DECISION_NAME);
|
||||
CIdentifier pairwiseID;
|
||||
pairwiseID.fromString(tempNode->getAttribute(ALGORITHM_ID_ATTRIBUTE));
|
||||
if (pairwiseID != m_pairwiseDecisionID)
|
||||
{
|
||||
if (m_decisionStrategyAlgorithm != nullptr)
|
||||
{
|
||||
m_decisionStrategyAlgorithm->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_decisionStrategyAlgorithm);
|
||||
m_decisionStrategyAlgorithm = nullptr;
|
||||
}
|
||||
m_pairwiseDecisionID = pairwiseID;
|
||||
m_decisionStrategyAlgorithm = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(m_pairwiseDecisionID));
|
||||
m_decisionStrategyAlgorithm->initialize();
|
||||
}
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config));
|
||||
ip_config = tempNode->getChild(0);
|
||||
|
||||
Kernel::TParameterHandler<CIdentifier*> ip_algorithm(
|
||||
m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier));
|
||||
ip_algorithm = &algorithmID;
|
||||
|
||||
tempNode = configNode->getChildByName(SUB_CLASSIFIER_COUNT_NODE_NAME);
|
||||
std::stringstream ss(tempNode->getPCData());
|
||||
ss >> m_nSubClassifiers;
|
||||
|
||||
// Invert the class count from subCls = numClass*(numClass-1)/2.
|
||||
const size_t deltaCarre = 1 + 8 * m_nSubClassifiers;
|
||||
m_nClasses = size_t((1 + sqrt(double(deltaCarre))) / 2);
|
||||
|
||||
Kernel::TParameterHandler<uint64_t> ip_classCount(m_decisionStrategyAlgorithm->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
ip_classCount = m_nClasses;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig),
|
||||
"Loading decision strategy configuration failed", Kernel::ErrorType::Internal);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_decisionStrategyAlgorithm->process(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize),
|
||||
"Parameterizing decision strategy failed", Kernel::ErrorType::Internal);
|
||||
|
||||
return loadSubClassifierConfig(configNode->getChildByName(SUB_CLASSIFIERS_NODE_NAME));
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::loadSubClassifierConfig(XML::IXMLNode* node)
|
||||
{
|
||||
createSubClassifiers();
|
||||
|
||||
for (size_t i = 0; i < node->getChildCount(); ++i)
|
||||
{
|
||||
double firstClass, secondClass;
|
||||
|
||||
//Now we have to restore class indexes
|
||||
XML::IXMLNode* subClassifierNode = node->getChild(i);
|
||||
std::stringstream ss1(subClassifierNode->getAttribute(FIRST_CLASS_ATRRIBUTE_NAME));
|
||||
ss1 >> firstClass;
|
||||
std::stringstream ss2(subClassifierNode->getAttribute(SECOND_CLASS_ATTRIBUTE_NAME));
|
||||
ss2 >> secondClass;
|
||||
|
||||
Kernel::IAlgorithmProxy* subClassifier = m_subClassifiers[std::make_pair(size_t(firstClass), size_t(secondClass))];
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(subClassifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_config = subClassifierNode->getChild(0);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(subClassifier->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig),
|
||||
"Unable to load the configuration for the sub-classifier " << i + 1, Kernel::ErrorType::Internal);
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_subClassifiers.size() == m_nSubClassifiers,
|
||||
"Invalid number of loaded classifiers [" << m_subClassifiers.size() << "] (expected = " << m_nSubClassifiers << ")",
|
||||
Kernel::ErrorType::Internal);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmClassifierOneVsOne::setSubClassifierIdentifier(const CIdentifier& id)
|
||||
{
|
||||
m_subClassifierAlgorithmID = id;
|
||||
m_algorithmComparison = Toolkit::getClassificationComparisonFunction(id);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_algorithmComparison != nullptr, "No comparison function found for classifier " << m_subClassifierAlgorithmID.str(),
|
||||
Kernel::ErrorType::ResourceNotFound);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
//The aim of this structure is to record informations returned by the sub-classifier. They will be used by
|
||||
// pairwise decision algorithms to compute probability vector.
|
||||
// Should be use only by OneVsOne and pairwise decision algorithm
|
||||
typedef struct
|
||||
{
|
||||
double firstClass;
|
||||
double secondClass;
|
||||
double classLabel;
|
||||
//This output is probabilist
|
||||
CMatrix* classificationValue;
|
||||
} classification_info_t;
|
||||
|
||||
|
||||
class CAlgorithmClassifierOneVsOne final : public Toolkit::CAlgorithmPairingStrategy
|
||||
{
|
||||
public:
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool train(const Toolkit::IFeatureVectorSet& dataset) override;
|
||||
bool classify(const Toolkit::IFeatureVector& sample, double& classId, Toolkit::IVector& distance, Toolkit::IVector& probability) override;
|
||||
bool designArchitecture(const CIdentifier& id, const size_t classCount) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode* configNode) override;
|
||||
size_t getNProbabilities() override { return m_nClasses; }
|
||||
size_t getNDistances() override { return 0; }
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::CAlgorithmPairingStrategy, OVP_ClassId_Algorithm_ClassifierOneVsOne)
|
||||
|
||||
protected:
|
||||
|
||||
bool createSubClassifiers();
|
||||
|
||||
private:
|
||||
size_t m_nClasses = 0;
|
||||
size_t m_nSubClassifiers = 0;
|
||||
|
||||
std::map<std::pair<size_t, size_t>, Kernel::IAlgorithmProxy*> m_subClassifiers;
|
||||
fClassifierComparison m_algorithmComparison = nullptr;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_decisionStrategyAlgorithm = nullptr;
|
||||
CIdentifier m_pairwiseDecisionID = CIdentifier::undefined();
|
||||
|
||||
static XML::IXMLNode* getClassifierConfig(double firstClass, double secondClass, Kernel::IAlgorithmProxy* subClassifier);
|
||||
XML::IXMLNode* getPairwiseDecisionConfiguration() const;
|
||||
|
||||
// size_t getClassCount() const;
|
||||
|
||||
bool loadSubClassifierConfig(XML::IXMLNode* node);
|
||||
|
||||
// SSubClassifierDescriptor& getSubClassifierDescriptor(const size_t FirstClass, const size_t SecondClass);
|
||||
bool setSubClassifierIdentifier(const CIdentifier& id);
|
||||
};
|
||||
|
||||
class CAlgorithmClassifierOneVsOneDesc final : public Toolkit::CAlgorithmPairingStrategyDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("OneVsOne pairing classifier"); }
|
||||
CString getAuthorName() const override { return CString("Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/Loria"); }
|
||||
CString getShortDescription() const override { return CString(""); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.2"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ClassifierOneVsOne; }
|
||||
IPluginObject* create() override { return new CAlgorithmClassifierOneVsOne; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairingStrategyDesc::getAlgorithmPrototype(prototype);
|
||||
prototype.addInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType, "Pairwise Decision Strategy",
|
||||
Kernel::ParameterType_Enumeration, OVP_TypeId_ClassificationPairwiseStrategy);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairingStrategyDesc, OVP_ClassId_Algorithm_ClassifierOneVsOneDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
#include "ovpCAlgorithmConditionedCovariance.h"
|
||||
|
||||
/*
|
||||
* This implementation is based on the matlab code corresponding to
|
||||
*
|
||||
* Ledoit & Wolf: "A Well-Conditioned Estimator for Large-Dimensional Covariance Matrices", 2004.
|
||||
*
|
||||
*/
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
#define COV_DEBUG 0
|
||||
#if COV_DEBUG
|
||||
void CAlgorithmConditionedCovariance::dumpMatrix(Kernel::ILogManager &mgr, const MatrixXdRowMajor &mat, const CString &desc)
|
||||
{
|
||||
mgr << Kernel::LogLevel_Info << desc << "\n";
|
||||
for (int i = 0 ; i < mat.rows() ; i++)
|
||||
{
|
||||
mgr << Kernel::LogLevel_Info << "Row " << i << ": ";
|
||||
for (int j = 0 ; j < mat.cols() ; j++) { mgr << mat(i,j) << " "; }
|
||||
mgr << "\n";
|
||||
}
|
||||
}
|
||||
#else
|
||||
void CAlgorithmConditionedCovariance::dumpMatrix(Kernel::ILogManager& /* mgr */, const MatrixXdRowMajor& /*mat*/, const CString& /*desc*/) { }
|
||||
#endif
|
||||
|
||||
bool CAlgorithmConditionedCovariance::initialize()
|
||||
{
|
||||
// Default value setting
|
||||
Kernel::TParameterHandler<double> ip_shrinkage(getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage));
|
||||
ip_shrinkage = -1.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmConditionedCovariance::process()
|
||||
{
|
||||
// Set up the IO
|
||||
const Kernel::TParameterHandler<double> ip_shrinkage(getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage));
|
||||
const Kernel::TParameterHandler<CMatrix*> ip_sample(getInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet));
|
||||
Kernel::TParameterHandler<CMatrix*> op_mean(getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean));
|
||||
Kernel::TParameterHandler<CMatrix*> op_covMatrix(getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix));
|
||||
double shrinkage = ip_shrinkage;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(shrinkage <= 1.0, "Invalid shrinkage value " << shrinkage << "(expected value <= 1.0)", Kernel::ErrorType::BadConfig);
|
||||
|
||||
|
||||
OV_ERROR_UNLESS_KRF(ip_sample->getDimensionCount() == 2,
|
||||
"Invalid dimension count for vector set " << ip_sample->getDimensionCount() << "(expected value = 2)", Kernel::ErrorType::BadInput);
|
||||
|
||||
const size_t nRows = ip_sample->getDimensionSize(0);
|
||||
const size_t nCols = ip_sample->getDimensionSize(1);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(nRows >= 1 && nCols >= 1, "Invalid input matrix [" << nRows << "x" << nCols << "] (expected at least 1x1 size)",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
const double* buffer = ip_sample->getBuffer();
|
||||
|
||||
|
||||
OV_ERROR_UNLESS_KRF(buffer, "Invalid NULL feature set buffer", Kernel::ErrorType::BadInput);
|
||||
|
||||
// Set the output buffers so we can write the results to them without copy
|
||||
op_mean->resize(1, nCols);
|
||||
op_covMatrix->resize(nCols, nCols);
|
||||
|
||||
// Insert our data into an Eigen matrix. As Eigen doesn't have const double* constructor, we cast away the const.
|
||||
const Eigen::Map<MatrixXdRowMajor> dataMatrix(const_cast<double*>(buffer), nRows, nCols);
|
||||
|
||||
// Estimate the data center and center the data
|
||||
Eigen::Map<MatrixXdRowMajor> dataMean(op_mean->getBuffer(), 1, nCols);
|
||||
dataMean = dataMatrix.colwise().mean();
|
||||
const MatrixXdRowMajor dataCentered = dataMatrix.rowwise() - dataMean.row(0);
|
||||
|
||||
// Compute the sample cov matrix
|
||||
const Eigen::MatrixXd sampleCov = (dataCentered.transpose() * dataCentered) * (1 / double(nRows));
|
||||
|
||||
// Compute the prior cov matrix
|
||||
Eigen::MatrixXd priorCov = Eigen::MatrixXd::Zero(nCols, nCols);
|
||||
priorCov.diagonal().setConstant(sampleCov.diagonal().mean());
|
||||
|
||||
// Compute shrinkage coefficient if its not given
|
||||
if (shrinkage < 0)
|
||||
{
|
||||
const Eigen::MatrixXd dataSquared = dataCentered.cwiseProduct(dataCentered);
|
||||
const Eigen::MatrixXd phiMat = (dataSquared.transpose() * dataSquared) / double(nRows) - sampleCov.cwiseAbs2();
|
||||
|
||||
const double phi = phiMat.sum();
|
||||
const double gamma = (sampleCov - priorCov).squaredNorm(); // Frobenius norm
|
||||
const double kappa = phi / gamma;
|
||||
|
||||
shrinkage = std::max<double>(0, std::min<double>(1, kappa / double(nRows)));
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Phi " << phi << " Gamma " << gamma << " kappa " << kappa << "\n";
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Estimated shrinkage weight to be " << shrinkage << "\n";
|
||||
|
||||
dumpMatrix(this->getLogManager(), phiMat, "PhiMat");
|
||||
}
|
||||
else { this->getLogManager() << Kernel::LogLevel_Debug << "Using user-provided shrinkage weight " << shrinkage << "\n"; }
|
||||
|
||||
// Use the output as a buffer to avoid copying
|
||||
Eigen::Map<MatrixXdRowMajor> oCov(op_covMatrix->getBuffer(), nCols, nCols);
|
||||
|
||||
// Mix the prior and the sample estimates according to the shrinkage parameter
|
||||
oCov = shrinkage * priorCov + (1.0 - shrinkage) * sampleCov;
|
||||
|
||||
// Debug block
|
||||
dumpMatrix(this->getLogManager(), dataMean, "DataMean");
|
||||
dumpMatrix(this->getLogManager(), sampleCov, "Sample cov");
|
||||
dumpMatrix(this->getLogManager(), priorCov, "Prior cov");
|
||||
dumpMatrix(this->getLogManager(), oCov, "Output cov");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <Eigen/Dense>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CAlgorithmConditionedCovariance final : virtual public Toolkit::TAlgorithm<IAlgorithm>
|
||||
{
|
||||
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> MatrixXdRowMajor;
|
||||
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override;
|
||||
bool uninitialize() override { return true; }
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_ConditionedCovariance)
|
||||
|
||||
protected:
|
||||
// Debug method. Prints the matrix to the logManager. May be disabled in implementation.
|
||||
static void dumpMatrix(Kernel::ILogManager& mgr, const MatrixXdRowMajor& mat, const CString& desc);
|
||||
};
|
||||
|
||||
class CAlgorithmConditionedCovarianceDesc final : virtual public IAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Conditioned Covariance"); }
|
||||
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("Computes covariance with shrinkage."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString(
|
||||
"Shrinkage: {<0 = auto-estimate, [0,1] balance between prior and sample cov}. The conditioned covariance matrix may allow better accuracies with models that rely on inverting the cov matrix, in cases where the regular cov matrix is close to singular.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_ConditionedCovariance; }
|
||||
IPluginObject* create() override { return new CAlgorithmConditionedCovariance; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage, "Shrinkage (-1 == auto)", Kernel::ParameterType_Float);
|
||||
prototype.addInputParameter(OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet, "Feature vectors", Kernel::ParameterType_Matrix);
|
||||
|
||||
// The algorithm returns these outputs
|
||||
prototype.addOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean, "Mean vector", Kernel::ParameterType_Matrix);
|
||||
prototype.addOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix, "Covariance matrix", Kernel::ParameterType_Matrix);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_ConditionedCovarianceDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
#include "ovpCAlgorithmLDADiscriminantFunction.h"
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include <Eigen/Eigenvalues>
|
||||
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const BASE_NODE_NAME = "Class-config";
|
||||
static const char* const WEIGHT_NODE_NAME = "Weights";
|
||||
static const char* const BIAS_NODE_NAME = "Bias";
|
||||
|
||||
bool CAlgorithmLDADiscriminantFunction::loadConfig(const XML::IXMLNode* configuration)
|
||||
{
|
||||
std::stringstream bias(configuration->getChildByName(BIAS_NODE_NAME)->getPCData());
|
||||
bias >> m_bias;
|
||||
|
||||
std::stringstream data(configuration->getChildByName(WEIGHT_NODE_NAME)->getPCData());
|
||||
std::vector<double> coefficients;
|
||||
while (!data.eof())
|
||||
{
|
||||
double value;
|
||||
data >> value;
|
||||
coefficients.push_back(value);
|
||||
}
|
||||
|
||||
m_weight.resize(coefficients.size());
|
||||
for (size_t i = 0; i < coefficients.size(); ++i) { m_weight(i, 0) = coefficients[i]; }
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmLDADiscriminantFunction::getConfiguration()
|
||||
{
|
||||
XML::IXMLNode* rootNode = XML::createNode(BASE_NODE_NAME);
|
||||
|
||||
std::stringstream weigths, bias;
|
||||
|
||||
weigths << std::scientific;
|
||||
for (int i = 0; i < m_weight.size(); ++i) { weigths << " " << m_weight(i, 0); }
|
||||
|
||||
bias << m_bias;
|
||||
|
||||
XML::IXMLNode* tempNode = XML::createNode(WEIGHT_NODE_NAME);
|
||||
tempNode->setPCData(weigths.str().c_str());
|
||||
rootNode->addChild(tempNode);
|
||||
|
||||
tempNode = XML::createNode(BIAS_NODE_NAME);
|
||||
tempNode->setPCData(bias.str().c_str());
|
||||
rootNode->addChild(tempNode);
|
||||
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
#endif
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
|
||||
#include <Eigen/Eigenvalues>
|
||||
|
||||
#include "ovpCAlgorithmClassifierLDA.h"
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
//The purpose of this class is to compute the "membership" of a vector
|
||||
class CAlgorithmLDADiscriminantFunction
|
||||
{
|
||||
public:
|
||||
CAlgorithmLDADiscriminantFunction() {}
|
||||
|
||||
void setWeight(const Eigen::VectorXd& weigth) { m_weight = weigth; }
|
||||
void setBias(const double bias) { m_bias = bias; }
|
||||
|
||||
//Return the class membership of the feature vector
|
||||
double getValue(const Eigen::VectorXd& featureVector) { return (m_weight.transpose() * featureVector)(0) + m_bias; }
|
||||
size_t getNWeight() const { return m_weight.size(); }
|
||||
|
||||
|
||||
bool loadConfig(const XML::IXMLNode* configuration);
|
||||
XML::IXMLNode* getConfiguration();
|
||||
|
||||
const Eigen::VectorXd& getWeight() const { return m_weight; }
|
||||
double getBias() const { return m_bias; }
|
||||
|
||||
private:
|
||||
double m_bias = 0;
|
||||
Eigen::VectorXd m_weight;
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
#endif
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
#include <iostream>
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
|
||||
bool CAlgorithmPairwiseDecision::process()
|
||||
{
|
||||
// @note there is essentially no test that these are called in correct order. Caller be careful!
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute))
|
||||
{
|
||||
Kernel::TParameterHandler<std::vector<classification_info_t>*> ip_classifications = this->getInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs);
|
||||
Kernel::TParameterHandler<CMatrix*> op_probability = this->getOutputParameter(OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector);
|
||||
return this->compute(*static_cast<std::vector<classification_info_t>*>(ip_classifications), static_cast<CMatrix*>(op_probability));
|
||||
}
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig))
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(this->getOutputParameter(OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config));
|
||||
XML::IXMLNode* tmp = this->saveConfig();
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp != nullptr, "Invalid NULL xml node returned while saving configuration", Kernel::ErrorType::Internal);
|
||||
|
||||
op_configuration = tmp;
|
||||
return true;
|
||||
}
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig))
|
||||
{
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config));
|
||||
XML::IXMLNode* tmp = static_cast<XML::IXMLNode*>(op_config);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp != nullptr, "Invalid NULL xml node to load configuration in", Kernel::ErrorType::BadInput);
|
||||
|
||||
return this->loadConfig(*tmp);
|
||||
}
|
||||
if (this->isInputTriggerActive(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize)) { return this->parameterize(); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
#include "ovpCAlgorithmClassifierOneVsOne.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseDecision class
|
||||
* This is the default class for every decision usable with the One Vs One pairwise strategy.
|
||||
*/
|
||||
class CAlgorithmPairwiseDecision : virtual public Toolkit::TAlgorithm<IAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override = 0;
|
||||
bool uninitialize() override = 0;
|
||||
|
||||
virtual bool parameterize() = 0;
|
||||
|
||||
virtual bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) = 0;
|
||||
virtual XML::IXMLNode* saveConfig() = 0;
|
||||
virtual bool loadConfig(XML::IXMLNode& node) = 0;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TAlgorithm<IAlgorithm>, OVP_ClassId_Algorithm_PairwiseDecision)
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseDecisionDesc : virtual public IAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_InputParameter_ProbabilityMatrix, "Probability Matrix", Kernel::ParameterType_Matrix);
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config, "Configuration node", Kernel::ParameterType_Pointer);
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition, "Set repartition", Kernel::ParameterType_Matrix);
|
||||
prototype.addInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier, "Classification Algorithm", Kernel::ParameterType_Identifier);
|
||||
prototype.addInputParameter(
|
||||
OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs, "Classification Outputs", Kernel::ParameterType_Pointer);
|
||||
prototype.addInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount, "Class Count", Kernel::ParameterType_UInteger);
|
||||
|
||||
prototype.addOutputParameter(OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector, "Probability Vector", Kernel::ParameterType_Matrix);
|
||||
prototype.addOutputParameter(OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config, "Configuration node", Kernel::ParameterType_Pointer);
|
||||
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute, "Compute");
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize, "Parametrize");
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig, "Save configuration");
|
||||
prototype.addInputTrigger(OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig, "Load configuration");
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IAlgorithmDesc, OVP_ClassId_Algorithm_PairwiseDecisionDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
#define HT_DEBUG 0
|
||||
|
||||
#define ALPHA_DELTA 0.01
|
||||
#include "ovpCAlgorithmPairwiseDecisionHT.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
#include <xml/IXMLHandler.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "PairwiseDecision_HT";
|
||||
static const char* const REPARTITION_NODE_NAME = "Repartition";
|
||||
|
||||
bool CAlgorithmPairwiseDecisionHT::parameterize()
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClass(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
m_nClass = size_t(ip_nClass);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision HT algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool CAlgorithmPairwiseDecisionHT::compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision HT algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_Repartition = this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
std::vector<double> probability(m_nClass * m_nClass);
|
||||
|
||||
//First we set the diagonal to 0
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probability[i * m_nClass + i] = 0.; }
|
||||
|
||||
#if HT_DEBUG
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i){
|
||||
|
||||
for (size_t j = 0 ; j<m_nClass ; ++j){
|
||||
std::cout << probability[i*m_nClass + j] << " ";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (size_t i = 0; i < classifications.size(); ++i)
|
||||
{
|
||||
classification_info_t& temp = classifications[i];
|
||||
const size_t firstIdx = size_t(temp.firstClass);
|
||||
const size_t secondIdx = size_t(temp.secondClass);
|
||||
const double* values = temp.classificationValue->getBuffer();
|
||||
probability[firstIdx * m_nClass + secondIdx] = values[0];
|
||||
probability[secondIdx * m_nClass + firstIdx] = 1 - values[0];
|
||||
}
|
||||
|
||||
std::vector<double> p(m_nClass);
|
||||
std::vector<std::vector<double>> mu(m_nClass);
|
||||
size_t amountSample = 0;
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { mu[i].resize(m_nClass); }
|
||||
for (size_t i = 0; i < m_nClass; ++i) { amountSample += size_t(ip_Repartition->getBuffer()[i]); }
|
||||
for (size_t i = 0; i < m_nClass; ++i) { p[i] = ip_Repartition->getBuffer()[i] / amountSample; }
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < m_nClass; ++j)
|
||||
{
|
||||
if (i != j) { mu[i][j] = p[i] / (p[i] + p[j]); }
|
||||
else { mu[i][i] = 0; }
|
||||
}
|
||||
}
|
||||
|
||||
#if HT_DEBUG
|
||||
std::cout << "Initial probability and Mu" << std::endl;
|
||||
for (size_t i = 0 ; i < m_nClass ; ++i) { std::cout << p[i] << " "; }
|
||||
std::cout << std::endl << std::endl;
|
||||
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i)
|
||||
{
|
||||
for (size_t j = 0 ; j<m_nClass ; ++j) { std::cout << mu[i][j] << " "; }
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
|
||||
|
||||
size_t consecutiveAlpha = 0;
|
||||
size_t index = 0;
|
||||
while (consecutiveAlpha != m_nClass)
|
||||
{
|
||||
double firstSum = 0.0;
|
||||
double secondSum = 0.0;
|
||||
|
||||
for (size_t j = 0; j < m_nClass; ++j)
|
||||
{
|
||||
if (j != index)
|
||||
{
|
||||
const size_t temp = size_t(probability[index] + ip_Repartition->getBuffer()[j]);
|
||||
|
||||
firstSum += temp * probability[index * m_nClass + j];
|
||||
secondSum += temp * mu[index][j];
|
||||
}
|
||||
}
|
||||
|
||||
const double alpha = (secondSum != 0) ? firstSum / secondSum : 1;
|
||||
|
||||
for (size_t j = 0; j < m_nClass; ++j)
|
||||
{
|
||||
if (j != index)
|
||||
{
|
||||
mu[index][j] = (alpha * mu[index][j]) / (alpha * mu[index][j] + mu[j][index]);
|
||||
mu[j][index] = 1 - mu[index][j];
|
||||
}
|
||||
}
|
||||
|
||||
p[index] *= alpha;
|
||||
if (alpha > 1 - ALPHA_DELTA && alpha < 1 + ALPHA_DELTA) { ++consecutiveAlpha; }
|
||||
else { consecutiveAlpha = 0; }
|
||||
index = (index + 1) % m_nClass;
|
||||
|
||||
#if HT_DEBUG
|
||||
std::cout << "Intermediate probability, MU and alpha" << std::endl << alpha << std::endl;
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i) { std::cout << p[i] << " "; }
|
||||
std::cout << std::endl << std::endl;
|
||||
|
||||
for (size_t i = 0 ; i< m_nClass ; ++i)
|
||||
{
|
||||
for (size_t j = 0 ; j<m_nClass ; ++j) { std::cout << mu[i][j] << " "; }
|
||||
std::cout << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#if HT_DEBUG
|
||||
std::cout << "Result " << std::endl;
|
||||
for (size_t i = 0; i<m_nClass ; ++i) { std::cout << p[i] << " "; }
|
||||
std::cout << std::endl << std::endl;
|
||||
#endif
|
||||
|
||||
probabilities->resize(m_nClass);
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probabilities->getBuffer()[i] = p[i]; }
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmPairwiseDecisionHT::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* node = XML::createNode(TYPE_NODE_NAME);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_repartition = this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
const size_t nClass = ip_repartition->getDimensionSize(0);
|
||||
|
||||
std::stringstream ss;
|
||||
for (size_t i = 0; i < nClass; ++i) { ss << ip_repartition->getBuffer()[i] << " "; }
|
||||
XML::IXMLNode* repartition = XML::createNode(REPARTITION_NODE_NAME);
|
||||
repartition->setPCData(ss.str().c_str());
|
||||
node->addChild(repartition);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
bool CAlgorithmPairwiseDecisionHT::loadConfig(XML::IXMLNode& node)
|
||||
{
|
||||
std::stringstream ss(node.getChildByName(REPARTITION_NODE_NAME)->getPCData());
|
||||
Kernel::TParameterHandler<CMatrix*> ip_repartition = this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition);
|
||||
|
||||
|
||||
std::vector<double> repartition;
|
||||
while (!ss.eof())
|
||||
{
|
||||
size_t value;
|
||||
ss >> value;
|
||||
repartition.push_back(value);
|
||||
}
|
||||
|
||||
ip_repartition->resize(repartition.size());
|
||||
for (size_t i = 0; i < repartition.size(); ++i) { ip_repartition->getBuffer()[i] = repartition[i]; }
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseDecisionHT class is a decision strategy for the One Vs One pairwise decision that implement the
|
||||
* method describe in the article Hastie, Trevor; Tibshirani, Robert. Classification by pairwise coupling. The Annals of Statistics 26 (1998), no. 2, 451--471
|
||||
*
|
||||
* Probability required
|
||||
*/
|
||||
class CAlgorithmPairwiseDecisionHT final : virtual public CAlgorithmPairwiseDecision
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmPairwiseDecisionHT() { }
|
||||
void release() override { delete this; }
|
||||
bool initialize() override { return true; }
|
||||
bool uninitialize() override { return true; }
|
||||
bool parameterize() override;
|
||||
bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode& node) override;
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecision, OVP_ClassId_Algorithm_PairwiseDecision_HT)
|
||||
|
||||
private:
|
||||
size_t m_nClass = 0;
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseDecisionHTDesc final : virtual public CAlgorithmPairwiseDecisionDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Pairwise decision strategy based on HT"); }
|
||||
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString("This method is based on the method describe in the article "
|
||||
"Hastie, Trevor; Tibshirani, Robert. Classification by pairwise coupling."
|
||||
"The Annals of Statistics 26 (1998), no. 2, 451--471");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_PairwiseDecision_HT; }
|
||||
IPluginObject* create() override { return new CAlgorithmPairwiseDecisionHT; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairwiseDecisionDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecisionDesc, OVP_ClassId_Algorithm_PairwiseDecision_HTDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
#define VOTING_DEBUG 0
|
||||
#include "ovpCAlgorithmPairwiseDecisionVoting.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "PairwiseDecision_Voting";
|
||||
|
||||
bool CAlgorithmPairwiseDecisionVoting::parameterize()
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClass(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
m_nClass = size_t(ip_nClass);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision Voting algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmPairwiseDecisionVoting::compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision Voting algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
#if VOTING_DEBUG
|
||||
std::cout << classifications.size() << std::endl;
|
||||
|
||||
for (size_t i = 0 ; i < classifications.size() ; ++i)
|
||||
{
|
||||
std::cout << classifications[i].firstClass << " " << classifications[i].secondClass << std::endl;
|
||||
std::cout << classifications[i].classLabel << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<size_t> win(m_nClass);
|
||||
for (size_t i = 0; i < m_nClass; ++i) { win[i] = 0; }
|
||||
|
||||
for (size_t i = 0; i < classifications.size(); ++i)
|
||||
{
|
||||
classification_info_t& temp = classifications[i];
|
||||
if (temp.classLabel == 0) { ++(win[size_t(temp.firstClass)]); }
|
||||
else { ++(win[size_t(temp.secondClass)]); }
|
||||
}
|
||||
|
||||
#if VOTING_DEBUG
|
||||
for (size_t i = 0; i < m_nClass ; ++i) { std::cout << (double(win[i])/ classifications.size() << " "; }
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
|
||||
probabilities->resize(m_nClass);
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probabilities->getBuffer()[i] = double(win[i]) / classifications.size(); }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmPairwiseDecisionVoting::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* node = XML::createNode(TYPE_NODE_NAME);
|
||||
return node;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseDecisionVoting class
|
||||
* This strategy relies on a basic voting system. If class A beats class B, class A win 1 point and B 0 point. At the end, the vector of
|
||||
* probability is composed by the normalized score of each class.
|
||||
*
|
||||
* Probability required.
|
||||
*/
|
||||
class CAlgorithmPairwiseDecisionVoting final : virtual public CAlgorithmPairwiseDecision
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmPairwiseDecisionVoting() { }
|
||||
void release() override { delete this; }
|
||||
bool initialize() override { return true; }
|
||||
bool uninitialize() override { return true; }
|
||||
bool parameterize() override;
|
||||
bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode& /*node*/) override { return true; }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecision, OVP_ClassId_Algorithm_PairwiseDecision_Voting)
|
||||
|
||||
private:
|
||||
size_t m_nClass = 0;
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseDecisionVotingDesc final : virtual public CAlgorithmPairwiseDecisionDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Pairwise decision strategy based on Voting"); }
|
||||
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("."); }
|
||||
CString getDetailedDescription() const override { return CString(""); }
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_PairwiseDecision_Voting; }
|
||||
IPluginObject* create() override { return new CAlgorithmPairwiseDecisionVoting; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairwiseDecisionDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecisionDesc, OVP_ClassId_Algorithm_PairwiseDecision_VotingDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
#define PKPD_DEBUG 0
|
||||
#include "ovpCAlgorithmPairwiseStrategyPKPD.h"
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
static const char* const TYPE_NODE_NAME = "PairwiseDecision_PKDP";
|
||||
|
||||
bool CAlgorithmPairwiseStrategyPKPD::parameterize()
|
||||
{
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClass(this->getInputParameter(OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount));
|
||||
m_nClass = size_t(ip_nClass);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision PKPD algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CAlgorithmPairwiseStrategyPKPD::compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_nClass >= 2, "Pairwise decision PKPD algorithm needs at least 2 classes [" << m_nClass << "] found", Kernel::ErrorType::BadInput);
|
||||
|
||||
std::vector<double> matrix(m_nClass * m_nClass);
|
||||
|
||||
//First we set the diagonal to 0
|
||||
for (size_t i = 0; i < m_nClass; ++i) { matrix[i * m_nClass + i] = 0.; }
|
||||
|
||||
for (size_t i = 0; i < classifications.size(); ++i)
|
||||
{
|
||||
classification_info_t& temp = classifications[i];
|
||||
const size_t firstIdx = size_t(temp.firstClass);
|
||||
const size_t secondIdx = size_t(temp.secondClass);
|
||||
const double* values = temp.classificationValue->getBuffer();
|
||||
matrix[firstIdx * m_nClass + secondIdx] = values[0];
|
||||
matrix[secondIdx * m_nClass + firstIdx] = 1 - values[0];
|
||||
}
|
||||
|
||||
#if PKPD_DEBUG
|
||||
for (size_t i = 0 ; i < m_nClass ; ++i)
|
||||
{
|
||||
for (size_t j = 0 ; j < m_nClass ; ++j) { std::cout << matrix[i * m_nClass + j] << " "; }
|
||||
std::cout << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<double> probVector(m_nClass);
|
||||
double sum = 0;
|
||||
for (size_t classIdx = 0; classIdx < m_nClass; ++classIdx)
|
||||
{
|
||||
double tmpSum = 0;
|
||||
for (size_t secondClass = 0; secondClass < m_nClass; ++secondClass)
|
||||
{
|
||||
if (secondClass != classIdx) { tmpSum += 1 / matrix[m_nClass * classIdx + secondClass]; }
|
||||
}
|
||||
probVector[classIdx] = 1 / (tmpSum - (m_nClass - 2));
|
||||
sum += probVector[classIdx];
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probVector[i] /= sum; }
|
||||
|
||||
#if PKPD_DEBUG
|
||||
for (size_t i = 0; i < m_nClass ; ++i) { std::cout << probVector[i] << " "; }
|
||||
std::cout << std::endl;
|
||||
#endif
|
||||
|
||||
probabilities->resize(m_nClass);
|
||||
|
||||
for (size_t i = 0; i < m_nClass; ++i) { probabilities->getBuffer()[i] = probVector[i]; }
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
XML::IXMLNode* CAlgorithmPairwiseStrategyPKPD::saveConfig()
|
||||
{
|
||||
XML::IXMLNode* node = XML::createNode(TYPE_NODE_NAME);
|
||||
return node;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCAlgorithmPairwiseDecision.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
/**
|
||||
* @brief The CAlgorithmPairwiseStrategyPKPD class
|
||||
* This strategy relies on the algorithm describe in the article . Price, S. Knerr, L. Personnaz, and G. Dreyfus.
|
||||
* Pairwise neural network classifiers with probabilistic outputs. In G. Tesauro, D. Touretzky, and T. Leen (eds.)
|
||||
* Advances in Neural Information Processing Systems 7 (NIPS-94), pp. 1109-1116. MIT Press, 1995.
|
||||
*/
|
||||
class CAlgorithmPairwiseStrategyPKPD final : virtual public CAlgorithmPairwiseDecision
|
||||
{
|
||||
public:
|
||||
|
||||
CAlgorithmPairwiseStrategyPKPD() { }
|
||||
void release() override { delete this; }
|
||||
bool initialize() override { return true; }
|
||||
bool uninitialize() override { return true; }
|
||||
bool parameterize() override;
|
||||
bool compute(std::vector<classification_info_t>& classifications, CMatrix* probabilities) override;
|
||||
XML::IXMLNode* saveConfig() override;
|
||||
bool loadConfig(XML::IXMLNode& /*node*/) override { return true; }
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecision, OVP_ClassId_Algorithm_PairwiseStrategy_PKPD)
|
||||
|
||||
private:
|
||||
size_t m_nClass = 0;
|
||||
};
|
||||
|
||||
class CAlgorithmPairwiseStrategyPKPDDesc final : virtual public CAlgorithmPairwiseDecisionDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Pairwise decision strategy based on PKPD"); }
|
||||
CString getAuthorName() const override { return CString("Serrière Guillaume"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString("Price, S. Knerr, L. Personnaz, and G. Dreyfus."
|
||||
"Pairwise neural network classifiers with probabilistic outputs."
|
||||
" In G. Tesauro, D. Touretzky, and T. Leen (eds.)"
|
||||
"Advances in Neural Information Processing Systems 7 (NIPS-94), pp."
|
||||
" 1109-1116. MIT Press, 1995.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString(""); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_Algorithm_PairwiseStrategy_PKPD; }
|
||||
IPluginObject* create() override { return new CAlgorithmPairwiseStrategyPKPD; }
|
||||
|
||||
bool getAlgorithmPrototype(Kernel::IAlgorithmProto& prototype) const override
|
||||
{
|
||||
CAlgorithmPairwiseDecisionDesc::getAlgorithmPrototype(prototype);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(CAlgorithmPairwiseDecisionDesc, OVP_ClassId_Algorithm_PairwiseStrategy_PKPDDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
#include "ovpCBoxAlgorithmClassifierProcessor.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <xml/IXMLHandler.h>
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::loadClassifier(const char* filename)
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
}
|
||||
|
||||
XML::IXMLHandler* handler = XML::createXMLHandler();
|
||||
XML::IXMLNode* rootNode = handler->parseFile(filename);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(rootNode, "Unable to get xml root node from file at " << filename, Kernel::ErrorType::BadParsing);
|
||||
|
||||
m_stimulations.clear();
|
||||
|
||||
// Check the version of the file
|
||||
OV_ERROR_UNLESS_KRF(rootNode->hasAttribute(FORMAT_VERSION_ATTRIBUTE_NAME), "Configuration file [" << filename << "] has no version information",
|
||||
Kernel::ErrorType::ResourceNotFound);
|
||||
|
||||
std::stringstream data(rootNode->getAttribute(FORMAT_VERSION_ATTRIBUTE_NAME));
|
||||
size_t version;
|
||||
data >> version;
|
||||
|
||||
OV_WARNING_UNLESS_K(version <= OVP_Classification_BoxTrainerFormatVersion,
|
||||
"Classifier configuration in [" << filename << "] saved using a newer version: saved version = [" << version
|
||||
<< "] vs current version = [" << OVP_Classification_BoxTrainerFormatVersion << "]");
|
||||
|
||||
OV_ERROR_UNLESS_KRF(version >= OVP_Classification_BoxTrainerFormatVersionRequired,
|
||||
"Classifier configuration in [" << filename << "] saved using an obsolete version [" << version << "] (minimum expected version = "
|
||||
<< OVP_Classification_BoxTrainerFormatVersionRequired << ")", Kernel::ErrorType::BadVersion);
|
||||
|
||||
CIdentifier algorithmClassID = CIdentifier::undefined();
|
||||
|
||||
XML::IXMLNode* tmp = rootNode->getChildByName(STRATEGY_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp, "Configuration file [" << filename << "] has no node " << STRATEGY_NODE_NAME, Kernel::ErrorType::BadParsing);
|
||||
|
||||
algorithmClassID.fromString(tmp->getAttribute(IDENTIFIER_ATTRIBUTE_NAME));
|
||||
|
||||
//If the Identifier is undefined, that means we need to load a native algorithm
|
||||
if (algorithmClassID == CIdentifier::undefined())
|
||||
{
|
||||
tmp = rootNode->getChildByName(ALGORITHM_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp, "Configuration file [" << filename << "] has no node " << ALGORITHM_NODE_NAME, Kernel::ErrorType::BadParsing);
|
||||
|
||||
algorithmClassID.fromString(tmp->getAttribute(IDENTIFIER_ATTRIBUTE_NAME));
|
||||
|
||||
//If the algorithm is still unknown, that means that we face an error
|
||||
OV_ERROR_UNLESS_KRF(algorithmClassID != CIdentifier::undefined(), "No classifier retrieved from configuration file [" << filename << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
}
|
||||
|
||||
//Now loading all stimulations output
|
||||
XML::IXMLNode* stimNode = rootNode->getChildByName(STIMULATIONS_NODE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(stimNode, "Configuration file [" << filename << "] has no node " << STIMULATIONS_NODE_NAME, Kernel::ErrorType::BadParsing);
|
||||
|
||||
//Now load every stimulation and store them in the map with the right class id
|
||||
for (size_t i = 0; i < stimNode->getChildCount(); ++i)
|
||||
{
|
||||
tmp = stimNode->getChild(i);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(tmp, "Invalid NULL child node " << i << " for node [" << STIMULATIONS_NODE_NAME << "]", Kernel::ErrorType::BadParsing);
|
||||
|
||||
CString name(tmp->getPCData());
|
||||
|
||||
double classID;
|
||||
const char* att = tmp->getAttribute(IDENTIFIER_ATTRIBUTE_NAME);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(att, "Invalid child node " << i << " for node [" << STIMULATIONS_NODE_NAME << "]: attribute ["
|
||||
<< IDENTIFIER_ATTRIBUTE_NAME << "] not found", Kernel::ErrorType::BadParsing);
|
||||
|
||||
std::stringstream ss(att);
|
||||
ss >> classID;
|
||||
m_stimulations[classID] = this->getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, name);
|
||||
}
|
||||
|
||||
const CIdentifier id = this->getAlgorithmManager().createAlgorithm(algorithmClassID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(id != CIdentifier::undefined(),
|
||||
"Invalid classifier algorithm with id [" << algorithmClassID.str() << "] in configuration file [" << filename << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(id);
|
||||
m_classifier->initialize();
|
||||
|
||||
// Connect the params to the new classifier
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample = m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector);
|
||||
ip_sample.setReferenceTarget(m_sampleDecoder.getOutputMatrix());
|
||||
|
||||
m_hyperplanesEncoder.getInputMatrix().
|
||||
setReferenceTarget(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ClassificationValues));
|
||||
m_probabilitiesEncoder.getInputMatrix().setReferenceTarget(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_ProbabilityValues));
|
||||
// note: labelsencoder cannot be directly bound here as the classifier returns a float, but we need to output a stimulation
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_classificationConfig(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_classificationConfig = rootNode->getChildByName(CLASSIFIER_ROOT)->getChild(0);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig),
|
||||
"Loading configuration failed for subclassifier [" << id.str() << "]", Kernel::ErrorType::Internal);
|
||||
|
||||
rootNode->release();
|
||||
handler->release();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::initialize()
|
||||
{
|
||||
m_classifier = nullptr;
|
||||
|
||||
//First of all, let's get the XML file for configuration
|
||||
const CString configFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(configFilename != CString(""), "Invalid empty configuration file name", Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_sampleDecoder.initialize(*this, 0);
|
||||
m_stimDecoder.initialize(*this, 1);
|
||||
|
||||
m_labelsEncoder.initialize(*this, 0);
|
||||
m_hyperplanesEncoder.initialize(*this, 1);
|
||||
m_probabilitiesEncoder.initialize(*this, 2);
|
||||
|
||||
return loadClassifier(configFilename.toASCIIString());
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::uninitialize()
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
}
|
||||
|
||||
m_probabilitiesEncoder.uninitialize();
|
||||
m_hyperplanesEncoder.uninitialize();
|
||||
m_labelsEncoder.uninitialize();
|
||||
|
||||
m_stimDecoder.uninitialize();
|
||||
m_sampleDecoder.uninitialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierProcessor::process()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
|
||||
// Check if we have a command first
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
|
||||
{
|
||||
m_stimDecoder.decode(i);
|
||||
if (m_stimDecoder.isHeaderReceived()) { }
|
||||
if (m_stimDecoder.isBufferReceived())
|
||||
{
|
||||
for (size_t j = 0; j < m_stimDecoder.getOutputStimulationSet()->getStimulationCount(); ++j)
|
||||
{
|
||||
if (m_stimDecoder.getOutputStimulationSet()->getStimulationIdentifier(j) == OVTK_StimulationId_TrainCompleted)
|
||||
{
|
||||
CString configFilename = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
if (!loadClassifier(configFilename.toASCIIString())) { return false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_stimDecoder.isEndReceived()) { }
|
||||
}
|
||||
|
||||
// Classify data
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
|
||||
{
|
||||
const uint64_t startTime = boxContext.getInputChunkStartTime(0, i);
|
||||
const uint64_t endTime = boxContext.getInputChunkEndTime(0, i);
|
||||
|
||||
m_sampleDecoder.decode(i);
|
||||
if (m_sampleDecoder.isHeaderReceived())
|
||||
{
|
||||
m_labelsEncoder.encodeHeader();
|
||||
m_hyperplanesEncoder.encodeHeader();
|
||||
m_probabilitiesEncoder.encodeHeader();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(1, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(2, startTime, endTime);
|
||||
}
|
||||
if (m_sampleDecoder.isBufferReceived())
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify)
|
||||
&& m_classifier->isOutputTriggerActive(OVTK_Algorithm_Classifier_OutputTriggerId_Success),
|
||||
"Classification failed", Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<double> op_classificationState(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
|
||||
IStimulationSet* set = m_labelsEncoder.getInputStimulationSet();
|
||||
|
||||
set->setStimulationCount(1);
|
||||
set->setStimulationIdentifier(0, m_stimulations[op_classificationState]);
|
||||
set->setStimulationDate(0, endTime);
|
||||
set->setStimulationDuration(0, 0);
|
||||
|
||||
m_labelsEncoder.encodeBuffer();
|
||||
m_hyperplanesEncoder.encodeBuffer();
|
||||
m_probabilitiesEncoder.encodeBuffer();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(1, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(2, startTime, endTime);
|
||||
}
|
||||
|
||||
if (m_sampleDecoder.isEndReceived())
|
||||
{
|
||||
m_labelsEncoder.encodeEnd();
|
||||
m_hyperplanesEncoder.encodeEnd();
|
||||
m_probabilitiesEncoder.encodeEnd();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(1, startTime, endTime);
|
||||
boxContext.markOutputAsReadyToSend(2, startTime, endTime);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CBoxAlgorithmClassifierProcessor final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool processInput(const size_t index) override;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_ClassifierProcessor)
|
||||
|
||||
protected:
|
||||
bool loadClassifier(const char* filename);
|
||||
|
||||
private:
|
||||
|
||||
Toolkit::TFeatureVectorDecoder<CBoxAlgorithmClassifierProcessor> m_sampleDecoder;
|
||||
Toolkit::TStimulationDecoder<CBoxAlgorithmClassifierProcessor> m_stimDecoder;
|
||||
Toolkit::TStimulationEncoder<CBoxAlgorithmClassifierProcessor> m_labelsEncoder;
|
||||
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmClassifierProcessor> m_hyperplanesEncoder;
|
||||
Toolkit::TStreamedMatrixEncoder<CBoxAlgorithmClassifierProcessor> m_probabilitiesEncoder;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_classifier = nullptr;
|
||||
|
||||
std::map<double, uint64_t> m_stimulations;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmClassifierProcessorDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Classifier processor"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard, Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
CString getShortDescription() const override { return CString("Generic classification, relying on several box algorithms"); }
|
||||
|
||||
CString getDetailedDescription() const override { return CString("Classifies incoming feature vectors using a previously learned classifier."); }
|
||||
|
||||
CString getCategory() const override { return CString("Classification"); }
|
||||
CString getVersion() const override { return CString("2.1"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.1.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ClassifierProcessor; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmClassifierProcessor; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Features", OV_TypeId_FeatureVector);
|
||||
prototype.addInput("Commands", OV_TypeId_Stimulations);
|
||||
prototype.addOutput("Labels", OV_TypeId_Stimulations);
|
||||
prototype.addOutput("Hyperplane distance", OV_TypeId_StreamedMatrix);
|
||||
prototype.addOutput("Probability values", OV_TypeId_StreamedMatrix);
|
||||
|
||||
//We load everything in the save filed
|
||||
prototype.addSetting("Filename to load configuration from", OV_TypeId_Filename, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
// virtual IBoxListener* createBoxListener() const { return new CBoxAlgorithmCommonClassifierListener(5); }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ClassifierProcessorDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+590
@@ -0,0 +1,590 @@
|
||||
#include "ovpCBoxAlgorithmClassifierTrainer.h"
|
||||
#include <system/ovCMath.h>
|
||||
|
||||
#include <xml/IXMLHandler.h>
|
||||
#include <xml/IXMLNode.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <iomanip> // setw
|
||||
//This needs to reachable from outside
|
||||
const char* const CLASSIFIER_ROOT = "OpenViBE-Classifier";
|
||||
const char* const FORMAT_VERSION_ATTRIBUTE_NAME = "FormatVersion";
|
||||
const char* const CREATOR_ATTRIBUTE_NAME = "Creator";
|
||||
const char* const CREATOR_VERSION_ATTRIBUTE_NAME = "CreatorVersion";
|
||||
const char* const IDENTIFIER_ATTRIBUTE_NAME = "class-id";
|
||||
const char* const STRATEGY_NODE_NAME = "Strategy-Identifier";
|
||||
const char* const ALGORITHM_NODE_NAME = "Algorithm-Identifier";
|
||||
const char* const STIMULATIONS_NODE_NAME = "Stimulations";
|
||||
const char* const REJECTED_CLASS_NODE_NAME = "Rejected-Class";
|
||||
const char* const CLASS_STIMULATION_NODE_NAME = "Class-Stimulation";
|
||||
const char* const CLASSIFICATION_BOX_ROOT = "OpenViBE-Classifier-Box";
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::initialize()
|
||||
{
|
||||
m_classifier = nullptr;
|
||||
m_parameter = nullptr;
|
||||
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
//As we add some parameter in the middle of "static" parameters, we cannot rely on settings index.
|
||||
m_parameter = new std::map<CString, CString>();
|
||||
for (size_t i = 0; i < boxContext.getSettingCount(); ++i)
|
||||
{
|
||||
CString name;
|
||||
boxContext.getSettingName(i, name);
|
||||
const CString value = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), i);
|
||||
(*m_parameter)[name] = value;
|
||||
}
|
||||
|
||||
bool isPairing = false;
|
||||
|
||||
const CString configFilename(FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2));
|
||||
|
||||
OV_ERROR_UNLESS_KRF(configFilename != CString(""), "Invalid empty configuration filename", Kernel::ErrorType::BadSetting);
|
||||
|
||||
CIdentifier classifierAlgorithmClassID;
|
||||
|
||||
const CIdentifier strategyClassID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationStrategy, (*m_parameter)[MULTICLASS_STRATEGY_SETTING_NAME]);
|
||||
classifierAlgorithmClassID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationAlgorithm, (*m_parameter)[ALGORITHM_SETTING_NAME]);
|
||||
|
||||
if (strategyClassID == CIdentifier::undefined())
|
||||
{
|
||||
//That means that we want to use a classical algorithm so just let's create it
|
||||
const CIdentifier classifierAlgorithmID = this->getAlgorithmManager().createAlgorithm(classifierAlgorithmClassID);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(classifierAlgorithmID != CIdentifier::undefined(),
|
||||
"Unable to instantiate classifier for class [" << classifierAlgorithmID.str() << "]", Kernel::ErrorType::BadConfig);
|
||||
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(classifierAlgorithmID);
|
||||
m_classifier->initialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
isPairing = true;
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(strategyClassID));
|
||||
m_classifier->initialize();
|
||||
}
|
||||
m_trainStimulation = this->getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, (*m_parameter)[TRAIN_TRIGGER_SETTING_NAME]);
|
||||
|
||||
const int64_t nPartition = this->getConfigurationManager().expandAsInteger((*m_parameter)[FOLD_SETTING_NAME]);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(nPartition >= 0, "Invalid partition count [" << nPartition << "] (expected value >= 0)", Kernel::ErrorType::BadSetting);
|
||||
|
||||
m_nPartition = uint64_t(nPartition);
|
||||
|
||||
m_stimDecoder.initialize(*this, 0);
|
||||
for (size_t i = 1; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
m_sampleDecoder.push_back(new Toolkit::TFeatureVectorDecoder<CBoxAlgorithmClassifierTrainer>());
|
||||
m_sampleDecoder.back()->initialize(*this, i);
|
||||
}
|
||||
|
||||
//We link the parameters to the extra parameters input parameter to transmit them
|
||||
Kernel::TParameterHandler<std::map<CString, CString>*> ip_parameter(
|
||||
m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter));
|
||||
ip_parameter = m_parameter;
|
||||
|
||||
m_encoder.initialize(*this, 0);
|
||||
|
||||
m_nFeatures.clear();
|
||||
|
||||
OV_ERROR_UNLESS_KRF(boxContext.getInputCount() >= 2, "Invalid input count [" << boxContext.getInputCount() << "] (at least 2 input expected)",
|
||||
Kernel::ErrorType::BadSetting);
|
||||
|
||||
// Provide the number of classes to the classifier
|
||||
const size_t nClass = boxContext.getInputCount() - 1;
|
||||
Kernel::TParameterHandler<uint64_t> ip_nClasses(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NClasses));
|
||||
ip_nClasses = nClass;
|
||||
|
||||
//If we have to deal with a pairing strategy we have to pass argument
|
||||
if (isPairing)
|
||||
{
|
||||
Kernel::TParameterHandler<CIdentifier*> ip_classId(
|
||||
m_classifier->getInputParameter(OVTK_Algorithm_PairingStrategy_InputParameterId_SubClassifierAlgorithm));
|
||||
ip_classId = &classifierAlgorithmClassID;
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_PairingStrategy_InputTriggerId_DesignArchitecture), "Failed to design architecture",
|
||||
Kernel::ErrorType::Internal);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::uninitialize()
|
||||
{
|
||||
m_stimDecoder.uninitialize();
|
||||
m_encoder.uninitialize();
|
||||
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_sampleDecoder.size(); ++i)
|
||||
{
|
||||
m_sampleDecoder[i]->uninitialize();
|
||||
delete m_sampleDecoder[i];
|
||||
}
|
||||
m_sampleDecoder.clear();
|
||||
|
||||
m_encoder.uninitialize();
|
||||
m_stimDecoder.uninitialize();
|
||||
|
||||
for (size_t i = 0; i < m_datasets.size(); ++i)
|
||||
{
|
||||
delete m_datasets[i].sampleMatrix;
|
||||
m_datasets[i].sampleMatrix = nullptr;
|
||||
}
|
||||
m_datasets.clear();
|
||||
|
||||
if (m_parameter)
|
||||
{
|
||||
delete m_parameter;
|
||||
m_parameter = nullptr;
|
||||
}
|
||||
|
||||
// @fixme who frees this? freeing here -> crash
|
||||
/*
|
||||
if(m_pExtraParameter != nullptr)
|
||||
{
|
||||
delete m_pExtraParameter;
|
||||
m_pExtraParameter = NULL;
|
||||
}
|
||||
*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the most likely class and resample the dataset so that each class is as likely
|
||||
bool CBoxAlgorithmClassifierTrainer::balanceDataset()
|
||||
{
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
const size_t nClass = boxContext.getInputCount() - 1;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Balancing dataset...\n";
|
||||
|
||||
// Collect index set of feature vectors per class
|
||||
std::vector<std::vector<size_t>> classIndexes;
|
||||
classIndexes.resize(nClass);
|
||||
for (size_t i = 0; i < m_datasets.size(); ++i) { classIndexes[m_datasets[i].inputIdx].push_back(i); }
|
||||
|
||||
// Count how many vectors the largest class has
|
||||
size_t nMax = 0;
|
||||
for (size_t i = 0; i < nClass; ++i) { nMax = std::max<size_t>(nMax, classIndexes[i].size()); }
|
||||
|
||||
m_balancedDatasets.clear();
|
||||
|
||||
// Pad those classes with resampled examples (sampling with replacement) that have fewer examples than the largest class
|
||||
for (size_t i = 0; i < nClass; ++i)
|
||||
{
|
||||
const size_t examplesInClass = classIndexes[i].size();
|
||||
const size_t paddingNeeded = nMax - examplesInClass;
|
||||
if (examplesInClass == 0)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Cannot resample class " << i << ", 0 examples\n";
|
||||
continue;
|
||||
}
|
||||
if (paddingNeeded > 0) { this->getLogManager() << Kernel::LogLevel_Debug << "Padding class " << i << " with " << paddingNeeded << " examples\n"; }
|
||||
|
||||
// Copy all the examples first to a temporary array so we don't mess with the original data.
|
||||
// This is not too bad as instead of data, we copy the pointer. m_datasets owns the data pointer.
|
||||
const std::vector<size_t>& thisClassesIndexes = classIndexes[i];
|
||||
for (size_t j = 0; j < examplesInClass; ++j) { m_balancedDatasets.push_back(m_datasets[thisClassesIndexes[j]]); }
|
||||
|
||||
for (size_t j = 0; j < paddingNeeded; ++j)
|
||||
{
|
||||
const size_t sampledIndex = System::Math::randomWithCeiling(examplesInClass);
|
||||
const sample_t& sourceVector = m_datasets[thisClassesIndexes[sampledIndex]];
|
||||
m_balancedDatasets.push_back(sourceVector);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::process()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
const size_t nInput = this->getStaticBoxContext().getInputCount();
|
||||
|
||||
bool startTrain = false;
|
||||
|
||||
// Parses stimulations
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
|
||||
{
|
||||
m_stimDecoder.decode(i);
|
||||
|
||||
if (m_stimDecoder.isHeaderReceived())
|
||||
{
|
||||
m_encoder.encodeHeader();
|
||||
boxContext.markOutputAsReadyToSend(0, 0, 0);
|
||||
}
|
||||
if (m_stimDecoder.isBufferReceived())
|
||||
{
|
||||
const IStimulationSet* iStimulationSet = m_stimDecoder.getOutputStimulationSet();
|
||||
IStimulationSet* oStimulationSet = m_encoder.getInputStimulationSet();
|
||||
oStimulationSet->clear();
|
||||
|
||||
for (size_t j = 0; j < iStimulationSet->getStimulationCount(); ++j)
|
||||
{
|
||||
if (iStimulationSet->getStimulationIdentifier(j) == m_trainStimulation)
|
||||
{
|
||||
startTrain = true;
|
||||
const uint64_t id = this->getTypeManager().getEnumerationEntryValueFromName(OV_TypeId_Stimulation, "OVTK_StimulationId_TrainCompleted");
|
||||
oStimulationSet->appendStimulation(id, iStimulationSet->getStimulationDate(j), 0);
|
||||
}
|
||||
}
|
||||
m_encoder.encodeBuffer();
|
||||
|
||||
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
|
||||
}
|
||||
if (m_stimDecoder.isEndReceived())
|
||||
{
|
||||
m_encoder.encodeEnd();
|
||||
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
|
||||
}
|
||||
}
|
||||
|
||||
// Parses feature vectors
|
||||
for (size_t i = 1; i < nInput; ++i)
|
||||
{
|
||||
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
|
||||
{
|
||||
m_sampleDecoder[i - 1]->decode(j);
|
||||
|
||||
if (m_sampleDecoder[i - 1]->isHeaderReceived()) { }
|
||||
if (m_sampleDecoder[i - 1]->isBufferReceived())
|
||||
{
|
||||
const CMatrix* sampleMatrix = m_sampleDecoder[i - 1]->getOutputMatrix();
|
||||
|
||||
sample_t sample;
|
||||
sample.sampleMatrix = new CMatrix();
|
||||
sample.startTime = boxContext.getInputChunkStartTime(i, j);
|
||||
sample.endTime = boxContext.getInputChunkEndTime(i, j);
|
||||
sample.inputIdx = i - 1;
|
||||
|
||||
sample.sampleMatrix->copy(*sampleMatrix);
|
||||
m_datasets.push_back(sample);
|
||||
m_nFeatures[i]++;
|
||||
}
|
||||
if (m_sampleDecoder[i - 1]->isEndReceived()) { }
|
||||
}
|
||||
}
|
||||
|
||||
// On train stimulation reception, build up the labelled feature vector set matrix and go on training
|
||||
if (startTrain)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(m_datasets.size() >= m_nPartition,
|
||||
"Received fewer examples (" << m_datasets.size() << ") than specified partition count (" << m_nPartition << ")",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(!m_datasets.empty(), "No training example received", Kernel::ErrorType::BadInput);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Received train stimulation. Data dim is [" << m_datasets.size() << "x"
|
||||
<< m_datasets[0].sampleMatrix->getBufferElementCount() << "]\n";
|
||||
for (size_t i = 1; i < nInput; ++i)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "For information, we have " << m_nFeatures[i] << " feature vector(s) for input " << i << "\n";
|
||||
}
|
||||
|
||||
const bool balancedDataset = this->getConfigurationManager().expandAsBoolean((*m_parameter)[BALANCE_SETTING_NAME]);
|
||||
if (balancedDataset) { balanceDataset(); }
|
||||
|
||||
const std::vector<sample_t>& actualDataset = (balancedDataset ? m_balancedDatasets : m_datasets);
|
||||
|
||||
std::vector<double> partitionAccuracies(m_nPartition);
|
||||
|
||||
const bool randomizeVectorOrder = this->getConfigurationManager().expandAsBoolean("${Plugin_Classification_RandomizeKFoldTestData}", false);
|
||||
|
||||
// create a vector used for mapping feature vectors (initialize it as v[i] = i)
|
||||
std::vector<size_t> featurePermutation;
|
||||
for (size_t i = 0; i < actualDataset.size(); ++i) { featurePermutation.push_back(i); }
|
||||
|
||||
// randomize the vector if necessary
|
||||
if (randomizeVectorOrder)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Randomizing the feature vector set\n";
|
||||
random_shuffle(featurePermutation.begin(), featurePermutation.end(), System::Math::randomWithCeiling);
|
||||
}
|
||||
|
||||
const size_t nClass = nInput - 1;
|
||||
CMatrix confusion(nClass, nClass);
|
||||
|
||||
if (m_nPartition >= 2)
|
||||
{
|
||||
double partitionAccuracy = 0;
|
||||
double finalAccuracy = 0;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "k-fold test could take quite a long time, be patient\n";
|
||||
for (size_t i = 0; i < m_nPartition; ++i)
|
||||
{
|
||||
const size_t startIdx = size_t(((i) * actualDataset.size()) / m_nPartition);
|
||||
const size_t stopIdx = size_t(((i + 1) * actualDataset.size()) / m_nPartition);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Training on partition " << i << " (feature vectors " << startIdx << " to " <<
|
||||
stopIdx - 1 << ")...\n";
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->train(actualDataset, featurePermutation, startIdx, stopIdx), "Training failed: bailing out (from xval)",
|
||||
Kernel::ErrorType::Internal);
|
||||
|
||||
partitionAccuracy = this->getAccuracy(actualDataset, featurePermutation, startIdx, stopIdx, confusion);
|
||||
partitionAccuracies[i] = partitionAccuracy;
|
||||
finalAccuracy += partitionAccuracy;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Finished with partition " << i + 1 << " / " << m_nPartition << " (performance : "
|
||||
<< partitionAccuracy << "%)\n";
|
||||
}
|
||||
|
||||
const double mean = finalAccuracy / m_nPartition;
|
||||
double deviation = 0;
|
||||
|
||||
for (size_t i = 0; i < m_nPartition; ++i)
|
||||
{
|
||||
const double diff = partitionAccuracies[i] - mean;
|
||||
deviation += diff * diff;
|
||||
}
|
||||
deviation = sqrt(deviation / m_nPartition);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Cross-validation test accuracy is " << mean << "% (sigma = " << deviation << "%)\n";
|
||||
|
||||
printConfusionMatrix(confusion);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Training without cross-validation.\n";
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "*** Reported training set accuracy will be optimistic ***\n";
|
||||
}
|
||||
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Training final classifier on the whole set...\n";
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->train(actualDataset, featurePermutation, 0, 0),
|
||||
"Training failed: bailing out (from whole set training)", Kernel::ErrorType::Internal);
|
||||
|
||||
confusion.resetBuffer();
|
||||
const double accuracy = this->getAccuracy(actualDataset, featurePermutation, 0, actualDataset.size(), confusion);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Training set accuracy is " << accuracy << "% (optimistic)\n";
|
||||
|
||||
printConfusionMatrix(confusion);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(this->saveConfig(), "Failed to save configuration", Kernel::ErrorType::Internal);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::train(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation, const size_t startIdx,
|
||||
const size_t stopIdx)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(stopIdx - startIdx != 1, "Invalid indexes: stopIdx - trainIndex = 1", Kernel::ErrorType::BadArgument);
|
||||
|
||||
const size_t nSample = dataset.size() - (stopIdx - startIdx);
|
||||
const size_t nFeature = dataset[0].sampleMatrix->getBufferElementCount();
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet));
|
||||
|
||||
ip_sample->resize(nSample, nFeature + 1);
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
for (size_t j = 0; j < dataset.size() - (stopIdx - startIdx); ++j)
|
||||
{
|
||||
const size_t k = permutation[(j < startIdx ? j : j + (stopIdx - startIdx))];
|
||||
const double classId = double(dataset[k].inputIdx);
|
||||
memcpy(buffer, dataset[k].sampleMatrix->getBuffer(), nFeature * sizeof(double));
|
||||
|
||||
buffer[nFeature] = classId;
|
||||
buffer += (nFeature + 1);
|
||||
}
|
||||
|
||||
OV_ERROR_UNLESS_KRF(m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Train), "Training failed", Kernel::ErrorType::Internal);
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_configuration(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
XML::IXMLNode* node = static_cast<XML::IXMLNode*>(op_configuration);
|
||||
|
||||
if (node != nullptr) { node->release(); }
|
||||
op_configuration = nullptr;
|
||||
|
||||
return m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_SaveConfig);
|
||||
}
|
||||
|
||||
// Note that this function is incremental for confusionMatrix and can be called many times; so we don't clear the matrix
|
||||
double CBoxAlgorithmClassifierTrainer::getAccuracy(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation,
|
||||
const size_t startIdx, const size_t stopIdx, CMatrix& confusionMatrix)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(stopIdx != startIdx, "Invalid indexes: start index equals stop index", Kernel::ErrorType::BadArgument);
|
||||
|
||||
const size_t nFeature = dataset[0].sampleMatrix->getBufferElementCount();
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
XML::IXMLNode* node = op_config;//Requested for affectation
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> ip_config(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_Config));
|
||||
ip_config = node;
|
||||
|
||||
m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_LoadConfig);
|
||||
|
||||
Kernel::TParameterHandler<CMatrix*> ip_sample(m_classifier->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_FeatureVector));
|
||||
Kernel::TParameterHandler<double> op_classificationState(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Class));
|
||||
ip_sample->resize(nFeature);
|
||||
|
||||
size_t nSuccess = 0;
|
||||
|
||||
for (size_t j = startIdx; j < stopIdx; ++j)
|
||||
{
|
||||
const size_t k = permutation[j];
|
||||
|
||||
double* buffer = ip_sample->getBuffer();
|
||||
const double correctValue = double(dataset[k].inputIdx);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Try to recognize " << correctValue << "\n";
|
||||
|
||||
memcpy(buffer, dataset[k].sampleMatrix->getBuffer(), nFeature * sizeof(double));
|
||||
|
||||
m_classifier->process(OVTK_Algorithm_Classifier_InputTriggerId_Classify);
|
||||
|
||||
const double predictedValue = op_classificationState;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Recognize " << predictedValue << "\n";
|
||||
|
||||
if (predictedValue == correctValue) { nSuccess++; }
|
||||
|
||||
if (predictedValue < confusionMatrix.getDimensionSize(0) && correctValue < confusionMatrix.getDimensionSize(0))
|
||||
{
|
||||
double* buf = confusionMatrix.getBuffer();
|
||||
buf[size_t(correctValue) * confusionMatrix.getDimensionSize(1) + size_t(predictedValue)] += 1.0;
|
||||
}
|
||||
else { std::cout << "error\n"; }
|
||||
}
|
||||
|
||||
return double((nSuccess * 100.0) / (stopIdx - startIdx));
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::printConfusionMatrix(const CMatrix& oMatrix)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(oMatrix.getDimensionCount() == 2 && oMatrix.getDimensionSize(0) == oMatrix.getDimensionSize(1),
|
||||
"Invalid confution matrix [dim count = " << oMatrix.getDimensionCount() << ", dim size 0 = "
|
||||
<< oMatrix.getDimensionSize(0) << ", dim size 1 = "<< oMatrix.getDimensionSize(1) << "] (expected 2 dimensions with same size)",
|
||||
Kernel::ErrorType::BadArgument);
|
||||
|
||||
const size_t rows = oMatrix.getDimensionSize(0);
|
||||
|
||||
if (rows > 10 && !this->getConfigurationManager().expandAsBoolean("${Plugin_Classification_ForceConfusionMatrixPrint}"))
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Info <<
|
||||
"Over 10 classes, not printing the confusion matrix. If needed, override with setting Plugin_Classification_ForceConfusionMatrixPrint token to true.\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
CMatrix tmp(oMatrix), rowSum(rows);
|
||||
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
{
|
||||
const size_t idx = i * rows;
|
||||
for (size_t j = 0; j < rows; ++j) { rowSum[i] += tmp[idx + j]; }
|
||||
for (size_t j = 0; j < rows; ++j) { tmp[idx + j] /= rowSum[i]; }
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::fixed;
|
||||
|
||||
ss << " Cls vs cls ";
|
||||
for (size_t i = 0; i < rows; ++i) { ss << std::setw(6) << (i + 1); }
|
||||
this->getLogManager() << Kernel::LogLevel_Info << ss.str() << "\n";
|
||||
|
||||
ss.precision(1);
|
||||
for (size_t i = 0; i < rows; ++i)
|
||||
{
|
||||
ss.str("");
|
||||
ss << " Target " << std::setw(2) << (i + 1) << ": ";
|
||||
for (size_t j = 0; j < rows; ++j) { ss << std::setw(6) << tmp[i * rows + j] * 100; }
|
||||
this->getLogManager() << Kernel::LogLevel_Info << ss.str() << " %, " << size_t(rowSum[i]) << " examples\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmClassifierTrainer::saveConfig()
|
||||
{
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
|
||||
Kernel::TParameterHandler<XML::IXMLNode*> op_config(m_classifier->getOutputParameter(OVTK_Algorithm_Classifier_OutputParameterId_Config));
|
||||
XML::IXMLNode* algorithmConfigNode = XML::createNode(CLASSIFIER_ROOT);
|
||||
algorithmConfigNode->addChild(static_cast<XML::IXMLNode*>(op_config));
|
||||
|
||||
XML::IXMLHandler* handler = XML::createXMLHandler();
|
||||
const CString configurationFilename(this->getConfigurationManager().expand((*m_parameter)[FILENAME_SETTING_NAME]));
|
||||
|
||||
XML::IXMLNode* root = XML::createNode(CLASSIFICATION_BOX_ROOT);
|
||||
std::stringstream version;
|
||||
version << OVP_Classification_BoxTrainerFormatVersion;
|
||||
root->addAttribute(FORMAT_VERSION_ATTRIBUTE_NAME, version.str().c_str());
|
||||
|
||||
const auto cleanup = [&]()
|
||||
{
|
||||
handler->release();
|
||||
root->release();
|
||||
op_config = nullptr;
|
||||
};
|
||||
root->addAttribute(CREATOR_ATTRIBUTE_NAME, this->getConfigurationManager().expand("${Application_Name}"));
|
||||
root->addAttribute(CREATOR_VERSION_ATTRIBUTE_NAME, this->getConfigurationManager().expand("${Application_Version}"));
|
||||
|
||||
XML::IXMLNode* tempNode = XML::createNode(STRATEGY_NODE_NAME);
|
||||
const CIdentifier strategyClassId = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationStrategy, (*m_parameter)[MULTICLASS_STRATEGY_SETTING_NAME]);
|
||||
tempNode->addAttribute(IDENTIFIER_ATTRIBUTE_NAME, strategyClassId.str().c_str());
|
||||
tempNode->setPCData((*m_parameter)[MULTICLASS_STRATEGY_SETTING_NAME].toASCIIString());
|
||||
root->addChild(tempNode);
|
||||
|
||||
tempNode = XML::createNode(ALGORITHM_NODE_NAME);
|
||||
const CIdentifier classifierClassId = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationAlgorithm, (*m_parameter)[ALGORITHM_SETTING_NAME]);
|
||||
tempNode->addAttribute(IDENTIFIER_ATTRIBUTE_NAME, classifierClassId.str().c_str());
|
||||
tempNode->setPCData((*m_parameter)[ALGORITHM_SETTING_NAME].toASCIIString());
|
||||
root->addChild(tempNode);
|
||||
|
||||
|
||||
XML::IXMLNode* stimulationsNode = XML::createNode(STIMULATIONS_NODE_NAME);
|
||||
|
||||
for (size_t i = 1; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
const std::string name = "Class " + std::to_string(i) + " label";
|
||||
const std::string id = std::to_string(i - 1);
|
||||
tempNode = XML::createNode(CLASS_STIMULATION_NODE_NAME);
|
||||
tempNode->addAttribute(IDENTIFIER_ATTRIBUTE_NAME, id.c_str());
|
||||
tempNode->setPCData((*m_parameter)[name.c_str()].toASCIIString());
|
||||
stimulationsNode->addChild(tempNode);
|
||||
}
|
||||
root->addChild(stimulationsNode);
|
||||
|
||||
root->addChild(algorithmConfigNode);
|
||||
|
||||
if (!handler->writeXMLInFile(*root, configurationFilename.toASCIIString()))
|
||||
{
|
||||
cleanup();
|
||||
OV_ERROR_KRF("Failed saving configuration to file [" << configurationFilename << "]", Kernel::ErrorType::BadFileWrite);
|
||||
}
|
||||
|
||||
cleanup();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include "ovpCBoxAlgorithmCommonClassifierListener.inl"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
const char* const TRAIN_TRIGGER_SETTING_NAME = "Train trigger";
|
||||
const char* const FILENAME_SETTING_NAME = "Filename to save configuration to";
|
||||
const char* const MULTICLASS_STRATEGY_SETTING_NAME = "Multiclass strategy to apply";
|
||||
const char* const ALGORITHM_SETTING_NAME = "Algorithm to use";
|
||||
const char* const FOLD_SETTING_NAME = "Number of partitions for k-fold cross-validation test";
|
||||
const char* const BALANCE_SETTING_NAME = "Balance classes";
|
||||
|
||||
class CBoxAlgorithmClassifierTrainer final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool processInput(const size_t index) override;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_ClassifierTrainer)
|
||||
|
||||
protected:
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CMatrix* sampleMatrix;
|
||||
uint64_t startTime;
|
||||
uint64_t endTime;
|
||||
size_t inputIdx;
|
||||
} sample_t;
|
||||
|
||||
bool train(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation, size_t startIdx, size_t stopIdx);
|
||||
double getAccuracy(const std::vector<sample_t>& dataset, const std::vector<size_t>& permutation, size_t startIdx, size_t stopIdx, CMatrix& confusionMatrix);
|
||||
bool printConfusionMatrix(const CMatrix& oMatrix);
|
||||
bool balanceDataset();
|
||||
|
||||
private:
|
||||
bool saveConfig();
|
||||
|
||||
protected:
|
||||
|
||||
std::map<size_t, size_t> m_nFeatures;
|
||||
|
||||
Kernel::IAlgorithmProxy* m_classifier = nullptr;
|
||||
uint64_t m_trainStimulation = 0;
|
||||
size_t m_nPartition = 0;
|
||||
|
||||
Toolkit::TStimulationDecoder<CBoxAlgorithmClassifierTrainer> m_stimDecoder;
|
||||
std::vector<Toolkit::TFeatureVectorDecoder<CBoxAlgorithmClassifierTrainer>*> m_sampleDecoder;
|
||||
|
||||
Toolkit::TStimulationEncoder<CBoxAlgorithmClassifierTrainer> m_encoder;
|
||||
|
||||
std::map<CString, CString>* m_parameter = nullptr;
|
||||
|
||||
std::vector<sample_t> m_datasets;
|
||||
std::vector<sample_t> m_balancedDatasets;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmClassifierTrainerDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Classifier trainer"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard, Guillaume Serriere"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA/IRISA"); }
|
||||
|
||||
CString getShortDescription() const override { return CString("Generic classifier trainer, relying on several box algorithms"); }
|
||||
|
||||
CString getDetailedDescription() const override { return CString("Performs classifier training with cross-validation -based error estimation"); }
|
||||
|
||||
CString getCategory() const override { return CString("Classification"); }
|
||||
CString getVersion() const override { return CString("2.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.1.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_ClassifierTrainer; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmClassifierTrainer; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Stimulations", OV_TypeId_Stimulations);
|
||||
prototype.addInput("Features for class 1", OV_TypeId_FeatureVector);
|
||||
prototype.addInput("Features for class 2", OV_TypeId_FeatureVector);
|
||||
|
||||
prototype.addOutput("Train-completed Flag", OV_TypeId_Stimulations);
|
||||
|
||||
prototype.addSetting(TRAIN_TRIGGER_SETTING_NAME, OV_TypeId_Stimulation, "OVTK_StimulationId_Train");
|
||||
prototype.addSetting(FILENAME_SETTING_NAME, OV_TypeId_Filename, "${Path_UserData}/my-classifier.xml");
|
||||
|
||||
prototype.addSetting(MULTICLASS_STRATEGY_SETTING_NAME, OVTK_TypeId_ClassificationStrategy, "Native");
|
||||
//Pairing startegy argument
|
||||
//Class label
|
||||
|
||||
prototype.addSetting(ALGORITHM_SETTING_NAME, OVTK_TypeId_ClassificationAlgorithm, "Linear Discrimimant Analysis (LDA)");
|
||||
//Argument of algorithm
|
||||
|
||||
prototype.addSetting(FOLD_SETTING_NAME, OV_TypeId_Integer, "10");
|
||||
prototype.addSetting(BALANCE_SETTING_NAME, OV_TypeId_Boolean, "false");
|
||||
|
||||
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
|
||||
|
||||
// prototype.addFlag(Kernel::BoxFlag_ManualUpdate);
|
||||
return true;
|
||||
}
|
||||
|
||||
IBoxListener* createBoxListener() const override
|
||||
{
|
||||
const size_t nCommonSetting = 6;
|
||||
return new CBoxAlgorithmCommonClassifierListener(nCommonSetting);
|
||||
}
|
||||
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_ClassifierTrainerDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+375
@@ -0,0 +1,375 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
//#define OV_DEBUG_CLASSIFIER_LISTENER
|
||||
|
||||
#ifdef OV_DEBUG_CLASSIFIER_LISTENER
|
||||
#define DEBUG_PRINT(x) x
|
||||
#else
|
||||
#define DEBUG_PRINT(x)
|
||||
#endif
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CBoxAlgorithmCommonClassifierListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
|
||||
explicit CBoxAlgorithmCommonClassifierListener(const size_t customSettingBase) : m_customSettingBase(customSettingBase) { }
|
||||
|
||||
bool initialize() override
|
||||
{
|
||||
//Even if everything should have been set in constructor, we still set everything in initialize (in case of)
|
||||
m_classifierClassID = CIdentifier::undefined();
|
||||
m_classifier = nullptr;
|
||||
|
||||
//CIdentifier::undefined() is already use for the native, We initialize to an unused identifier in the strategy list
|
||||
m_strategyClassID = 0x0;
|
||||
m_strategy = nullptr;
|
||||
|
||||
//This value means that we need to calculate it
|
||||
m_strategyAmountSettings = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool uninitialize() override
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
}
|
||||
if (m_strategy)
|
||||
{
|
||||
m_strategy->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_strategy);
|
||||
m_strategy = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool initializedStrategy(Kernel::IBox& box)
|
||||
{
|
||||
CString name;
|
||||
box.getSettingName(getStrategyIndex() + 1, name);
|
||||
if (name == CString(PAIRWISE_STRATEGY_ENUMERATION_NAME)) { m_strategyAmountSettings = 1; }
|
||||
else { m_strategyAmountSettings = 0; }
|
||||
return true;
|
||||
}
|
||||
|
||||
//virtual bool onAlgorithmClassIdentifierChanged(Kernel::IBox &box)
|
||||
//{
|
||||
//this->initializedStrategy(box);
|
||||
//return true;
|
||||
//}
|
||||
|
||||
int getStrategySettingsCount(Kernel::IBox& box)
|
||||
{
|
||||
if (m_strategyAmountSettings < 0) { initializedStrategy(box); } //The value have never been initialized
|
||||
return m_strategyAmountSettings;
|
||||
}
|
||||
|
||||
static bool onInputAddedOrRemoved(Kernel::IBox& box)
|
||||
{
|
||||
box.setInputType(0, OV_TypeId_Stimulations);
|
||||
box.setInputName(0, "Stimulations");
|
||||
for (size_t i = 1; i < box.getInputCount(); ++i)
|
||||
{
|
||||
box.setInputName(i, ("Features for class " + std::to_string(i)).c_str());
|
||||
box.setInputType(i, OV_TypeId_FeatureVector);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onInputAdded(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
//index represent the number of the class (because of rejected offset)
|
||||
const std::string name = "Class " + std::to_string(index) + " label";
|
||||
std::stringstream stim;
|
||||
stim.fill('0');
|
||||
stim << "OVTK_StimulationId_Label_" << std::setw(2) << index;
|
||||
box.addSetting(name.c_str(), OV_TypeId_Stimulation, stim.str().c_str(), 3 - 1 + getStrategySettingsCount(box) + index);
|
||||
|
||||
//Rename input
|
||||
return onInputAddedOrRemoved(box);
|
||||
}
|
||||
|
||||
bool onInputRemoved(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
//First remove the removed input from settings
|
||||
box.removeSetting(3 - 1 + getStrategySettingsCount(box) + index);
|
||||
|
||||
//Then rename the remains inputs in settings
|
||||
for (size_t i = 1; i < box.getInputCount(); ++i)
|
||||
{
|
||||
const std::string name = "Class " + std::to_string(i) + " label";
|
||||
box.setSettingName(3 - 1 + getStrategySettingsCount(box) + i, name.c_str());
|
||||
}
|
||||
|
||||
//Then rename input
|
||||
return onInputAddedOrRemoved(box);
|
||||
}
|
||||
|
||||
bool onInitialized(Kernel::IBox& box) override
|
||||
{
|
||||
//We need to know if the box is already initialized (can be called after a restore state)
|
||||
CString strategyName;
|
||||
box.getSettingName(getStrategyIndex() + 2, strategyName);//this one is a class label
|
||||
const std::string settingName(strategyName.toASCIIString());
|
||||
|
||||
if (settingName.find("Class ") == std::string::npos)//We haven't initialized the box so let's do it
|
||||
{
|
||||
//Now added Settings for classes
|
||||
for (size_t i = 1; i < box.getInputCount(); ++i)
|
||||
{
|
||||
const std::string name = "Class " + std::to_string(i) + " label";
|
||||
std::stringstream stim;
|
||||
stim.fill('0');
|
||||
stim << "OVTK_StimulationId_Label_" << std::setw(2) << i;
|
||||
box.addSetting(name.c_str(), OV_TypeId_Stimulation, stim.str().c_str(), 3 - 1 + getStrategySettingsCount(box) + i);
|
||||
DEBUG_PRINT(std::cout << "Add setting (type D) " << buffer << " " << stimulation << "\n";)
|
||||
}
|
||||
return this->onAlgorithmClassifierChanged(box);
|
||||
}
|
||||
return true;
|
||||
//return this->onAlgorithmClassifierChanged(box);
|
||||
}
|
||||
|
||||
//Return the index of the combo box used to select the strategy (native/ OnevsOne...)
|
||||
static size_t getStrategyIndex() { return 2; }
|
||||
|
||||
//Return the index of the combo box used to select the classification algorithm
|
||||
size_t getClassifierIndex(Kernel::IBox& box) { return getStrategySettingsCount(box) + 3 + box.getInputCount() - 1; }
|
||||
|
||||
bool onSettingValueChanged(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
if (index == getClassifierIndex(box)) { return this->onAlgorithmClassifierChanged(box); }
|
||||
if (index == getStrategyIndex()) { return this->onStrategyChanged(box); }
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool updateDecision(Kernel::IBox& box)
|
||||
{
|
||||
const size_t i = getStrategyIndex() + 1;
|
||||
if (m_strategyClassID == OVP_ClassId_Algorithm_ClassifierOneVsOne)
|
||||
{
|
||||
CString classifierName = "Unknown";
|
||||
box.getSettingValue(getClassifierIndex(box), classifierName);
|
||||
const CIdentifier typeID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVP_TypeId_OneVsOne_DecisionAlgorithms, classifierName);
|
||||
|
||||
OV_ERROR_UNLESS_KRF(typeID != CIdentifier::undefined(),
|
||||
"Unable to find Pairwise Decision for the algorithm [" << m_classifierClassID.str() << "] (" << classifierName << ")",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
Kernel::IParameter* param = m_strategy->getInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType);
|
||||
Kernel::TParameterHandler<uint64_t> ip_parameter(param);
|
||||
|
||||
const CString entry = this->getTypeManager().getTypeName(typeID);
|
||||
uint64_t value = ip_parameter;
|
||||
uint64_t idx;
|
||||
CString name;
|
||||
|
||||
box.getSettingValue(i, name);
|
||||
|
||||
const uint64_t oldID = this->getTypeManager().getEnumerationEntryValueFromName(typeID, name);
|
||||
//The previous strategy does not exists in the new enum, let's switch to the default value (the first)
|
||||
if (oldID == CIdentifier::undefined().id()) { idx = 0; }
|
||||
else { idx = oldID; }
|
||||
|
||||
this->getTypeManager().getEnumerationEntry(typeID, idx, name, value);
|
||||
ip_parameter = value;
|
||||
|
||||
box.setSettingType(i, typeID);
|
||||
box.setSettingName(i, entry);
|
||||
box.setSettingValue(i, name);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onStrategyChanged(Kernel::IBox& box)
|
||||
{
|
||||
CString name;
|
||||
|
||||
box.getSettingValue(getStrategyIndex(), name);
|
||||
|
||||
const CIdentifier id = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVTK_TypeId_ClassificationStrategy, name);
|
||||
if (id != m_strategyClassID)
|
||||
{
|
||||
if (m_strategy)
|
||||
{
|
||||
m_strategy->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_strategy);
|
||||
m_strategy = nullptr;
|
||||
m_strategyClassID = CIdentifier::undefined();
|
||||
}
|
||||
if (id != CIdentifier::undefined())
|
||||
{
|
||||
m_strategy = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(id));
|
||||
m_strategy->initialize();
|
||||
m_strategyClassID = id;
|
||||
}
|
||||
|
||||
for (size_t i = getStrategyIndex() + getStrategySettingsCount(box); i > getStrategyIndex(); --i)
|
||||
{
|
||||
DEBUG_PRINT(std::cout << "Remove pairing strategy setting at idx " << i-1 << "\n";)
|
||||
box.removeSetting(i);
|
||||
}
|
||||
m_strategyAmountSettings = 0;
|
||||
}
|
||||
else { return true; } //If we don't change the strategy we just have to return
|
||||
|
||||
if (m_strategy)
|
||||
{
|
||||
box.getSettingValue(getClassifierIndex(box), name);
|
||||
const size_t i = getStrategyIndex() + 1;
|
||||
if (m_strategyClassID == OVP_ClassId_Algorithm_ClassifierOneVsOne)
|
||||
{
|
||||
const CIdentifier typeID = this->getTypeManager().getEnumerationEntryValueFromName(
|
||||
OVP_TypeId_OneVsOne_DecisionAlgorithms, name);
|
||||
OV_ERROR_UNLESS_KRF(typeID != CIdentifier::undefined(),
|
||||
"Unable to find Pairwise Decision for the algorithm [" << m_classifierClassID.str() << "]",
|
||||
Kernel::ErrorType::BadConfig);
|
||||
|
||||
//As we just switch to this strategy, we take the default value set in the strategy to initialize the value
|
||||
Kernel::IParameter* param = m_strategy->getInputParameter(OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType);
|
||||
const Kernel::TParameterHandler<uint64_t> ip_param(param);
|
||||
const uint64_t value = ip_param;
|
||||
name = this->getTypeManager().getEnumerationEntryNameFromValue(typeID, value);
|
||||
|
||||
const CString paramName = this->getTypeManager().getTypeName(typeID);
|
||||
|
||||
DEBUG_PRINT(std::cout << "Adding setting (case C) " << paramName << " : '" << name << "' to index " << i << "\n";)
|
||||
box.addSetting(paramName, typeID, name, i);
|
||||
|
||||
m_strategyAmountSettings = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onAlgorithmClassifierChanged(Kernel::IBox& box)
|
||||
{
|
||||
CString name;
|
||||
box.getSettingValue(getClassifierIndex(box), name);
|
||||
CIdentifier id = this->getTypeManager().getEnumerationEntryValueFromName(OVTK_TypeId_ClassificationAlgorithm, name);
|
||||
if (id != m_classifierClassID)
|
||||
{
|
||||
if (m_classifier)
|
||||
{
|
||||
m_classifier->uninitialize();
|
||||
this->getAlgorithmManager().releaseAlgorithm(*m_classifier);
|
||||
m_classifier = nullptr;
|
||||
m_classifierClassID = CIdentifier::undefined();
|
||||
}
|
||||
if (id != CIdentifier::undefined())
|
||||
{
|
||||
m_classifier = &this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(id));
|
||||
m_classifier->initialize();
|
||||
m_classifierClassID = id;
|
||||
}
|
||||
|
||||
//Disable the graphical refresh to avoid abusive redraw (not really a problem)
|
||||
while (box.getSettingCount() >= m_customSettingBase + box.getInputCount() + getStrategySettingsCount(box))
|
||||
{
|
||||
box.removeSetting(getClassifierIndex(box) + 1);
|
||||
}
|
||||
}
|
||||
else { return true; }//If we don't change the algorithm we just have to return
|
||||
|
||||
if (m_classifier)
|
||||
{
|
||||
size_t i = getClassifierIndex(box) + 1;
|
||||
while ((id = m_classifier->getNextInputParameterIdentifier(id)) != CIdentifier::undefined())
|
||||
{
|
||||
if ((id != OVTK_Algorithm_Classifier_InputParameterId_FeatureVector)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_FeatureVectorSet)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_Config)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_NClasses)
|
||||
&& (id != OVTK_Algorithm_Classifier_InputParameterId_ExtraParameter))
|
||||
{
|
||||
CIdentifier typeID;
|
||||
CString paramName = m_classifier->getInputParameterName(id);
|
||||
Kernel::IParameter* param = m_classifier->getInputParameter(id);
|
||||
Kernel::TParameterHandler<int64_t> ip_iParameter(param);
|
||||
Kernel::TParameterHandler<uint64_t> ip_uiParameter(param);
|
||||
Kernel::TParameterHandler<double> ip_dParameter(param);
|
||||
Kernel::TParameterHandler<bool> ip_bParameter(param);
|
||||
Kernel::TParameterHandler<CString*> ip_sParameter(param);
|
||||
std::string buffer;
|
||||
bool valid = true;
|
||||
switch (param->getType())
|
||||
{
|
||||
case Kernel::ParameterType_Enumeration:
|
||||
buffer = this->getTypeManager().getEnumerationEntryNameFromValue(param->getSubTypeIdentifier(), ip_uiParameter).toASCIIString();
|
||||
typeID = param->getSubTypeIdentifier();
|
||||
break;
|
||||
|
||||
case Kernel::ParameterType_Integer:
|
||||
case Kernel::ParameterType_UInteger:
|
||||
buffer = std::to_string(int64_t(ip_iParameter));
|
||||
typeID = OV_TypeId_Integer;
|
||||
break;
|
||||
|
||||
case Kernel::ParameterType_Boolean:
|
||||
buffer = (bool(ip_bParameter)) ? "true" : "false";
|
||||
typeID = OV_TypeId_Boolean;
|
||||
break;
|
||||
|
||||
case Kernel::ParameterType_Float:
|
||||
buffer = std::to_string(double(ip_dParameter));
|
||||
typeID = OV_TypeId_Float;
|
||||
break;
|
||||
case Kernel::ParameterType_String:
|
||||
buffer = static_cast<CString*>(ip_sParameter)->toASCIIString();
|
||||
typeID = OV_TypeId_String;
|
||||
break;
|
||||
default:
|
||||
std::cout << "Invalid parameter type " << param->getType() << "\n";
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
// @FIXME argh, the -2 is a hard coding that the classifier trainer has 2 settings after the classifier setting... ouch
|
||||
DEBUG_PRINT(std::cout << "Adding setting (case A) " << paramName << " : " << buffer << " to slot "
|
||||
<< box.getSettingCount() - 2 << "\n";)
|
||||
box.addSetting(paramName, typeID, buffer.c_str(), box.getSettingCount() - 2);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// This changes the pairwise strategy decision voting type of the box settings allowing
|
||||
// designer to list the correct choices in the combo box.
|
||||
updateDecision(box);
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
|
||||
protected:
|
||||
|
||||
CIdentifier m_classifierClassID = CIdentifier::undefined();
|
||||
CIdentifier m_strategyClassID =
|
||||
0x0; // CIdentifier::undefined() is already use, We initialize to an unused identifier in the strategy list
|
||||
Kernel::IAlgorithmProxy* m_classifier = nullptr;
|
||||
Kernel::IAlgorithmProxy* m_strategy = nullptr;
|
||||
const size_t m_customSettingBase = 0;
|
||||
int m_strategyAmountSettings = -1;
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
#include "ovpCBoxAlgorithmVotingClassifier.h"
|
||||
|
||||
#include <list>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::initialize()
|
||||
{
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
|
||||
m_classificationChoiceEncoder.initialize(*this, 0);
|
||||
|
||||
CIdentifier typeID;
|
||||
boxContext.getInputType(0, typeID);
|
||||
m_matrixBased = (typeID == OV_TypeId_StreamedMatrix);
|
||||
|
||||
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
if (m_matrixBased)
|
||||
{
|
||||
auto* decoder = new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmVotingClassifier>();
|
||||
decoder->initialize(*this, i);
|
||||
input.decoder = decoder;
|
||||
input.op_matrix = decoder->getOutputMatrix();
|
||||
input.twoValueInput = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
auto* decoder = new Toolkit::TStimulationDecoder<CBoxAlgorithmVotingClassifier>();
|
||||
decoder->initialize(*this, i);
|
||||
input.decoder = decoder;
|
||||
input.op_stimSet = decoder->getOutputStimulationSet();
|
||||
input.twoValueInput = false;
|
||||
}
|
||||
}
|
||||
|
||||
m_nRepetitions = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
m_targetClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
m_nonTargetClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 2);
|
||||
m_rejectClassLabel = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 3);
|
||||
m_resultClassLabelBase = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 4);
|
||||
m_chooseOneIfExAequo = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 5);
|
||||
|
||||
m_lastTime = 0;
|
||||
|
||||
m_classificationChoiceEncoder.encodeHeader();
|
||||
this->getDynamicBoxContext().markOutputAsReadyToSend(0, m_lastTime, this->getPlayerContext().getCurrentTime());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::uninitialize()
|
||||
{
|
||||
const size_t nInput = this->getStaticBoxContext().getInputCount();
|
||||
|
||||
for (size_t i = 0; i < nInput; ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
input.decoder->uninitialize();
|
||||
delete input.decoder;
|
||||
}
|
||||
|
||||
m_classificationChoiceEncoder.uninitialize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::processInput(const size_t /*index*/)
|
||||
{
|
||||
this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmVotingClassifier::process()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
const size_t nInput = this->getStaticBoxContext().getInputCount();
|
||||
|
||||
bool canChoose = true;
|
||||
|
||||
for (size_t i = 0; i < nInput; ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
|
||||
{
|
||||
input.decoder->decode(j);
|
||||
|
||||
if (input.decoder->isHeaderReceived())
|
||||
{
|
||||
if (m_matrixBased)
|
||||
{
|
||||
if (input.op_matrix->getBufferElementCount() != 1)
|
||||
{
|
||||
OV_ERROR_UNLESS_KRF(input.op_matrix->getBufferElementCount() == 2,
|
||||
"Invalid input matrix with [" << input.op_matrix->getBufferElementCount() << "] (expected values must be 1 or 2)",
|
||||
Kernel::ErrorType::BadInput);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug <<
|
||||
"Input got two dimensions, the value use for the vote will be the difference between the two values\n";
|
||||
input.twoValueInput = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input.decoder->isBufferReceived())
|
||||
{
|
||||
if (m_matrixBased)
|
||||
{
|
||||
double value;
|
||||
if (input.twoValueInput) { value = input.op_matrix->getBuffer()[1] - input.op_matrix->getBuffer()[0]; }
|
||||
else { value = input.op_matrix->getBuffer()[0]; }
|
||||
input.scores.push_back(std::pair<double, uint64_t>(-value, boxContext.getInputChunkEndTime(i, j)));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < input.op_stimSet->getStimulationCount(); ++k)
|
||||
{
|
||||
const uint64_t id = input.op_stimSet->getStimulationIdentifier(k);
|
||||
if (id == m_targetClassLabel || id == m_nonTargetClassLabel || id == m_rejectClassLabel)
|
||||
{
|
||||
input.scores.push_back(std::pair<double, uint64_t>(id == m_targetClassLabel ? 1 : 0, input.op_stimSet->getStimulationDate(k)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (input.decoder->isEndReceived())
|
||||
{
|
||||
m_classificationChoiceEncoder.encodeEnd();
|
||||
boxContext.markOutputAsReadyToSend(0, m_lastTime, this->getPlayerContext().getCurrentTime());
|
||||
}
|
||||
}
|
||||
|
||||
if (input.scores.size() < m_nRepetitions) { canChoose = false; }
|
||||
}
|
||||
|
||||
if (canChoose)
|
||||
{
|
||||
double score = -1E100;
|
||||
uint64_t classLabel = m_rejectClassLabel;
|
||||
uint64_t time = 0;
|
||||
|
||||
std::map<uint32_t, double> scores;
|
||||
for (size_t i = 0; i < nInput; ++i)
|
||||
{
|
||||
input_t& input = m_results[i];
|
||||
scores[i] = 0;
|
||||
for (size_t j = 0; j < m_nRepetitions; ++j) { scores[i] += input.scores[j].first; }
|
||||
|
||||
if (scores[i] > score)
|
||||
{
|
||||
score = scores[i];
|
||||
classLabel = m_resultClassLabelBase + i;
|
||||
time = input.scores[size_t(m_nRepetitions - 1)].second;
|
||||
}
|
||||
else if (scores[i] == score)
|
||||
{
|
||||
if (!m_chooseOneIfExAequo)
|
||||
{
|
||||
score = scores[i];
|
||||
classLabel = m_rejectClassLabel;
|
||||
time = input.scores[size_t(m_nRepetitions - 1)].second;
|
||||
}
|
||||
}
|
||||
|
||||
input.scores.erase(input.scores.begin(), input.scores.begin() + int(m_nRepetitions));
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Input " << i << " got score " << scores[i] << "\n";
|
||||
}
|
||||
|
||||
if (classLabel != m_rejectClassLabel)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Chosen " << this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, classLabel)
|
||||
<< " with score " << score << "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Chosen rejection "
|
||||
<< this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, classLabel) << "\n";
|
||||
}
|
||||
m_classificationChoiceEncoder.getInputStimulationSet()->clear();
|
||||
m_classificationChoiceEncoder.getInputStimulationSet()->appendStimulation(classLabel, time, 0);
|
||||
|
||||
m_classificationChoiceEncoder.encodeBuffer();
|
||||
boxContext.markOutputAsReadyToSend(0, m_lastTime, time);
|
||||
m_lastTime = time;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
#pragma once
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
class CBoxAlgorithmVotingClassifier final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
bool processInput(const size_t index) override;
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_VotingClassifier)
|
||||
|
||||
protected:
|
||||
|
||||
size_t m_nRepetitions = 0;
|
||||
size_t m_targetClassLabel = 0;
|
||||
size_t m_nonTargetClassLabel = 0;
|
||||
size_t m_rejectClassLabel = 0;
|
||||
size_t m_resultClassLabelBase = 0;
|
||||
bool m_chooseOneIfExAequo = false;
|
||||
|
||||
private:
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Toolkit::TDecoder<CBoxAlgorithmVotingClassifier>* decoder = nullptr;
|
||||
Kernel::TParameterHandler<IStimulationSet*> op_stimSet;
|
||||
Kernel::TParameterHandler<CMatrix*> op_matrix;
|
||||
bool twoValueInput;
|
||||
std::vector<std::pair<double, uint64_t>> scores;
|
||||
} input_t;
|
||||
|
||||
std::map<uint32_t, input_t> m_results;
|
||||
|
||||
Toolkit::TStimulationEncoder<CBoxAlgorithmVotingClassifier> m_classificationChoiceEncoder;
|
||||
Kernel::TParameterHandler<const IStimulationSet*> ip_classificationChoiceStimSet;
|
||||
|
||||
uint64_t m_lastTime = 0;
|
||||
bool m_matrixBased = false;
|
||||
};
|
||||
|
||||
class CBoxAlgorithmVotingClassifierListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
|
||||
CBoxAlgorithmVotingClassifierListener() : m_inputTypeID(OV_TypeId_Stimulations) { }
|
||||
|
||||
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
CIdentifier id = CIdentifier::undefined();
|
||||
box.getInputType(index, id);
|
||||
if (id == OV_TypeId_Stimulations || id == OV_TypeId_StreamedMatrix)
|
||||
{
|
||||
m_inputTypeID = id;
|
||||
for (size_t i = 0; i < box.getInputCount(); ++i) { box.setInputType(i, m_inputTypeID); }
|
||||
}
|
||||
else { box.setInputType(index, m_inputTypeID); }
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onInputAdded(Kernel::IBox& box, const size_t /*index*/) override
|
||||
{
|
||||
for (size_t i = 0; i < box.getInputCount(); ++i)
|
||||
{
|
||||
box.setInputType(i, m_inputTypeID);
|
||||
box.setInputName(i, ("Classification result " + std::to_string(i)).c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
|
||||
protected:
|
||||
|
||||
CIdentifier m_inputTypeID = CIdentifier::undefined();
|
||||
};
|
||||
|
||||
class CBoxAlgorithmVotingClassifierDesc final : public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
void release() override { }
|
||||
CString getName() const override { return CString("Voting Classifier"); }
|
||||
CString getAuthorName() const override { return CString("Yann Renard"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA"); }
|
||||
CString getShortDescription() const override { return CString("Majority voting classifier. Returns the chosen class."); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString(
|
||||
"Each classifier used as input is assumed to have its own two-class output stream. Mainly designed for P300 scenario use.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString("Classification"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getSoftwareComponent() const override { return CString("openvibe-sdk"); }
|
||||
CString getAddedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CString getUpdatedSoftwareVersion() const override { return CString("0.0.0"); }
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_VotingClassifier; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmVotingClassifier; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Classification result 1", OV_TypeId_Stimulations);
|
||||
prototype.addInput("Classification result 2", OV_TypeId_Stimulations);
|
||||
prototype.addOutput("Classification choice", OV_TypeId_Stimulations);
|
||||
prototype.addSetting("Number of repetitions", OV_TypeId_Integer, "12");
|
||||
prototype.addSetting("Target class label", OV_TypeId_Stimulation, "OVTK_StimulationId_Target");
|
||||
prototype.addSetting("Non target class label", OV_TypeId_Stimulation, "OVTK_StimulationId_NonTarget");
|
||||
prototype.addSetting("Reject class label", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_00");
|
||||
prototype.addSetting("Result class label base", OV_TypeId_Stimulation, "OVTK_StimulationId_Label_01");
|
||||
prototype.addSetting("Choose one if ex-aequo", OV_TypeId_Boolean, "false");
|
||||
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
|
||||
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
|
||||
return true;
|
||||
}
|
||||
|
||||
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmVotingClassifierListener; }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_VotingClassifierDesc)
|
||||
};
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#define OVP_Classification_BoxTrainerFormatVersion 4
|
||||
#define OVP_Classification_BoxTrainerFormatVersionRequired 4
|
||||
|
||||
// Global defines
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
#include "ovp_global_defines.h"
|
||||
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
|
||||
#define OVP_TypeId_ClassificationPairwiseStrategy OpenViBE::CIdentifier(0x0DD51C74, 0x3C4E74C9)
|
||||
#define OVP_TypeId_OneVsOne_DecisionAlgorithms OpenViBE::CIdentifier(0xDEC1510, 0xDEC1510)
|
||||
|
||||
#define OVP_ClassId_Algorithm_ClassifierLDA OpenViBE::CIdentifier(0x2BA17A3C, 0x1BD46D84)
|
||||
#define OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable OpenViBE::CIdentifier(0x79146976, 0xD7F01A25)
|
||||
#define OVP_ClassId_Algorithm_ClassifierLDADesc OpenViBE::CIdentifier(0x78FE2929, 0x644945B4)
|
||||
#define OVP_ClassId_Algorithm_ClassifierNULL OpenViBE::CIdentifier(0x043D09AB, 0xCB5E4859)
|
||||
#define OVP_ClassId_Algorithm_ClassifierNULLDesc OpenViBE::CIdentifier(0x3B365233, 0x812C47DD)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsOne OpenViBE::CIdentifier(0x638C2F90, 0xEAE10226)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsOneDesc OpenViBE::CIdentifier(0xE78E7CDB, 0x369AA9EF)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsAll OpenViBE::CIdentifier(0xD7183FC6, 0xBD74F297)
|
||||
#define OVP_ClassId_Algorithm_ClassifierOneVsAllDesc OpenViBE::CIdentifier(0xD42D5449, 0x7A28DDB0)
|
||||
#define OVP_ClassId_Algorithm_ConditionedCovariance OpenViBE::CIdentifier(0x0F3B77A6, 0x0301518A)
|
||||
#define OVP_ClassId_Algorithm_ConditionedCovarianceDesc OpenViBE::CIdentifier(0x18D15C41, 0x70545A66)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision OpenViBE::CIdentifier(0x26EF6DDA, 0xF137053C)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecisionDesc OpenViBE::CIdentifier(0x191EB02A, 0x6866214A)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_HT OpenViBE::CIdentifier(0xD24F7F19, 0xA744FAD2)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_HTDesc OpenViBE::CIdentifier(0xE837F5C0, 0xF65C1341)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_Voting OpenViBE::CIdentifier(0xA111B830, 0x4679BAFD)
|
||||
#define OVP_ClassId_Algorithm_PairwiseDecision_VotingDesc OpenViBE::CIdentifier(0xAC5A39E8, 0x3A57822A)
|
||||
#define OVP_ClassId_Algorithm_PairwiseStrategy_PKPD OpenViBE::CIdentifier(0x26EF6DDA, 0xF137053C)
|
||||
#define OVP_ClassId_Algorithm_PairwiseStrategy_PKPDDesc OpenViBE::CIdentifier(0x191EB02A, 0x6866214A)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierProcessor OpenViBE::CIdentifier(0x5FE23D17, 0x95B0452C)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierProcessorDesc OpenViBE::CIdentifier(0x29B66B00, 0xB4683D49)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierTrainer OpenViBE::CIdentifier(0xF3DAE8A8, 0x3B444154)
|
||||
#define OVP_ClassId_BoxAlgorithm_ClassifierTrainerDesc OpenViBE::CIdentifier(0xFE277C91, 0x1593B824)
|
||||
#define OVP_ClassId_BoxAlgorithm_VotingClassifier OpenViBE::CIdentifier(0xFAF62C2B, 0x0B75D1B3)
|
||||
#define OVP_ClassId_BoxAlgorithm_VotingClassifierDesc OpenViBE::CIdentifier(0x97E3CCC5, 0xAC353ED2)
|
||||
|
||||
#define OVP_Algorithm_ClassifierLDA_InputParameterId_UseShrinkage OpenViBE::CIdentifier(0x01357534, 0x028312A0)
|
||||
#define OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage OpenViBE::CIdentifier(0x01357534, 0x028312A1)
|
||||
#define OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov OpenViBE::CIdentifier(0x067E45C5, 0x15285CC7)
|
||||
#define OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter1 OpenViBE::CIdentifier(0x6DA99952, 0x7E72C143)
|
||||
#define OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter2 OpenViBE::CIdentifier(0xEAC5694A, 0x56CFEF02)
|
||||
#define OVP_Algorithm_ClassifierNULL_InputParameterId_Parameter3 OpenViBE::CIdentifier(0x72F6222D, 0x375BAE2C)
|
||||
#define OVP_Algorithm_OneVsOneStrategy_InputParameterId_DecisionType OpenViBE::CIdentifier(0x0C347BBA, 0x180577F9)
|
||||
#define OVP_Algorithm_ConditionedCovariance_InputParameterId_Shrinkage OpenViBE::CIdentifier(0x54B90EA7, 0x600A4ACC)
|
||||
#define OVP_Algorithm_ConditionedCovariance_InputParameterId_FeatureVectorSet OpenViBE::CIdentifier(0x2CF30E42, 0x051F3996)
|
||||
#define OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean OpenViBE::CIdentifier(0x0C671FB7, 0x550B01B3)
|
||||
#define OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix OpenViBE::CIdentifier(0x19F07FB4, 0x084E273B)
|
||||
#define OVP_Algorithm_Classifier_InputParameter_ProbabilityMatrix OpenViBE::CIdentifier(0xF48D35AD, 0xB8EFF834)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameterId_Config OpenViBE::CIdentifier(0x10EBAC09, 0x80926A63)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameterId_AlgorithmIdentifier OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E017)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameterId_SetRepartition OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E018)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassificationOutputs OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E019)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputParameter_ClassCount OpenViBE::CIdentifier(0xBE71BE18, 0x82A0E01A)
|
||||
#define OVP_Algorithm_Classifier_OutputParameter_ProbabilityVector OpenViBE::CIdentifier(0x883599FE, 0x2FDB32FF)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_OutputParameterId_Config OpenViBE::CIdentifier(0x69F05A61, 0x25C94515)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Train OpenViBE::CIdentifier(0x32219D21, 0xD3BE6105)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Parameterize OpenViBE::CIdentifier(0x32219D21, 0xD3BE6106)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_Compute OpenViBE::CIdentifier(0x3637344B, 0x05D03D7E)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_SaveConfig OpenViBE::CIdentifier(0xF19574AD, 0x024045A7)
|
||||
#define OVP_Algorithm_Classifier_Pairwise_InputTriggerId_LoadConfig OpenViBE::CIdentifier(0x97AF6C6C, 0x670A12E6)
|
||||
|
||||
extern const char* const FORMAT_VERSION_ATTRIBUTE_NAME;
|
||||
extern const char* const IDENTIFIER_ATTRIBUTE_NAME;
|
||||
|
||||
extern const char* const STRATEGY_NODE_NAME;
|
||||
extern const char* const ALGORITHM_NODE_NAME;
|
||||
extern const char* const STIMULATIONS_NODE_NAME;
|
||||
extern const char* const REJECTED_CLASS_NODE_NAME;
|
||||
extern const char* const CLASS_STIMULATION_NODE_NAME;
|
||||
|
||||
extern const char* const CLASSIFICATION_BOX_ROOT;
|
||||
extern const char* const CLASSIFIER_ROOT;
|
||||
|
||||
extern const char* const PAIRWISE_STRATEGY_ENUMERATION_NAME;
|
||||
|
||||
extern const char* const MLP_EVALUATION_FUNCTION_NAME;
|
||||
extern const char* const MLP_TRANSFERT_FUNCTION_NAME;
|
||||
|
||||
bool OVFloatEqual(double first, double second);
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#include <vector>
|
||||
|
||||
#include "ovp_defines.h"
|
||||
#include "toolkit/algorithms/classification/ovtkCAlgorithmPairingStrategy.h" //For comparision mecanism
|
||||
|
||||
#include "algorithms/ovpCAlgorithmClassifierNULL.h"
|
||||
#include "algorithms/ovpCAlgorithmClassifierOneVsAll.h"
|
||||
#include "algorithms/ovpCAlgorithmClassifierOneVsOne.h"
|
||||
|
||||
#include "algorithms/ovpCAlgorithmPairwiseDecision.h"
|
||||
#include "algorithms/ovpCAlgorithmPairwiseStrategyPKPD.h"
|
||||
#include "algorithms/ovpCAlgorithmPairwiseDecisionVoting.h"
|
||||
#include "algorithms/ovpCAlgorithmPairwiseDecisionHT.h"
|
||||
|
||||
#include "box-algorithms/ovpCBoxAlgorithmVotingClassifier.h"
|
||||
#include "box-algorithms/ovpCBoxAlgorithmClassifierTrainer.h"
|
||||
#include "box-algorithms/ovpCBoxAlgorithmClassifierProcessor.h"
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
#include "algorithms/ovpCAlgorithmConditionedCovariance.h"
|
||||
#include "algorithms/ovpCAlgorithmClassifierLDA.h"
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
#include<cmath>
|
||||
|
||||
const char* const PAIRWISE_STRATEGY_ENUMERATION_NAME = "Pairwise Decision Strategy";
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace Classification {
|
||||
|
||||
|
||||
OVP_Declare_Begin()
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationStrategy, "Native", CIdentifier::undefined().id());
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationStrategy, "OneVsAll", OVP_ClassId_Algorithm_ClassifierOneVsAll.id());
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationStrategy, "OneVsOne", OVP_ClassId_Algorithm_ClassifierOneVsOne.id());
|
||||
|
||||
// context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationAlgorithm, "NULL Classifier (does nothing)",OVP_ClassId_Algorithm_ClassifierNULL.id());
|
||||
// OVP_Declare_New(CAlgorithmClassifierNULLDesc);
|
||||
|
||||
|
||||
OVP_Declare_New(CBoxAlgorithmVotingClassifierDesc);
|
||||
OVP_Declare_New(CBoxAlgorithmClassifierTrainerDesc);
|
||||
OVP_Declare_New(CBoxAlgorithmClassifierProcessorDesc);
|
||||
|
||||
OVP_Declare_New(CAlgorithmClassifierOneVsAllDesc);
|
||||
OVP_Declare_New(CAlgorithmClassifierOneVsOneDesc);
|
||||
|
||||
// Functions related to deciding winner in OneVsOne multiclass decision strategy
|
||||
context.getTypeManager().registerEnumerationType(OVP_TypeId_ClassificationPairwiseStrategy, PAIRWISE_STRATEGY_ENUMERATION_NAME);
|
||||
|
||||
OVP_Declare_New(CAlgorithmPairwiseStrategyPKPDDesc);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ClassificationPairwiseStrategy, "PKPD", OVP_ClassId_Algorithm_PairwiseStrategy_PKPD.id());
|
||||
OVP_Declare_New(CAlgorithmPairwiseDecisionVotingDesc);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ClassificationPairwiseStrategy, "Voting", OVP_ClassId_Algorithm_PairwiseDecision_Voting.id());
|
||||
OVP_Declare_New(CAlgorithmPairwiseDecisionHTDesc);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_ClassificationPairwiseStrategy, "HT", OVP_ClassId_Algorithm_PairwiseDecision_HT.id());
|
||||
|
||||
#if defined TARGET_HAS_ThirdPartyEIGEN
|
||||
OVP_Declare_New(CAlgorithmConditionedCovarianceDesc);
|
||||
|
||||
context.getTypeManager().registerEnumerationEntry(OVTK_TypeId_ClassificationAlgorithm, "Linear Discrimimant Analysis (LDA)",
|
||||
OVP_ClassId_Algorithm_ClassifierLDA.id());
|
||||
Toolkit::registerClassificationComparisonFunction(OVP_ClassId_Algorithm_ClassifierLDA, LDAClassificationCompare);
|
||||
OVP_Declare_New(CAlgorithmClassifierLDADesc);
|
||||
context.getTypeManager().registerEnumerationType(OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, PAIRWISE_STRATEGY_ENUMERATION_NAME);
|
||||
context.getTypeManager().registerEnumerationEntry(
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, "PKPD", OVP_ClassId_Algorithm_PairwiseStrategy_PKPD.id());
|
||||
context.getTypeManager().registerEnumerationEntry(
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, "Voting", OVP_ClassId_Algorithm_PairwiseDecision_Voting.id());
|
||||
context.getTypeManager().registerEnumerationEntry(
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable, "HT", OVP_ClassId_Algorithm_PairwiseDecision_HT.id());
|
||||
|
||||
context.getTypeManager().registerEnumerationType(OVP_TypeId_OneVsOne_DecisionAlgorithms, "One vs One Decision Algorithms");
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeId_OneVsOne_DecisionAlgorithms, "Linear Discrimimant Analysis (LDA)",
|
||||
OVP_ClassId_Algorithm_ClassifierLDA_DecisionAvailable.id());
|
||||
|
||||
#endif // TARGET_HAS_ThirdPartyEIGEN
|
||||
|
||||
OVP_Declare_End()
|
||||
|
||||
} // namespace Classification
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
bool OVFloatEqual(const double first, const double second)
|
||||
{
|
||||
const double epsilon = 0.000001;
|
||||
return epsilon > fabs(first - second);
|
||||
}
|
||||
Reference in New Issue
Block a user