Commit 0ab20de9 authored by Jussi Lindgren's avatar Jussi Lindgren
Browse files

Algorithms: Modifications to the LDA classifier

- Uses the class count from the outside
- Input classes should now be 0,...,k-1
- Allows some classes to have no training examples
parent c7487433
......@@ -137,7 +137,8 @@ boolean CAlgorithmClassifierLDA::train(const IFeatureVectorSet& rFeatureVectorSe
l_pDiagonalCov = this->getBooleanParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_DiagonalCov);
}
else{
else
{
//If we don't use shrinkage we need to set lambda to 0.
TParameterHandler< float64 > ip_f64Shrinkage(this->getInputParameter(OVP_Algorithm_ClassifierLDA_InputParameterId_Shrinkage));
ip_f64Shrinkage = 0.0;
......@@ -148,8 +149,6 @@ boolean CAlgorithmClassifierLDA::train(const IFeatureVectorSet& rFeatureVectorSe
}
this->uninitializeExtraParameterMechanism();
// IO to the covariance alg
TParameterHandler < OpenViBE::IMatrix* > op_pMean(m_pCovarianceAlgorithm->getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_Mean));
TParameterHandler < OpenViBE::IMatrix* > op_pCovarianceMatrix(m_pCovarianceAlgorithm->getOutputParameter(OVP_Algorithm_ConditionedCovariance_OutputParameterId_CovarianceMatrix));
......@@ -165,51 +164,64 @@ boolean CAlgorithmClassifierLDA::train(const IFeatureVectorSet& rFeatureVectorSe
return false;
}
// Count the classes
std::map < float64, uint32 > l_vClassCounts;
// The max amount of classes to be expected
TParameterHandler < uint64 > ip_pNumberOfClasses(this->getInputParameter(OVTK_Algorithm_Classifier_InputParameterId_NumberOfClasses));
m_ui32NumClasses = static_cast<uint32>(ip_pNumberOfClasses);
// Count the classes actually present
std::vector< uint32 > l_vClassCounts;
l_vClassCounts.resize(m_ui32NumClasses);
for(uint32 i=0; i<rFeatureVectorSet.getFeatureVectorCount(); i++)
{
l_vClassCounts[rFeatureVectorSet[i].getLabel()]++;
uint32 classIdx = static_cast<uint32>(rFeatureVectorSet[i].getLabel());
l_vClassCounts[classIdx]++;
}
const uint32 l_ui32nClasses = l_vClassCounts.size();
// Get class labels
for(std::map < float64, uint32 >::iterator iter = l_vClassCounts.begin() ; iter != l_vClassCounts.end() ; ++iter)
for(uint32 i=0;i<m_ui32NumClasses;i++)
{
m_vLabelList.push_back(iter->first);
m_vLabelList.push_back(i);
m_vDiscriminantFunctions.push_back(CAlgorithmLDADiscriminantFunction());
}
// Per-class means and a global covariance are used to form the LDA model
MatrixXd* l_oPerClassMeans = new MatrixXd[l_ui32nClasses];
MatrixXd* l_oPerClassMeans = new MatrixXd[m_ui32NumClasses];
MatrixXd l_oGlobalCov = MatrixXd::Zero(l_ui32nCols,l_ui32nCols);
// We need the means per class
for(uint32 l_ui32classIdx=0;l_ui32classIdx<l_ui32nClasses;l_ui32classIdx++)
for(uint32 l_ui32classIdx=0;l_ui32classIdx<m_ui32NumClasses;l_ui32classIdx++)
{
const float64 l_f64Label = m_vLabelList[l_ui32classIdx];
const uint32 l_ui32nExamplesInClass = l_vClassCounts[l_f64Label];
// Copy all the data of the class to a matrix
CMatrix l_oClassData;
l_oClassData.setDimensionCount(2);
l_oClassData.setDimensionSize(0, l_ui32nExamplesInClass);
l_oClassData.setDimensionSize(1, l_ui32nCols);
float64 *l_pBuffer = l_oClassData.getBuffer();
for(uint32 i=0;i<l_ui32nRows;i++)
if(l_vClassCounts[l_ui32classIdx]>0)
{
if(rFeatureVectorSet[i].getLabel() == l_f64Label)
const float64 l_f64Label = m_vLabelList[l_ui32classIdx];
const uint32 l_ui32nExamplesInClass = l_vClassCounts[l_ui32classIdx];
// Copy all the data of the class to a matrix
CMatrix l_oClassData;
l_oClassData.setDimensionCount(2);
l_oClassData.setDimensionSize(0, l_ui32nExamplesInClass);
l_oClassData.setDimensionSize(1, l_ui32nCols);
float64 *l_pBuffer = l_oClassData.getBuffer();
for(uint32 i=0;i<l_ui32nRows;i++)
{
System::Memory::copy(l_pBuffer, rFeatureVectorSet[i].getBuffer(), l_ui32nCols*sizeof(float64));
l_pBuffer += l_ui32nCols;
if(rFeatureVectorSet[i].getLabel() == l_ui32classIdx)
{
System::Memory::copy(l_pBuffer, rFeatureVectorSet[i].getBuffer(), l_ui32nCols*sizeof(float64));
l_pBuffer += l_ui32nCols;
}
}
// Get the mean out of it
Map<MatrixXdRowMajor> l_oDataMapper(l_oClassData.getBuffer(), l_ui32nExamplesInClass, l_ui32nCols);
const MatrixXd l_oClassMean = l_oDataMapper.colwise().mean().transpose();
l_oPerClassMeans[l_ui32classIdx] = l_oClassMean;
}
else
{
MatrixXd l_oTmp; l_oTmp.resize(l_ui32nCols, 1); l_oTmp.setZero();
l_oPerClassMeans[l_ui32classIdx] = l_oTmp;
}
// Get the mean out of it
Map<MatrixXdRowMajor> l_oDataMapper(l_oClassData.getBuffer(), l_ui32nExamplesInClass, l_ui32nCols);
const MatrixXd l_oClassMean = l_oDataMapper.colwise().mean().transpose();
l_oPerClassMeans[l_ui32classIdx] = l_oClassMean;
}
// We need a global covariance, use the regularized cov algorithm
......@@ -265,27 +277,53 @@ boolean CAlgorithmClassifierLDA::train(const IFeatureVectorSet& rFeatureVectorSe
l_oEigenValues(i) = 1.0/l_oEigenValues(i);
}
}
const MatrixXd l_oGlobalCovInv = l_oEigenSolver.eigenvectors() * l_oEigenValues.asDiagonal() * l_oEigenSolver.eigenvectors().inverse();
// const MatrixXd l_oGlobalCovInv = l_oGlobalCov.inverse();
//We send the bias and the weight of each class to ComputationHelper
for(size_t i = 0 ; i < getClassCount() ; ++i)
{
const float64 l_f64ExamplesInClass = l_vClassCounts[m_vLabelList[i]];
const uint32 l_ui32TotalExamples = rFeatureVectorSet.getFeatureVectorCount();
const float64 l_f64ExamplesInClass = l_vClassCounts[i];
if(l_f64ExamplesInClass > 0)
{
const uint32 l_ui32TotalExamples = rFeatureVectorSet.getFeatureVectorCount();
// This formula e.g. in Hastie, Tibshirani & Friedman: "Elements...", 2nd ed., p. 109
const VectorXd l_oWeight = (l_oGlobalCovInv * l_oPerClassMeans[i]);
const MatrixXd l_oInter = -0.5 * l_oPerClassMeans[i].transpose() * l_oGlobalCovInv * l_oPerClassMeans[i];
const float64 l_f64Bias = l_oInter(0,0) + std::log(l_f64ExamplesInClass/l_ui32TotalExamples);
// This formula e.g. in Hastie, Tibshirani & Friedman: "Elements...", 2nd ed., p. 109
const VectorXd l_oWeight = (l_oGlobalCovInv * l_oPerClassMeans[i]);
const MatrixXd l_oInter = -0.5 * l_oPerClassMeans[i].transpose() * l_oGlobalCovInv * l_oPerClassMeans[i];
const float64 l_f64Bias = l_oInter(0,0) + std::log(l_f64ExamplesInClass/l_ui32TotalExamples);
// this->getLogManager() << LogLevel_Info << "Bias for " << i << " is " << l_f64Bias << ", from " << l_f64ExamplesInClass / l_ui32TotalExamples
// << ", " << stuffInClass << "/" << featVectors
// << "\n";
// dumpMatrix(this->getLogManager(), l_oPerClassMeans[i], "Means");
this->getLogManager() << LogLevel_Debug << "Bias for " << i << " is " << l_f64Bias << ", from " << l_f64ExamplesInClass / l_ui32TotalExamples
<< ", " << l_f64ExamplesInClass << "/" << l_ui32TotalExamples << ", int=" << l_oInter(0,0)
<< "\n";
// dumpMatrix(this->getLogManager(), l_oPerClassMeans[i], "Means");
m_vDiscriminantFunctions[i].setWeight(l_oWeight);
m_vDiscriminantFunctions[i].setBias(l_f64Bias);
m_vDiscriminantFunctions[i].setWeight(l_oWeight);
m_vDiscriminantFunctions[i].setBias(l_f64Bias);
}
else
{
this->getLogManager() << LogLevel_Debug << "Class " << i << " has no examples\n";
}
}
// Hack for classes with zero examples, give them valid models but such that will always lose
uint32 l_ui32NonZeroClassIdx=0;
for(size_t i = 0; i < getClassCount() ; i++)
{
if(l_vClassCounts[i]>0)
{
l_ui32NonZeroClassIdx = i;
break;
}
}
for(size_t i = 0; i < getClassCount() ; i++)
{
if(l_vClassCounts[i]==0)
{
m_vDiscriminantFunctions[i].setWeight(m_vDiscriminantFunctions[l_ui32NonZeroClassIdx].getWeight());
m_vDiscriminantFunctions[i].setBias(m_vDiscriminantFunctions[l_ui32NonZeroClassIdx].getBias() - 1.0); // Will always lose to the orig
}
}
m_ui32NumCols = l_ui32nCols;
......@@ -373,6 +411,7 @@ boolean CAlgorithmClassifierLDA::classify(const IFeatureVector& rFeatureVector,
// p(Ck | x) = 1 / sum[j](exp(aj - ak))
//All ak are given by computation helper
errno = 0;
for(size_t i = 0 ; i < l_ui32ClassCount ; ++i)
{
float64 l_f64ExpSum = 0.;
......@@ -381,9 +420,10 @@ boolean CAlgorithmClassifierLDA::classify(const IFeatureVector& rFeatureVector,
l_f64ExpSum += exp(l_pValueArray[j] - l_pValueArray[i]);
}
l_pProbabilityValue[i] = 1/l_f64ExpSum;
// std::cout << "p " << i << " = " << l_pProbabilityValue[i] << ", v=" << l_pValueArray[i] << ", " << errno << "\n";
}
//Then we just found the highest score and took it as results
//Then we just find the highest probability and take it as a result
uint32 l_ui32ClassIndex = std::distance(l_pValueArray, std::max_element(l_pValueArray, l_pValueArray+l_ui32ClassCount));
rClassificationValues.setSize(l_ui32ClassCount);
......@@ -394,17 +434,19 @@ boolean CAlgorithmClassifierLDA::classify(const IFeatureVector& rFeatureVector,
rClassificationValues[i] = l_pValueArray[i];
rProbabilityValue[i] = l_pProbabilityValue[i];
}
rf64Class = m_vLabelList[l_ui32ClassIndex];
delete[] l_pValueArray;
delete[] l_pProbabilityValue;
}
return true;
}
uint32 CAlgorithmClassifierLDA::getClassCount()
{
return m_vLabelList.size();
return m_ui32NumClasses;
}
XML::IXMLNode* CAlgorithmClassifierLDA::saveConfiguration(void)
......@@ -435,6 +477,7 @@ XML::IXMLNode* CAlgorithmClassifierLDA::saveConfiguration(void)
return l_pAlgorithmNode;
}
//Extract a float64 from the PCDATA of a node
float64 getFloatFromNode(XML::IXMLNode *pNode)
{
......@@ -517,6 +560,7 @@ void CAlgorithmClassifierLDA::loadClassesFromNode(XML::IXMLNode *pNode)
{
m_vLabelList.push_back(l_f64Temp);
}
m_ui32NumClasses = m_vLabelList.size();
}
//Load the weight vector
......
......@@ -67,6 +67,7 @@ namespace OpenViBEPlugins
OpenViBE::float64 m_f64w0;
OpenViBE::uint32 m_ui32NumCols;
OpenViBE::uint32 m_ui32NumClasses;
OpenViBE::boolean m_bv1Classification;
......
......@@ -31,6 +31,9 @@ namespace OpenViBEPlugins
OpenViBE::boolean loadConfiguration(const XML::IXMLNode* pConfiguration);
XML::IXMLNode* getConfiguration(void);
const Eigen::VectorXd& getWeight(void) const { return m_oWeight; };
OpenViBE::float64 getBias(void) const { return m_f64Bias; };
private:
OpenViBE::uint32 m_ui32NumCol;
OpenViBE::float64 m_f64Bias;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment