#include "Main.h"
const UINT pointCount = 400;
const double errorRate = 0.0;
const double trainingSetRatio = 0.75;
const UINT bmpSize = 256;
const String resultSuffix = "_outlier0";
struct CartesianPoint
{
__forceinline UINT Length() const
{
return 2;
}
__forceinline double operator[] (UINT k) const
{
return Pos[k];
}
__forceinline double& operator[] (UINT k)
{
return Pos[k];
}
double Pos[2];
};
struct KernelEntry
{
KernelEntry() {}
KernelEntry(SVMKernel<CartesianPoint> *_kernel, const String &_description)
{
kernel = _kernel;
description = _description;
}
SVMKernel<CartesianPoint> *kernel;
String description;
};
class App
{
public:
void Init();
void RunBinaryClassifiers();
void RunMulticlassClassifiers();
private:
void RotatePoint(double &x, double &y, double theta);
void MakeBinaryCartesianDataset(BinaryClassifier<CartesianPoint>::Dataset &dataset, UINT exampleCount, double errorRate);
void MakeMulticlassCartesianDataset(MulticlassClassifier<CartesianPoint>::Dataset &dataset, UINT exampleCount, double errorRate);
void TrainAndSaveBinaryClassifierResults(BinaryClassifier<CartesianPoint> &classifier, const BinaryClassifier<CartesianPoint>::Dataset &trainingSet, const BinaryClassifier<CartesianPoint>::Dataset &testSet, const String &classifierName);
void TrainAndSaveMulticlassClassifierResults(MulticlassClassifier<CartesianPoint> &classifier, const MulticlassClassifier<CartesianPoint>::Dataset &trainingSet, const MulticlassClassifier<CartesianPoint>::Dataset &testSet, const String &classifierName);
void SaveBinaryImage(const BinaryClassifier<CartesianPoint> &classifier, const BinaryClassifier<CartesianPoint>::Dataset &dataset, UINT class0Index, UINT class1Index, const String &filename);
void SaveMulticlassImage(const MulticlassClassifier<CartesianPoint> &classifier, const MulticlassClassifier<CartesianPoint>::Dataset &dataset, const String &filename);
Vector<KernelEntry> _SVMKernels;
struct
{
SVMKernelLinear<CartesianPoint> linear;
SVMKernelQuadratic<CartesianPoint> quadratic;
SVMKernelCubic<CartesianPoint> cubic;
SVMKernelQuartic<CartesianPoint> quartic;
SVMKernelQuintic<CartesianPoint> quintic;
SVMKernelGaussian<CartesianPoint> gaussian0;
SVMKernelGaussian<CartesianPoint> gaussian1;
SVMKernelGaussian<CartesianPoint> gaussian2;
SVMKernelGaussian<CartesianPoint> gaussian3;
SVMKernelGaussian<CartesianPoint> gaussian4;
} _kernels;
};
void App::Init()
{
_SVMKernels.PushEnd(KernelEntry(&_kernels.linear, "linear"));
_SVMKernels.PushEnd(KernelEntry(&_kernels.quadratic, "quadratic"));
_SVMKernels.PushEnd(KernelEntry(&_kernels.cubic, "cubic"));
_SVMKernels.PushEnd(KernelEntry(&_kernels.quartic, "quartic"));
_SVMKernels.PushEnd(KernelEntry(&_kernels.quintic, "quintic"));
_SVMKernels.PushEnd(KernelEntry(&_kernels.gaussian0, "gaussian0"));
_SVMKernels.PushEnd(KernelEntry(&_kernels.gaussian2, "gaussian1"));
_SVMKernels.PushEnd(KernelEntry(&_kernels.gaussian4, "gaussian2"));
_kernels.gaussian0.sigma = 0.1;
_kernels.gaussian1.sigma = 0.5;
_kernels.gaussian2.sigma = 1;
_kernels.gaussian3.sigma = 5;
_kernels.gaussian4.sigma = 10;
}
void App::TrainAndSaveBinaryClassifierResults(BinaryClassifier<CartesianPoint> &classifier, const BinaryClassifier<CartesianPoint>::Dataset &trainingSet, const BinaryClassifier<CartesianPoint>::Dataset &testSet, const String &classifierName)
{
classifier.Train(trainingSet, 0, 1);
SaveBinaryImage(classifier, testSet, 0, 1, String("BC0") + resultSuffix + String("\\") + classifierName + String(".png"));
double trainingError = classifier.DatasetClassificationError(trainingSet);
double testError = classifier.DatasetClassificationError(testSet);
Console::WriteLine(classifierName + " training error: " + String(trainingError));
Console::WriteLine(classifierName + " test error: " + String(testError));
}
void App::TrainAndSaveMulticlassClassifierResults(MulticlassClassifier<CartesianPoint> &classifier, const MulticlassClassifier<CartesianPoint>::Dataset &trainingSet, const MulticlassClassifier<CartesianPoint>::Dataset &testSet, const String &classifierName)
{
classifier.Train(trainingSet);
SaveMulticlassImage(classifier, testSet, String("MC0") + resultSuffix + String("\\") + classifierName + String(".png"));
double trainingError = classifier.DatasetClassificationError(trainingSet);
double testError = classifier.DatasetClassificationError(testSet);
Console::WriteLine(classifierName + " training error: " + String(trainingError));
Console::WriteLine(classifierName + " test error: " + String(testError));
}
void App::SaveBinaryImage(const BinaryClassifier<CartesianPoint> &classifier, const BinaryClassifier<CartesianPoint>::Dataset &dataset, UINT class0Index, UINT class1Index, const String &filename)
{
Rectangle2f bbox;
for(UINT exampleIndex = 0; exampleIndex < dataset.Entries().Length(); exampleIndex++)
{
const BinaryClassifier<CartesianPoint>::Example &curExample = dataset.Entries()[exampleIndex];
Vec2f coordinates(float(curExample.Input[0]), float(curExample.Input[1]));
if(exampleIndex == 0)
{
bbox.Min = coordinates;
bbox.Max = coordinates;
}
else
{
bbox.Min = Vec2f::Minimize(bbox.Min, coordinates);
bbox.Max = Vec2f::Maximize(bbox.Max, coordinates);
}
}
bbox = Rectangle2f::ConstructFromCenterVariance(bbox.Center(), bbox.Dimensions() * 0.6f);
Bitmap bmp;
float AspectRatio = 1.0f;
if(AspectRatio > 1.0f)
{
bmp.Allocate(UINT(bmpSize / AspectRatio), bmpSize);
}
else
{
bmp.Allocate(bmpSize, UINT(bmpSize * AspectRatio));
}
BinaryClassifier<CartesianPoint>::Example baseExample = dataset.Entries()[0];
for(UINT Y = 0; Y < bmp.Height(); Y++)
{
for(UINT X = 0; X < bmp.Width(); X++)
{
Vec2f CurFunctionPos(Math::LinearMap(0.0f, bmp.Width() - 1.0f, bbox.Min.x, bbox.Max.x, float(X)),
Math::LinearMap(0.0f, bmp.Height() - 1.0f, bbox.Min.y, bbox.Max.y, float(Y)));
baseExample.Input[0] = CurFunctionPos.x;
baseExample.Input[1] = CurFunctionPos.y;
UINT Result;
double ProbabilityFirstClass = 0.0;
classifier.Evaluate(baseExample.Input, Result, ProbabilityFirstClass);
RGBColor C;
if(Result == class0Index)
{
C = RGBColor::Interpolate(RGBColor::White, RGBColor::Blue, float(Utility::Bound(Math::LinearMap(0.5, 1.0, 0.0, 1.0, ProbabilityFirstClass), 0.0, 1.0)));
}
else
{
C = RGBColor::Interpolate(RGBColor::Red, RGBColor::Black, float(Utility::Bound(Math::LinearMap(0.0, 0.5, 0.0, 1.0, ProbabilityFirstClass), 0.0, 1.0)));
}
bmp[Y][X] = C;
}
}
AliasRender R;
for(UINT exampleIndex = 0; exampleIndex < dataset.Entries().Length(); exampleIndex++)
{
const BinaryClassifier<CartesianPoint>::Example &curExample = dataset.Entries()[exampleIndex];
Vec2i coordinates(Math::Round(Math::LinearMap(bbox.Min.x, bbox.Max.x, 0.0f, bmp.Width() - 1.0f, float(curExample.Input[0]))),
Math::Round(Math::LinearMap(bbox.Min.y, bbox.Max.y, 0.0f, bmp.Height() - 1.0f, float(curExample.Input[1]))));
if(curExample.Class == class0Index)
{
R.DrawSquare(bmp, coordinates, 2, RGBColor::Blue, RGBColor::Black);
}
else
{
R.DrawSquare(bmp, coordinates, 2, RGBColor::Red, RGBColor::Black);
}
}
bmp.SavePNG(filename);
}
void App::SaveMulticlassImage(const MulticlassClassifier<CartesianPoint> &classifier, const MulticlassClassifier<CartesianPoint>::Dataset &dataset, const String &filename)
{
srand(2);
KMeansClustering<Vec3f, Vec3fKMeansMetric> colorClusters;
Vector<Vec3f> randomColors(1000 * dataset.ClassCount());
for(UINT colorIndex = 0; colorIndex < randomColors.Length(); colorIndex++)
{
Vec3f &curColor = randomColors[colorIndex];
curColor = Vec3f(rnd(), rnd(), rnd());
while(curColor.x + curColor.y + curColor.z < 0.75f)
{
curColor = Vec3f(rnd(), rnd(), rnd());
}
}
colorClusters.Cluster(randomColors, dataset.ClassCount());
Rectangle2f bbox;
for(UINT exampleIndex = 0; exampleIndex < dataset.Entries().Length(); exampleIndex++)
{
const MulticlassClassifier<CartesianPoint>::Example &curExample = dataset.Entries()[exampleIndex];
Vec2f coordinates(float(curExample.Input[0]), float(curExample.Input[1]));
if(exampleIndex == 0)
{
bbox.Min = coordinates;
bbox.Max = coordinates;
}
else
{
bbox.Min = Vec2f::Minimize(bbox.Min, coordinates);
bbox.Max = Vec2f::Maximize(bbox.Max, coordinates);
}
}
bbox = Rectangle2f::ConstructFromCenterVariance(bbox.Center(), bbox.Dimensions() * 0.6f);
Bitmap bmp;
float AspectRatio = 1.0f;
if(AspectRatio > 1.0f)
{
bmp.Allocate(UINT(bmpSize / AspectRatio), bmpSize);
}
else
{
bmp.Allocate(bmpSize, UINT(bmpSize * AspectRatio));
}
BinaryClassifier<CartesianPoint>::Example baseExample = dataset.Entries()[0];
for(UINT Y = 0; Y < bmp.Height(); Y++)
{
for(UINT X = 0; X < bmp.Width(); X++)
{
Vec2f CurFunctionPos(Math::LinearMap(0.0f, bmp.Width() - 1.0f, bbox.Min.x, bbox.Max.x, float(X)),
Math::LinearMap(0.0f, bmp.Height() - 1.0f, bbox.Min.y, bbox.Max.y, float(Y)));
baseExample.Input[0] = CurFunctionPos.x;
baseExample.Input[1] = CurFunctionPos.y;
UINT Class;
Vector<double> ClassProbabilities;
classifier.Evaluate(baseExample.Input, Class, ClassProbabilities);
RGBColor ClusterColor = RGBColor(colorClusters.ClusterCenter(Class));
bmp[Y][X] = RGBColor::Interpolate(RGBColor::Black, ClusterColor, float(ClassProbabilities[Class]));
}
}
AliasRender R;
for(UINT exampleIndex = 0; exampleIndex < dataset.Entries().Length(); exampleIndex++)
{
const MulticlassClassifier<CartesianPoint>::Example &curExample = dataset.Entries()[exampleIndex];
Vec2i coordinates(Math::Round(Math::LinearMap(bbox.Min.x, bbox.Max.x, 0.0f, bmp.Width() - 1.0f, float(curExample.Input[0]))),
Math::Round(Math::LinearMap(bbox.Min.y, bbox.Max.y, 0.0f, bmp.Height() - 1.0f, float(curExample.Input[1]))));
R.DrawSquare(bmp, coordinates, 2, RGBColor(colorClusters.ClusterCenter(curExample.Class)), RGBColor::Black);
}
bmp.SavePNG(filename);
}
void App::RotatePoint(double &x, double &y, double theta)
{
Matrix4 rotation = Matrix4::RotationZ(float(theta));
Vec3f result = rotation.TransformPoint(Vec3f(float(x), float(y), 0.0f));
x = result.x;
y = result.y;
}
void App::MakeBinaryCartesianDataset(BinaryClassifier<CartesianPoint>::Dataset &dataset, UINT exampleCount, double errorRate)
{
dataset.ClassCount() = 2;
for(UINT exampleIndex = 0; exampleIndex < exampleCount; exampleIndex++)
{
ClassifierExample<CartesianPoint> curExample;
curExample.Input.Pos[0] = pmrnd();
curExample.Input.Pos[1] = pmrnd();
double x = curExample.Input.Pos[0] - 0.3;
double y = curExample.Input.Pos[1] - 0.2;
RotatePoint(x, y, Math::DegreesToRadians(27.0f));
if(y > 0.1 * x * x * x - 3.5 * x * x + 1.3 * x - 0.1 && 0.75 * x + y > -0.7)
{
curExample.Class = 1;
}
else
{
curExample.Class = 0;
}
if(rnd() < errorRate)
{
curExample.Class = 1 - curExample.Class;
}
dataset.AddEntry(curExample);
}
}
void App::MakeMulticlassCartesianDataset(MulticlassClassifier<CartesianPoint>::Dataset &dataset, UINT exampleCount, double errorRate)
{
dataset.ClassCount() = 4;
for(UINT exampleIndex = 0; exampleIndex < exampleCount; exampleIndex++)
{
ClassifierExample<CartesianPoint> curExample;
curExample.Input.Pos[0] = pmrnd();
curExample.Input.Pos[1] = pmrnd();
double x = curExample.Input.Pos[0] + 0.1f;
double y = curExample.Input.Pos[1] + 0.2f;
RotatePoint(x, y, Math::DegreesToRadians(20.0f));
bool test0 = x + y > 0.0;
bool test1 = y > x * x - 0.5;
curExample.Class = Utility::CastBoolToUINT(test0) * 2 + Utility::CastBoolToUINT(test1);
if(rnd() < errorRate)
{
curExample.Class = rand() % 4;
}
dataset.AddEntry(curExample);
}
}
void App::RunMulticlassClassifiers()
{
Console::WriteLine("Running multiclass classifiers");
MulticlassClassifier<CartesianPoint>::Dataset dataset, trainingSet, testSet;
MakeMulticlassCartesianDataset(dataset, pointCount, errorRate);
dataset.PartitionDataset(trainingSet, testSet, trainingSetRatio);
for(UINT k = 1; k <= 13; k += 2)
{
MulticlassClassifierNearestNeighborBruteForce<CartesianPoint> nearestNeighborClassifier;
nearestNeighborClassifier.Configure(NearestNeighborConfiguration(k));
TrainAndSaveMulticlassClassifierResults(nearestNeighborClassifier, trainingSet, testSet, "NearestNeighbor_k=" + String(k));
}
for(UINT treeDepth = 1; treeDepth <= 9; treeDepth++)
{
MulticlassClassifierDecisionTree<CartesianPoint> decisionTreeClassifier(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount));
TrainAndSaveMulticlassClassifierResults(decisionTreeClassifier, trainingSet, testSet, "DecisionTree_depth=" + String(treeDepth));
}
for(UINT treeDepth = 1; treeDepth <= 9; treeDepth++)
{
const UINT boostCount = 100;
MulticlassClassifierFactoryDecisionTree<CartesianPoint> decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount));
MulticlassClassifierAdaBoostM1<CartesianPoint> adaBoostClassifier(AdaBoostM1Configuration<CartesianPoint>(boostCount, pointCount, true, &decisionStumpFactory) );
TrainAndSaveMulticlassClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth));
}
const UINT boostCountSet[4] = {5, 10, 100, 1000};
for(UINT boostIndex = 0; boostIndex < 4; boostIndex++)
{
const UINT treeDepth = 3;
const UINT boostCount = boostCountSet[boostIndex];
MulticlassClassifierFactoryDecisionTree<CartesianPoint> decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount));
MulticlassClassifierAdaBoostM1<CartesianPoint> adaBoostClassifier(AdaBoostM1Configuration<CartesianPoint>(boostCount, pointCount, true, &decisionStumpFactory) );
TrainAndSaveMulticlassClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth));
}
for(UINT kernelIndex = 0; kernelIndex < _SVMKernels.Length(); kernelIndex++)
{
BinaryClassifierFactorySVM<CartesianPoint> SVMClassifierFactory(SVMConfiguration<CartesianPoint>(1.0, _SVMKernels[kernelIndex].kernel));
MulticlassClassifierPairwiseCoupling<CartesianPoint> pairwiseCoupling((PairwiseCouplingConfiguration<CartesianPoint>(&SVMClassifierFactory)));
TrainAndSaveMulticlassClassifierResults(pairwiseCoupling, trainingSet, testSet, "PairwiseCoupling(SVM)_C=1.0_kernel=" + String(_SVMKernels[kernelIndex].description));
}
for(UINT kernelIndex = 0; kernelIndex < _SVMKernels.Length(); kernelIndex++)
{
BinaryClassifierFactorySVM<CartesianPoint> SVMClassifierFactory(SVMConfiguration<CartesianPoint>(1.0, _SVMKernels[kernelIndex].kernel));
MulticlassClassifierOneVsAll<CartesianPoint> oneVsAll((OneVsAllConfiguration<CartesianPoint>(&SVMClassifierFactory)));
TrainAndSaveMulticlassClassifierResults(oneVsAll, trainingSet, testSet, "OneVsAll(SVM)_C=1.0_kernel=" + String(_SVMKernels[kernelIndex].description));
}
}
void App::RunBinaryClassifiers()
{
Console::WriteLine("Running binary classifiers");
BinaryClassifier<CartesianPoint>::Dataset dataset, trainingSet, testSet;
MakeBinaryCartesianDataset(dataset, pointCount, errorRate);
dataset.PartitionDataset(trainingSet, testSet, trainingSetRatio);
for(UINT k = 1; k <= 13; k += 2)
{
MulticlassClassifierNearestNeighborBruteForce<CartesianPoint> nearestNeighborClassifier;
nearestNeighborClassifier.Configure(NearestNeighborConfiguration(k));
BinaryClassifierMulticlass<CartesianPoint> multiClassWrapper(nearestNeighborClassifier);
TrainAndSaveBinaryClassifierResults(multiClassWrapper, trainingSet, testSet, "NearestNeighbor_k=" + String(k));
}
for(UINT treeDepth = 1; treeDepth <= 10; treeDepth++)
{
MulticlassClassifierDecisionTree<CartesianPoint> decisionTreeClassifier(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount));
BinaryClassifierMulticlass<CartesianPoint> multiClassWrapper(decisionTreeClassifier);
TrainAndSaveBinaryClassifierResults(multiClassWrapper, trainingSet, testSet, "DecisionTree_depth=" + String(treeDepth));
}
for(UINT treeDepth = 1; treeDepth <= 9; treeDepth++)
{
const UINT boostCount = 100;
MulticlassClassifierFactoryDecisionTree<CartesianPoint> decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount));
BinaryClassifierFactoryMulticlass<CartesianPoint> binaryFactory(&decisionStumpFactory);
BinaryClassifierAdaBoost<CartesianPoint> adaBoostClassifier(AdaBoostConfiguration<CartesianPoint>(boostCount, pointCount, true, &binaryFactory) );
TrainAndSaveBinaryClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth));
}
const UINT boostCountSet[4] = {5, 10, 100, 1000};
for(UINT boostIndex = 0; boostIndex < 4; boostIndex++)
{
const UINT treeDepth = 2;
const UINT boostCount = boostCountSet[boostIndex];
MulticlassClassifierFactoryDecisionTree<CartesianPoint> decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount));
BinaryClassifierFactoryMulticlass<CartesianPoint> binaryFactory(&decisionStumpFactory);
BinaryClassifierAdaBoost<CartesianPoint> adaBoostClassifier(AdaBoostConfiguration<CartesianPoint>(boostCount, pointCount, true, &binaryFactory) );
TrainAndSaveBinaryClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth));
}
for(UINT treeDepth = 1; treeDepth <= 10; treeDepth++)
{
MulticlassClassifierDecisionTree<CartesianPoint> decisionTreeClassifier(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount));
decisionTreeClassifier.Configure(DecisionTreeConfiguration(treeDepth));
BinaryClassifierMulticlass<CartesianPoint> multiClassWrapper(decisionTreeClassifier);
TrainAndSaveBinaryClassifierResults(multiClassWrapper, trainingSet, testSet, "DecisionTree_depth=" + String(treeDepth));
}
for(UINT kernelIndex = 0; kernelIndex < _SVMKernels.Length(); kernelIndex++)
{
BinaryClassifierSVM<CartesianPoint> SVMClassifier(SVMConfiguration<CartesianPoint>(1.0, _SVMKernels[kernelIndex].kernel));
TrainAndSaveBinaryClassifierResults(SVMClassifier, trainingSet, testSet, "SVM_C=1.0_kernel=" + _SVMKernels[kernelIndex].description);
}
const double CSet[9] = {0.001, 0.01, 0.1, 0.5, 1, 5, 10, 100, 1000};
for(UINT CIndex = 0; CIndex < 9; CIndex++)
{
double C = CSet[CIndex];
BinaryClassifierSVM<CartesianPoint> SVMClassifier(SVMConfiguration<CartesianPoint>(C, _SVMKernels[7].kernel));
TrainAndSaveBinaryClassifierResults(SVMClassifier, trainingSet, testSet, "SVM_C=" + String(C) + "_kernel=" + _SVMKernels[7].description);
}
}
void main()
{
App A;
A.Init();
A.RunBinaryClassifiers();
A.RunMulticlassClassifiers();
}