This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
@@ -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);
}