/*
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<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.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<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 = 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<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)
{
    //
    // Ensure consistent coloring between images
    //
    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 = 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<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);
    
    //
    // Nearest Neighbor
    //
    for(UINT k = 1; k <= 13; k += 2)
    {
        MulticlassClassifierNearestNeighborBruteForce<CartesianPoint> nearestNeighborClassifier;
        nearestNeighborClassifier.Configure(NearestNeighborConfiguration(k));
        TrainAndSaveMulticlassClassifierResults(nearestNeighborClassifier, trainingSet, testSet, "NearestNeighbor_k=" + String(k));
    }

    //
    // Decision Tree
    //
    for(UINT treeDepth = 1; treeDepth <= 9; treeDepth++)
    {
        MulticlassClassifierDecisionTree<CartesianPoint> 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<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));
    }

    //
    // 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<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));
    }

    //
    // SVM Pairwise Coupling, varying kernel
    //
    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));
    }

    //
    // SVM One vs. All, varying kernel
    //
    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);

    //
    // Nearest Neighbor
    //
    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));
    }

    //
    // Decision Tree
    //
    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));
    }

    //
    // AdaBoost, varying tree depth
    //
    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));
    }

    //
    // 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<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));
    }

    //
    // SVM, varying kernel
    //
    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);
    }

    //
    // 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<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();
}