Search code examples
machine-learningaccord.net

Getting the class label using DynamicTimeWarping using Accord.NET


I'm developing a project in which I need to do gesture recognition. After searching for a way to do this I came across dynamic time warping. To try this idea, and since my project is in C#, I decided to try Accord.NET. Before I tried this out in my project, I created a clean project and modified the example on Accord.NET's documentation, that can be found here:

http://accord-framework.net/docs/html/T_Accord_Statistics_Kernels_DynamicTimeWarping.htm

So, what I'm trying right now, is to train my algorithm with a set of learning data (composed of the gestures swipe right, swipe left and double tap) and then, use the same examples in the learning data to see if the algorithm is identifying the correct gesture. The values are just an exemple, not the real deal.

The documentation is not very clear on how to do this, since the Decide method used on the example only returns a boolean. I've searched the documentation for a way to identify the correct class but to no avail.

The only thing I've found was the following line, but what it returns is an int value of 0 or 1, instead of a boolean:

var res1 = ((IClassifier)svm.ToMulticlass()).Decide(sequences[0]);

Can anyone point me in the right direction on how to correctly identify the gesture? This is my first attempt at machine learning and Accord.NET, so this is all absolutelly new to me. The example code can be found bellow.

namespace DynamicTimeWarpingExample
{
    public class Program
    {
        public static void Main(string[] args)
        {

            double[][][] sequences =
            {
                new double[][] // Swipe left
                {
                    new double[] { 1, 1, 1 }, 
                    new double[] { 1, 2, 1 }, 
                    new double[] { 1, 2, 2 }, 
                    new double[] { 2, 2, 2 }, 
                },

                new double[][] // Swipe right
                {
                    new double[] { 1, 10, 6 }, 
                    new double[] { 1, 5, 6 }, 
                    new double[] { 6, 7, 1 }, 
                },

                new double[][] // Double tap
                {
                    new double[] { 8, 2, 5 }, 
                    new double[] { 1, 50, 4 }, 
                }
            };


            int[] outputs =
            {
                    0,  // Swipe left
                    1,  // Swipe right
                    2  // Double tap
            };

            var smo = new SequentialMinimalOptimization<DynamicTimeWarping, double[][]>()
            {
                Complexity = 1.5,

                Kernel = new DynamicTimeWarping(alpha: 1, degree: 1)
            };

            var svm = smo.Learn(sequences, outputs);

            bool[] predicted = svm.Decide(sequences);

            double error = new ZeroOneLoss(outputs).Loss(predicted); // error will be 0.0

            var res1 = ((IClassifier<double[][], int>)svm.ToMulticlass()).Decide(sequences[0]); // returns 0

            var res2 = ((IClassifier<double[][], int>)svm.ToMulticlass()).Decide(sequences[1]); // returns 1

            var res3 = ((IClassifier<double[][], int>)svm.ToMulticlass()).Decide(sequences[2]); // returns 1
        }
    }
}

***************** New Version *****************

public static void Main(string[] args)
{
    double[][][] sequences =
    {
        new double[][] // Swipe left
        {
            new double[] { 1, 1, 1 },
            new double[] { 1, 2, 1 },
            new double[] { 1, 2, 2 },
            new double[] { 2, 2, 2 },
        },

        new double[][] // Swipe right
        {
            new double[] { 1, 10, 6 },
            new double[] { 1, 5, 6 },
            new double[] { 6, 7, 1 },
        },

        new double[][] // Double tap
        {
            new double[] { 8, 2, 5 },
            new double[] { 1, 50, 4 },
        }
    };


    int[] outputs =
    {
            0,  // Swipe left
            1,  // Swipe right
            2  // Double tap
    };

    var teacher = new MulticlassSupportVectorLearning<DynamicTimeWarping, double[][]>()
    {
        // Configure the learning algorithm to use SMO to train the
        //  underlying SVMs in each of the binary class subproblems.
        Learner = (param) => new SequentialMinimalOptimization<DynamicTimeWarping, double[][]>
        {
            Complexity = 1.5,
            Kernel = new DynamicTimeWarping(alpha: 1, degree: 1),
            //UseKernelEstimation = true
        }
    };

    // Learn a machine
    var machine = teacher.Learn(sequences, outputs);

    // Create the multi-class learning algorithm for the machine
    var calibration = new MulticlassSupportVectorLearning<DynamicTimeWarping, double[][]>()
    {
        Model = machine, // We will start with an existing machine

        // Configure the learning algorithm to use Platt's calibration
        Learner = (param) => new ProbabilisticOutputCalibration<DynamicTimeWarping, double[][]>()
        {
            Model = param.Model // Start with an existing machine
        }
    };

    // Configure parallel execution options
    calibration.ParallelOptions.MaxDegreeOfParallelism = 1;

    // Learn a machine
    calibration.Learn(sequences, outputs);

    double decision1, decision2, decision3, decision4, decision5, decision6;


    var res1 = machine.Probability(sequences[0], out decision1); // decision 0 - Probability 0.78698604216159851 - Score 1
    var res2 = machine.Probability(sequences[1], out decision2); // decision 1 - Probability 0.67246889837875257 - Score 1
    var res3 = machine.Probability(sequences[2], out decision3); // decision 2 - Probability 0.78698604216159851 - Score 1


    var newGesture1 = new double[][]
    {
        new double[] { 1, 1, 1 },
        new double[] { 1, 2, 1 },
        new double[] { 1, 2, 2 },
        new double[] { 2, 2, 2 },
    };

    var newGesture2 = new double[][]
    {
        new double[] { 1, 10, 6 },
        new double[] { 1, 5, 6 },
        new double[] { 6, 7, 1 },
    };

    var newGesture3 = new double[][]
    {
        new double[] { 8, 2, 5 },
        new double[] { 1, 50, 4 },
    };

    var res5 = machine.Score(newGesture1, out decision5); // decision 0 - Probability 0.35577588944247057 - Score 0.051251948605637254
    var res6 = machine.Score(newGesture2, out decision6); // decision 1 - Probability 0.40733908994050544 - Score 0.19912250476931792
    var res4 = machine.Score(newGesture3, out decision4); // decision 2 - Probability 0.71853321355842836 - Score 0.816934034911964
}

Solution

  • The problem is that you are creating a binary classifier for a problem that actually involves multiple classes.

    In your case, instead of doing:

    var smo = new SequentialMinimalOptimization<DynamicTimeWarping, double[][]>()      
    {
        Complexity = 1.5,
        Kernel = new DynamicTimeWarping(alpha: 1, degree: 1)
    };
    
    var svm = smo.Learn(sequences, outputs);
    

    You would want to wrap this binary learning problem into a multi-class learning using

    // Create the multi-class learning algorithm for the machine
    var teacher = new MulticlassSupportVectorLearning<DynamicTimeWarping, double[][]>()
    {
        // Configure the learning algorithm to use SMO to train the
        //  underlying SVMs in each of the binary class subproblems.
        Learner = (param) => new SequentialMinimalOptimization<DynamicTimeWarping, double[][]>
        {
            Complexity = 1.5,
            Kernel = new DynamicTimeWarping(alpha: 1, degree: 1)
        };
    }
    
    // Learn a  machine
    var svm = teacher.Learn(inputs, outputs);