Posted
Comments None

Let's start our exploration of the new mapper functions with the 4-output mapper functions of 2, 3, and 4 arguments. In GeneXproTools these functions are represented by Map4A, Map4B, and Map4C, where the big letter at the end represents the number of arguments (A corresponds to 2, B to 3, and C to 4).

The Map4B and Map4C functions were the very first that I designed and tested (remember, I was trying to create functions with 3-4 discrete outputs that matched the argmin/argmax functions in performance), and both of them perform better than the argmax function of 4 arguments (98% hits for B, 99% for C, and 96% for the argmax).

I've already talked about the Map4B function in the post "Function Design: New 3-6 Output Functions" and as you can see from the code below, the Map4C function explores a similar design structure, with the difference that the middle point is in this case also determined independently by one of the arguments of the function.

The Map4A function is a late comer and is slightly inferior to the Map4B and C, but nonetheless performs slightly better than the argmax function of 4 arguments, even though it's only a function of 2 arguments (97% hits vs 96% for the argmax)!

Like I said, the Map4A function (and all mapper functions of the A series; I'll describe them in subsequent posts) was one of the latest mapper functions that I created. As they use only one argument for the map, I had to define a slack to generate the necessary number of intervals (here, for the Map4A function, we need to define 4 intervals for the four different outputs). And my first choice for the slack was 1000 but unfortunately it didn't work very well. I was very disappointed because I so wanted to create mapper functions of 2 arguments (functions of 2 arguments enjoy a special status in GeneXproTools because they require less bulky tree structures)! Just out of desperation I decided to try other values for the slack: 100, 10, and 1. And surprise: It made a huge difference, with 10 the best value for the Map4A function (a slack of 1 was the second best). (Who said all intervals are equal?)

For completeness I'm including here the C++ code for all the mappers of this series, including the Map4B function introduced in the post "Function Design: New 3-6 Output Functions":

    // Map4A(x0,x1): 4-Output Mapper Function
    const double SLACK = 10.0;
    double output = 0.0;
    if (x[1] < (x[0] - SLACK))
        output = 0.0;
    else if (x[1] >= (x[0] - SLACK) && x[1] < x[0])
        output = 1.0;
    else if (x[1] >= x[0] && x[1] < (x[0] + SLACK))
        output = 2.0;
    else if (x[1] >= (x[0] + SLACK))
        output = 3.0;
    return output;

 

    // Map4B(x0,x1,x2): 4-Output Mapper Function
    // evaluate min(x0,x1), max(x0,x1) and midrange
    double min = x[0];
    double max = x[1];
    if (min > x[1])
    {
        min = x[1];
        max = x[0];
    }
    double midrange = (max + min)/2.0;
    double output = 0.0;
    if (x[2] < min)
        output = 0.0;
    else if (x[2] >= min && x[2] < midrange)
        output = 1.0;
    else if (x[2] >= midrange && x[2] < max)
        output = 2.0;
    else if (x[2] >= max)
        output = 3.0;
    return output;

 

    // Map4C(x0,x1,x2,x3): 4-Output Mapper Function
    // evaluate min(x,y,z), max(x,y,z) and midleValue(x,y,z)
    //
    // evaluate min(x,y,z)
    double min = x[0];
    int argmin = 0;
    if (min > x[1])
    {
        min = x[1];
        argmin = 1;
    }
    if (min > x[2])
    {
        min = x[2];
        argmin = 2;
    }
    // evaluate max(x,y,z)
    double max = x[0];
    int argmax = 0;
    if (max < x[1])
    {
        max = x[1];
        argmax = 1;
    }
    if (max < x[2])
    {
        max = x[2];
        argmax = 2;
    }
    // evaluate midleValue(x,y,z)
    double midleValue = x[2];
    if (0 != argmin && 0 != argmax)
        midleValue = x[0];
    if (1 != argmin && 1 != argmax)
        midleValue = x[1];

    double output = 0.0;
    if (x[3] < min)
        output = 0.0;
    else if (x[3] >= min && x[3] < midleValue)
        output = 1.0;
    else if (x[3] >= midleValue && x[3] < max)
        output = 2.0;
    else if (x[3] >= max)
        output = 3.0;
    return output;

In the next post I'll describe the 5-output mapper functions of 2, 3, and 4 arguments.

Author

Posted
Comments None

One could easily go overboard in the design of all these new classifier and mapper functions, as the combinations of all the different parameters one can change are virtually endless. So it's a good idea to have a fast and simple way of measuring and comparing their performance so that we can keep just the ones that perform well in the GeneXproTools environment when we put them to work together with other functions.

So, in some kind of meta-analysis, I'm using GeneXproTools itself to decide which functions to add to the built-in math functions of GeneXproTools and which ones to reject! And what's more interesting, you don't have to have access to the source code to be able to do this kind of analysis: any one can do it using the Custom Math Functions of GeneXproTools!

But anyway, I chose the Iris dataset for this study because it's simple enough with just 3 classes (Iris Setosa, Iris Virginica and Iris Versicolor) and also because it's a well-balanced dataset with 50 records for each of the 3 classes. Indeed, you don't need very sophisticated classification algorithms to solve this problem successfully, but you do need a few simple tools to solve it in one go, such as the new 3-6 discrete output functions we've been talking about.

So the setup is very simple: I'm using a unigenic system with a single tree and a head size of 12. As for the other settings (population size, genetic operators, random numerical constants, etc.), I'm using the default values of GeneXproTools for Regression. For the function set I'm also using the Regression defaults, but I'm also adding the function under study weighted 20 times.

Then, using the Absolute Error/Hits fitness function with a Precision of zero, I perform 20 runs of 1000 generations each, where I keep just the last model of each run. Then I compute the average percentage of hits for each study by choosing the Hits favorite statistic in the History Panel. And this is the value that I'm giving you in the posts whenever I'm reporting how well a particular function performs and how it compares to the argmax function of 4 arguments, which as I said in the post "Function Design: Argmin & Argmax", will be used as my point of reference.



Author

Posted
Comments None

Sometimes the inspiration comes from the least expected places. I had been working on the Logic Synthesis documentation, updating the tutorials with new images and content. For a couple of weeks I lived and breathed Boolean logic. And in those situations I always end up discovering new interesting things (even if they are new only to me).

I was still under the influence of logic when I started working on this new project. And one of the things that always leaves a long impression when I work with the multiplexer functions is their structure. This time I could even get a glimpse at their structure with the new Variable Importance Chart of GeneXproTools. And I was mesmerized! There it was: The first 3 inputs of the 11-multiplexer are the most important and they determine the address!



Are you seeing where I'm going with this? I can use the same idea to design 4-6 output classifier functions of 2, 3, 4,… n arguments!

For functions of 2 arguments I can use the first argument to determine the intervals for the second (3, 4, 5, 6,… n intervals, but I'm limiting this to 6). For functions of 3 arguments I can use the first 2 arguments to map the third argument to a series of intervals, and similarly for functions of 4 arguments.

And there's yet another way of creating classifier functions exploring the same idea! For example, for functions of 3 arguments we can use the first argument to determine the anchoring point for the remaining 2 arguments and design more powerful functions of 3 arguments with properties similar to the BUY-SELL-WAIT function described in the post "Function Design: The BUY-SELL-WAIT Function" and the 3-output classifier functions described in the post "Function Design: More 3-Output Classifier Functions".

And similarly for functions of 4 arguments: we can use the first 2 arguments to generate the map for the last 2 arguments and create more flexible functions similar to the CL2B function described in the post "Function Design: More 3-Output Classifier Functions".

And what's really interesting is that I wasn't wrong about their potential! These functions are indeed special: some are better than others, but they all are comparable to the argmin/argmax functions (see my previous post where I describe how I'm measuring performance)!

So I'm giving the first class of functions a name of their own. I'm calling them Mapper Functions and representing them by Map3A, Map4A, Map5A, Map6A, … and so on in GeneXproTools (again, the numeral represents the number of discrete outputs and the big letter represents the order, A, B, C,… etc.).

As for the second class, they aren't really mapper functions because they don't map particular intervals to specific outputs. But they do use some of the arguments to encode anchoring points that are used to make decisions about the output. Since they have some affinity with the 3-output classifier functions described in the posts "Function Design: The BUY-SELL-WAIT Function" and "Function Design: More 3-Output Classifier Functions", I'm calling them Elastic Classifier Functions and representing them by ECL3A, ECL3B… in GeneXproTools.

Just to give you a flavor of the structure of these functions, I'm including here the C++ code of the very first mapper function that I implemented and tested, the 4-output mapper function of 3 arguments:

    // Map4B(x0,x1,x2): 4-Output Mapper Function
    // evaluate min(x0,x1), max(x0,x1) and midrange
    double min = x[0];
    double max = x[1];
    if (min > x[1])
    {
        min = x[1];
        max = x[0];
    }
    double midrange = (max + min)/2.0;
    
    double output = 0.0;
    if (x[2] < min)
        output = 0.0;
    if (x[2] >= min && x[2] < midrange)
        output = 1.0;
    if (x[2] >= midrange && x[2] < max)
        output = 2.0;
    if (x[2] >= max)
        output = 3.0;
    return output;

This function performs better than the argmax(x0,x1,x2,x3) (98% hits vs 96%) and has the additional advantage that it only needs 3 arguments! We can design similar mapper functions with 2, 3 and 4 arguments (I'm limiting myself to 4 because of the current max arity constraint of the built-in math functions of GeneXproTools, but you can extend this to any number of arguments) and with the number of discrete outputs that we need (I decided not to go beyond 6 because I have to add code for all these functions in all the 17 programming languages that GeneXproTools supports, but you can experiment with any number of outputs using, for example, the Custom Math Functions of GeneXproTools).

Over the next posts I'll give you the code for all these new mappers and elastic classifiers with some numerical evidence of their performance.

Author

Posted
Comments None

Just to recap, we are still trying to design 3-6 output classifier functions that work well both at the root level of a single tree and, if possible, as linking functions of 2 arguments, so that they can be used to create sophisticated trading rules as outlined in the post "New Project: Multi-class Classification & Trading Strategies".

The argmin and argmax functions of 3 and 4 arguments are a little detour in this pursuit because we cannot implement functions of 5 and 6 arguments in GeneXproTools (4 is the max limit). Moreover, the argmin and argmax functions of 2 arguments (which, by the way, are already available in GeneXproTools as GT2B and LT2B) are not very interesting as linking functions. Notwithstanding, we are implementing the argmin function of 2 arguments as a new linking function in GeneXproTools; for the argmax I was unable to find a neutral gene and therefore had to leave it out.

Now back to the argmin and argmax functions of 3 and 4 arguments. These functions are just amazing! They are much better than all the other functions of 3-4 discrete outputs that I managed to come up with (see my previous post "Function Design: 4-6 Output Classifier Functions")! And I was so disheartened the first time I came to this realization. What was so special about these argmin and argmax functions? Can I ever dream of designing 4-6 output classifier functions that could match their performance? (I must confess that at the time I wasn't having any grandiose dreams: I was just heartbroken.)

But anyway, here's the C++ code for the amazing, yet so simple, argmin and argmax functions of 4 arguments:

Argmin function:

    double temp = arg[0];
    double argMin = 0.0;
    if (temp >= arg[1])
    {
        temp = arg[1];
        argMin = 1.0;
    }
    if (temp >= arg[2])
    {
        temp = arg[2];
        argMin = 2.0;
    }
    if (temp >= arg[3])
    {
        argMin = 3.0;
    }
    return argMin;

Argmax function:

    double temp = arg[0];
    double argMax = 0.0;
    if (temp < arg[1])
    {
        temp = arg[1];
        argMax = 1.0;
    }
    if (temp < arg[2])
    {
        temp = arg[2];
        argMax = 2.0;
    }
    if (temp < arg[3])
    {
        argMax = 3.0;
    }
    return argMax;

The way I'm measuring and comparing their performance is totally pragmatic and has nothing mathematical to it as I'm just interested in creating good all-purpose functions that work well in the GeneXproTools modeling environment. So, as usual, I'm testing them on the Iris dataset and evaluating the average number of hits for a total of 20 best-of-run models. More specifically, I'm using the default math functions of GeneXproTools for Regression plus the function under study weighted 20 times. Then I create 20 runs of 1000 generations each, keeping just the last model of each run.

Curiously enough, the argmax function seems to perform slightly worse than the argmin, so I'll be using the argmax function of 4 arguments as my benchmark: If I ever manage to design a 4-6 output function that performs as well as the argmax function of 4 arguments, I'll have reason to celebrate!

I didn't know at the time, but I just couldn't let this go. There must be some other functions out there that could do a similar job! I still remember the first time I heard about the argmin and argmax functions: "Is that even a function?" "What are they good for?" "Who would go to the trouble of creating such functions and giving them such nice names?" Well, I didn't rest for two days and in the third I had my little eureka moment and came up with a solution. But I'll tell that story in the next post.

Author

Posted
Comments None

This is a story about failures. And I'm telling you about them because without them I would never have had the flash of inspiration that was needed to come up with good 4-6 discrete output classifier functions.

I first started my exploration with functions of 2 arguments with 4-6 discrete outputs. The reason for this was that I also wanted functions that worked well as linking functions in GeneXproTools, which as you know is for now limited to functions of 2 arguments. The idea was to then scale up these 2-argument functions to 3- and 4-argument functions, which is the current arity limit in GeneXproTools.

And here's the C++ code for the first failure:

    if ((arg[0] > 0.0 && arg[0] <= 1.0) && (arg[1] > 0.0 && arg[1] <= 1.0))
        output = 1.0;
    else
        if ((arg[0] > 1.0 && arg[0] <= 2.0) && (arg[1] > 1.0 && arg[1] <= 2.0))
            output = 2.0;
        else
            if ((arg[0] > 2.0 && arg[0] <= 3.0) && (arg[1] > 2.0 && arg[1] <= 3.0))
                output = 3.0;
            else output = 0.0;
    return output;

As usual, I tested it on the Iris dataset with just 3 classes and it just didn't work: most of the time it got stuck indefinitely on just 2 classes. I also noticed that the extra redundancy was playing a positive role and so I decided to extend this design to 6 discrete outputs. But again, most of the time it got stuck on just 2 classes!

I tried other designs, exploring symmetry around zero, but again the functions performed poorly. Here's the code of another failure:

    if (arg[0] >= 1.0 && arg[1] >= 1.0)
        output = 1.0;
    else
        if (arg[0] <= -1.0 && arg[1] <= -1.0)
            output = 2.0;
        else
            if (arg[0] >= 1.0 && arg[1] <= -1.0)
                output = 3.0;
            else output = 0.0;
    return output;

At this point I was starting to see why things weren't working the way I wanted them to work, and frankly I couldn't see how I could change this. How could I get around the fact that some lucky class output (usually the one under the last ELSE clause) got all the slack that it needed while the others got just a tiny little fraction of the real line to work with? And was this the real problem here? Or was it something else?

At this point I was scratching my head and resisting implementing the argmin and argmax functions of 3 and 4 arguments (they were on my long list of functions to be added to GeneXproTools) because they could not solve my problem for the linking functions (remember, they must be functions of 2 arguments)!

So out of desperation I decided to implement a new class of functions based on modulo n functions (an old untested idea of mine for the built-in math functions of GeneXproTools) that would fairly divide the real line estate among the different outputs. And if it worked I could scale it up to as many discrete outputs as I wanted and implement it as a function of 2, 3, 4 and even 1 arguments! Who needed the argmin and argmax functions?

Well, they work great both as linking functions and as normal functions when meshed with the other math functions in unigenic systems. These functions are definitely worth exploring and worth adding to the built-in math functions of GeneXproTools, but I'm leaving that to another project as there are some thorny issues with neutral genes if Random Numerical Constants are not turned on.

Here's the C++ code for a 4-output function of 2 arguments:

    output = fabs(arg[0]+ arg[1]);
    output = floor(((output/4.0) - (int)(output/4.0))*4.0);
    return output;

As you can see these functions can be easily adapted to accommodate 2-6 discrete outputs and can be easily implemented as functions of 1-4 arguments, and if we cover all the combinations we get a total of 20 new functions. Or we could implement just the functions with 2-4 outputs, giving a total of 12 new functions. Any ideas?

Well, after all these creative failures and the small success with modulo n functions, I was super curious to see how the argmin and argmax class of functions of 3 and 4 arguments performed on the Iris dataset. But I'll tell that story in the next post.

Author

← Older Newer →