/* App.cpp Written by Matthew Fisher App.cpp contains main() and is the source of all application-specific code. */ #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 *_kernel, const String &_description) { kernel = _kernel; description = _description; } SVMKernel *kernel; String description; }; class App { public: void Init(); void RunBinaryClassifiers(); void RunMulticlassClassifiers(); private: void RotatePoint(double &x, double &y, double theta); void MakeBinaryCartesianDataset(BinaryClassifier::Dataset &dataset, UINT exampleCount, double errorRate); void MakeMulticlassCartesianDataset(MulticlassClassifier::Dataset &dataset, UINT exampleCount, double errorRate); void TrainAndSaveBinaryClassifierResults(BinaryClassifier &classifier, const BinaryClassifier::Dataset &trainingSet, const BinaryClassifier::Dataset &testSet, const String &classifierName); void TrainAndSaveMulticlassClassifierResults(MulticlassClassifier &classifier, const MulticlassClassifier::Dataset &trainingSet, const MulticlassClassifier::Dataset &testSet, const String &classifierName); void SaveBinaryImage(const BinaryClassifier &classifier, const BinaryClassifier::Dataset &dataset, UINT class0Index, UINT class1Index, const String &filename); void SaveMulticlassImage(const MulticlassClassifier &classifier, const MulticlassClassifier::Dataset &dataset, const String &filename); Vector _SVMKernels; struct { SVMKernelLinear linear; SVMKernelQuadratic quadratic; SVMKernelCubic cubic; SVMKernelQuartic quartic; SVMKernelQuintic quintic; SVMKernelGaussian gaussian0; SVMKernelGaussian gaussian1; SVMKernelGaussian gaussian2; SVMKernelGaussian gaussian3; SVMKernelGaussian 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.gaussian1, "gaussian0.5")); _SVMKernels.PushEnd(KernelEntry(&_kernels.gaussian2, "gaussian1")); //_SVMKernels.PushEnd(KernelEntry(&_kernels.gaussian3, "gaussian5")); _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 &classifier, const BinaryClassifier::Dataset &trainingSet, const BinaryClassifier::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 &classifier, const MulticlassClassifier::Dataset &trainingSet, const MulticlassClassifier::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 &classifier, const BinaryClassifier::Dataset &dataset, UINT class0Index, UINT class1Index, const String &filename) { Rectangle2f bbox; for(UINT exampleIndex = 0; exampleIndex < dataset.Entries().Length(); exampleIndex++) { const BinaryClassifier::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 = bbox.Dimensions().y / bbox.Dimensions().x; float AspectRatio = 1.0f; if(AspectRatio > 1.0f) { bmp.Allocate(UINT(bmpSize / AspectRatio), bmpSize); } else { bmp.Allocate(bmpSize, UINT(bmpSize * AspectRatio)); } BinaryClassifier::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::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 &classifier, const MulticlassClassifier::Dataset &dataset, const String &filename) { // // Ensure consistent coloring between images // srand(2); KMeansClustering colorClusters; Vector 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::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 = bbox.Dimensions().y / bbox.Dimensions().x; float AspectRatio = 1.0f; if(AspectRatio > 1.0f) { bmp.Allocate(UINT(bmpSize / AspectRatio), bmpSize); } else { bmp.Allocate(bmpSize, UINT(bmpSize * AspectRatio)); } BinaryClassifier::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 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::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::Dataset &dataset, UINT exampleCount, double errorRate) { dataset.ClassCount() = 2; for(UINT exampleIndex = 0; exampleIndex < exampleCount; exampleIndex++) { ClassifierExample 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::Dataset &dataset, UINT exampleCount, double errorRate) { dataset.ClassCount() = 4; for(UINT exampleIndex = 0; exampleIndex < exampleCount; exampleIndex++) { ClassifierExample 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::Dataset dataset, trainingSet, testSet; MakeMulticlassCartesianDataset(dataset, pointCount, errorRate); dataset.PartitionDataset(trainingSet, testSet, trainingSetRatio); // // Nearest Neighbor // for(UINT k = 1; k <= 13; k += 2) { MulticlassClassifierNearestNeighborBruteForce nearestNeighborClassifier; nearestNeighborClassifier.Configure(NearestNeighborConfiguration(k)); TrainAndSaveMulticlassClassifierResults(nearestNeighborClassifier, trainingSet, testSet, "NearestNeighbor_k=" + String(k)); } // // Decision Tree // for(UINT treeDepth = 1; treeDepth <= 9; treeDepth++) { MulticlassClassifierDecisionTree decisionTreeClassifier(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount)); TrainAndSaveMulticlassClassifierResults(decisionTreeClassifier, trainingSet, testSet, "DecisionTree_depth=" + String(treeDepth)); } // // AdaBoost, varying tree depth // for(UINT treeDepth = 1; treeDepth <= 9; treeDepth++) { const UINT boostCount = 100; MulticlassClassifierFactoryDecisionTree decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount)); MulticlassClassifierAdaBoostM1 adaBoostClassifier(AdaBoostM1Configuration(boostCount, pointCount, true, &decisionStumpFactory) ); TrainAndSaveMulticlassClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth)); } // // AdaBoost, varying boost count // 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 decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount)); MulticlassClassifierAdaBoostM1 adaBoostClassifier(AdaBoostM1Configuration(boostCount, pointCount, true, &decisionStumpFactory) ); TrainAndSaveMulticlassClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth)); } // // SVM Pairwise Coupling, varying kernel // for(UINT kernelIndex = 0; kernelIndex < _SVMKernels.Length(); kernelIndex++) { BinaryClassifierFactorySVM SVMClassifierFactory(SVMConfiguration(1.0, _SVMKernels[kernelIndex].kernel)); MulticlassClassifierPairwiseCoupling pairwiseCoupling((PairwiseCouplingConfiguration(&SVMClassifierFactory))); TrainAndSaveMulticlassClassifierResults(pairwiseCoupling, trainingSet, testSet, "PairwiseCoupling(SVM)_C=1.0_kernel=" + String(_SVMKernels[kernelIndex].description)); } // // SVM One vs. All, varying kernel // for(UINT kernelIndex = 0; kernelIndex < _SVMKernels.Length(); kernelIndex++) { BinaryClassifierFactorySVM SVMClassifierFactory(SVMConfiguration(1.0, _SVMKernels[kernelIndex].kernel)); MulticlassClassifierOneVsAll oneVsAll((OneVsAllConfiguration(&SVMClassifierFactory))); TrainAndSaveMulticlassClassifierResults(oneVsAll, trainingSet, testSet, "OneVsAll(SVM)_C=1.0_kernel=" + String(_SVMKernels[kernelIndex].description)); } } void App::RunBinaryClassifiers() { Console::WriteLine("Running binary classifiers"); BinaryClassifier::Dataset dataset, trainingSet, testSet; MakeBinaryCartesianDataset(dataset, pointCount, errorRate); dataset.PartitionDataset(trainingSet, testSet, trainingSetRatio); // // Nearest Neighbor // for(UINT k = 1; k <= 13; k += 2) { MulticlassClassifierNearestNeighborBruteForce nearestNeighborClassifier; nearestNeighborClassifier.Configure(NearestNeighborConfiguration(k)); BinaryClassifierMulticlass multiClassWrapper(nearestNeighborClassifier); TrainAndSaveBinaryClassifierResults(multiClassWrapper, trainingSet, testSet, "NearestNeighbor_k=" + String(k)); } // // Decision Tree // for(UINT treeDepth = 1; treeDepth <= 10; treeDepth++) { MulticlassClassifierDecisionTree decisionTreeClassifier(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount)); BinaryClassifierMulticlass multiClassWrapper(decisionTreeClassifier); TrainAndSaveBinaryClassifierResults(multiClassWrapper, trainingSet, testSet, "DecisionTree_depth=" + String(treeDepth)); } // // AdaBoost, varying tree depth // for(UINT treeDepth = 1; treeDepth <= 9; treeDepth++) { const UINT boostCount = 100; MulticlassClassifierFactoryDecisionTree decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount)); BinaryClassifierFactoryMulticlass binaryFactory(&decisionStumpFactory); BinaryClassifierAdaBoost adaBoostClassifier(AdaBoostConfiguration(boostCount, pointCount, true, &binaryFactory) ); TrainAndSaveBinaryClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth)); } // // AdaBoost, varying boost count // 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 decisionStumpFactory(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount)); BinaryClassifierFactoryMulticlass binaryFactory(&decisionStumpFactory); BinaryClassifierAdaBoost adaBoostClassifier(AdaBoostConfiguration(boostCount, pointCount, true, &binaryFactory) ); TrainAndSaveBinaryClassifierResults(adaBoostClassifier, trainingSet, testSet, "AdaBoost(DecisionTree)_boostCount=" + String(boostCount) + "_depth=" + String(treeDepth)); } for(UINT treeDepth = 1; treeDepth <= 10; treeDepth++) { MulticlassClassifierDecisionTree decisionTreeClassifier(DecisionTreeConfiguration(treeDepth, 0, 0, pointCount)); decisionTreeClassifier.Configure(DecisionTreeConfiguration(treeDepth)); BinaryClassifierMulticlass multiClassWrapper(decisionTreeClassifier); TrainAndSaveBinaryClassifierResults(multiClassWrapper, trainingSet, testSet, "DecisionTree_depth=" + String(treeDepth)); } // // SVM, varying kernel // for(UINT kernelIndex = 0; kernelIndex < _SVMKernels.Length(); kernelIndex++) { BinaryClassifierSVM SVMClassifier(SVMConfiguration(1.0, _SVMKernels[kernelIndex].kernel)); TrainAndSaveBinaryClassifierResults(SVMClassifier, trainingSet, testSet, "SVM_C=1.0_kernel=" + _SVMKernels[kernelIndex].description); } // // SVM, varying C // 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 SVMClassifier(SVMConfiguration(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(); }