source: trunk/CrossPare/src/de/ugoe/cs/cpdp/training/GPTraining.java @ 107

Last change on this file since 107 was 106, checked in by atrautsch, 8 years ago

configurations pulled to top

File size: 27.5 KB
Line 
1package de.ugoe.cs.cpdp.training;
2
3import java.util.LinkedList;
4import java.util.List;
5
6import org.apache.commons.collections4.list.SetUniqueList;
7
8import weka.classifiers.AbstractClassifier;
9import weka.classifiers.Classifier;
10import weka.core.Instance;
11import weka.core.Instances;
12import org.apache.commons.lang3.ArrayUtils;
13import org.jgap.Configuration;
14import org.jgap.InvalidConfigurationException;
15import org.jgap.gp.CommandGene;
16import org.jgap.gp.GPProblem;
17
18import org.jgap.gp.function.Add;
19import org.jgap.gp.function.Multiply;
20import org.jgap.gp.function.Log;
21import org.jgap.gp.function.Subtract;
22import org.jgap.gp.function.Divide;
23import org.jgap.gp.function.Sine;
24import org.jgap.gp.function.Cosine;
25import org.jgap.gp.function.Max;
26import org.jgap.gp.function.Exp;
27
28import org.jgap.gp.impl.DeltaGPFitnessEvaluator;
29import org.jgap.gp.impl.GPConfiguration;
30import org.jgap.gp.impl.GPGenotype;
31import org.jgap.gp.impl.TournamentSelector;
32import org.jgap.gp.terminal.Terminal;
33import org.jgap.gp.GPFitnessFunction;
34import org.jgap.gp.IGPProgram;
35import org.jgap.gp.terminal.Variable;
36import org.jgap.gp.MathCommand;
37import org.jgap.util.ICloneable;
38
39import de.ugoe.cs.cpdp.util.WekaUtils;
40
41import org.jgap.gp.impl.ProgramChromosome;
42import org.jgap.util.CloneException;
43
44/**
45 * Genetic Programming Trainer
46 *
47 *
48 * - GPRun is a Run of a complete Genetic Programm Evolution, we want several complete runs.
49 * - GPVClassifier is the Validation Classifier
50 * - GPVVClassifier is the Validation-Voting Classifier
51 *
52 * config: <setwisetrainer name="GPTraining" param="GPVVClassifier" />
53 */
54public class GPTraining implements ISetWiseTrainingStrategy, IWekaCompatibleTrainer  {
55   
56    private GPVClassifier classifier = null;
57   
58    private int populationSize = 1000;
59    private int initMinDepth = 2;
60    private int initMaxDepth = 6;
61    private int tournamentSize = 7;
62    private int maxGenerations = 50;
63    private double errorType2Weight = 1;
64    private int numberRuns = 200;  // 200 in the paper
65    private int maxDepth = 20;  // max depth within one program
66    private int maxNodes = 100;  // max nodes within one program
67
68    @Override
69    public void setParameter(String parameters) {
70        if(parameters.equals("GPVVClassifier")) {
71            this.classifier = new GPVVClassifier();
72            ((GPVVClassifier)this.classifier).configure(populationSize, initMinDepth, initMaxDepth, tournamentSize, maxGenerations, errorType2Weight, numberRuns, maxDepth, maxNodes);
73        }else if(parameters.equals("GPVClassifier")) {
74            this.classifier = new GPVClassifier();
75            ((GPVClassifier)this.classifier).configure(populationSize, initMinDepth, initMaxDepth, tournamentSize, maxGenerations, errorType2Weight, numberRuns, maxDepth, maxNodes);
76        }else {
77            // default
78            this.classifier = new GPVVClassifier();
79            ((GPVVClassifier)this.classifier).configure(populationSize, initMinDepth, initMaxDepth, tournamentSize, maxGenerations, errorType2Weight, numberRuns, maxDepth, maxNodes);
80        }
81    }
82
83    @Override
84    public void apply(SetUniqueList<Instances> traindataSet) {
85        try {
86            classifier.buildClassifier(traindataSet);
87        }catch(Exception e) {
88            throw new RuntimeException(e);
89        }
90    }
91
92    @Override
93    public String getName() {
94        return "GPTraining";
95    }
96
97    @Override
98    public Classifier getClassifier() {
99        return this.classifier;
100    }
101   
102    public class InstanceData {
103        private double[][] instances_x;
104        private boolean[] instances_y;
105       
106        public InstanceData(Instances instances) {
107            this.instances_x = new double[instances.numInstances()][instances.numAttributes()-1];
108            this.instances_y = new boolean[instances.numInstances()];
109           
110            Instance current;
111            for(int i=0; i < this.instances_x.length; i++) {
112                current = instances.get(i);
113                this.instances_x[i] = WekaUtils.instanceValues(current);
114                this.instances_y[i] = 1.0 == current.classValue();
115            }
116        }
117       
118        public double[][] getX() {
119            return instances_x;
120        }
121        public boolean[] getY() {
122            return instances_y;
123        }
124    }
125   
126    /**
127     * One Run of a GP Classifier
128     * we want several runs to mitigate problems with local maxima/minima
129     */
130    public class GPRun extends AbstractClassifier {
131        private static final long serialVersionUID = -4250422550107888789L;
132
133        private int populationSize;
134        private int initMinDepth;
135        private int initMaxDepth;
136        private int tournamentSize;
137        private int maxGenerations;
138        private double errorType2Weight;
139        private int maxDepth;
140        private int maxNodes;
141       
142        private GPGenotype gp;
143        private GPProblem problem;
144       
145        public void configure(int populationSize, int initMinDepth, int initMaxDepth, int tournamentSize, int maxGenerations, double errorType2Weight, int maxDepth, int maxNodes) {
146            this.populationSize = populationSize;
147            this.initMinDepth = initMinDepth;
148            this.initMaxDepth = initMaxDepth;
149            this.tournamentSize = tournamentSize;
150            this.maxGenerations = maxGenerations;
151            this.errorType2Weight = errorType2Weight;
152            this.maxDepth = maxDepth;
153            this.maxNodes = maxNodes;
154        }
155       
156        public GPGenotype getGp() {
157            return this.gp;
158        }
159       
160        public Variable[] getVariables() {
161            return ((CrossPareGP)this.problem).getVariables();
162        }
163
164        @Override
165        public void buildClassifier(Instances traindata) throws Exception {
166            InstanceData train = new InstanceData(traindata);           
167            this.problem = new CrossPareGP(train.getX(), train.getY(), this.populationSize, this.initMinDepth, this.initMaxDepth, this.tournamentSize, this.errorType2Weight, this.maxDepth, this.maxNodes);
168            this.gp = problem.create();
169            this.gp.evolve(this.maxGenerations);
170        }
171       
172        /**
173         * GPProblem implementation
174         */
175        class CrossPareGP extends GPProblem {
176            private double[][] instances;
177            private boolean[] output;
178
179            private int maxDepth;
180            private int maxNodes;
181           
182            private Variable[] x;
183
184            public CrossPareGP(double[][] instances, boolean[] output, int populationSize, int minInitDept, int maxInitDepth, int tournamentSize, double errorType2Weight, int maxDepth, int maxNodes) throws InvalidConfigurationException {
185                super(new GPConfiguration());
186               
187                this.instances = instances;
188                this.output = output;
189                this.maxDepth = maxDepth;
190                this.maxNodes = maxNodes;
191
192                Configuration.reset();
193                GPConfiguration config = this.getGPConfiguration();
194               
195                this.x = new Variable[this.instances[0].length];
196               
197                for(int j=0; j < this.x.length; j++) {
198                    this.x[j] = Variable.create(config, "X"+j, CommandGene.DoubleClass);   
199                }
200
201                config.setGPFitnessEvaluator(new DeltaGPFitnessEvaluator()); // smaller fitness is better
202                //config.setGPFitnessEvaluator(new DefaultGPFitnessEvaluator()); // bigger fitness is better
203
204                config.setMinInitDepth(minInitDept);
205                config.setMaxInitDepth(maxInitDepth);
206               
207                config.setCrossoverProb((float)0.60);
208                config.setReproductionProb((float)0.10);
209                config.setMutationProb((float)0.30);
210
211                config.setSelectionMethod(new TournamentSelector(tournamentSize));
212
213                config.setPopulationSize(populationSize);
214
215                config.setMaxCrossoverDepth(4);
216                config.setFitnessFunction(new CrossPareFitness(this.x, this.instances, this.output, errorType2Weight));
217                config.setStrictProgramCreation(true);
218            }
219
220            // used for running the fitness function again for testing
221            public Variable[] getVariables() {
222                return this.x;
223            }
224
225
226            public GPGenotype create() throws InvalidConfigurationException {
227                GPConfiguration config = this.getGPConfiguration();
228
229                // return type
230                Class[] types = {CommandGene.DoubleClass};
231
232                // Arguments of result-producing chromosome: none
233                Class[][] argTypes = { {} };
234
235                // variables + functions, we set the variables with the values of the instances here
236                CommandGene[] vars = new CommandGene[this.instances[0].length];
237                for(int j=0; j < this.instances[0].length; j++) {
238                    vars[j] = this.x[j];
239                }
240                CommandGene[] funcs = {
241                    new Add(config, CommandGene.DoubleClass),
242                    new Subtract(config, CommandGene.DoubleClass),
243                    new Multiply(config, CommandGene.DoubleClass),
244                    new Divide(config, CommandGene.DoubleClass),
245                    new Sine(config, CommandGene.DoubleClass),
246                    new Cosine(config, CommandGene.DoubleClass),
247                    new Exp(config, CommandGene.DoubleClass),
248                    new Log(config, CommandGene.DoubleClass),
249                    new GT(config, CommandGene.DoubleClass),
250                    new Max(config, CommandGene.DoubleClass),
251                    new Terminal(config, CommandGene.DoubleClass, -100.0, 100.0, true), // min, max, whole numbers
252                };
253
254                CommandGene[] comb = (CommandGene[])ArrayUtils.addAll(vars, funcs);
255                CommandGene[][] nodeSets = {
256                    comb,
257                };
258               
259                // we only have one chromosome so this suffices
260                int minDepths[] = {config.getMinInitDepth()};
261                int maxDepths[] = {this.maxDepth};
262                GPGenotype result = GPGenotype.randomInitialGenotype(config, types, argTypes, nodeSets, minDepths, maxDepths, this.maxNodes, false); // 40 = maxNodes, true = verbose output
263
264                return result;
265            }
266        }
267
268       
269        /**
270         * Fitness function
271         */
272        class CrossPareFitness extends GPFitnessFunction {
273           
274            private static final long serialVersionUID = 75234832484387L;
275
276            private Variable[] x;
277
278            private double[][] instances;
279            private boolean[] output;
280
281            private double errorType2Weight = 1.0;
282
283            // needed in evaluate
284            //private Object[] NO_ARGS = new Object[0];
285
286            private double sfitness = 0.0f;
287            private int errorType1 = 0;
288            private int errorType2 = 0;
289
290            public CrossPareFitness(Variable[] x, double[][] instances, boolean[] output, double errorType2Weight) {
291                this.x = x;
292                this.instances = instances;
293                this.output = output;
294                this.errorType2Weight = errorType2Weight;
295            }
296
297            public int getErrorType1() {
298                return this.errorType1;
299            }
300
301            public int getErrorType2() {
302                return this.errorType2;
303            }
304
305            public double getSecondFitness() {
306                return this.sfitness;
307            }
308
309            public int getNumInstances() {
310                return this.instances.length;
311            }
312
313            @Override
314            protected double evaluate(final IGPProgram program) {
315                double pfitness = 0.0f;
316                this.sfitness = 0.0f;
317                double value = 0.0f;
318
319                // count classification errors
320                this.errorType1 = 0;
321                this.errorType2 = 0;
322
323                for(int i=0; i < this.instances.length; i++) {
324
325                    // requires that we have a variable for each column of our dataset (attribute of instance)
326                    for(int j=0; j < this.x.length; j++) {
327                        this.x[j].set(this.instances[i][j]);
328                    }
329
330                    // value gives us a double, if < 0.5 we set this instance as faulty
331                    value = program.execute_double(0, this.x);  // todo: test with this.x
332
333                    if(value < 0.5) {
334                        if(this.output[i] != true) {
335                            this.errorType1 += 1;
336                        }
337                    }else {
338                        if(this.output[i] == true) {
339                            this.errorType2 += 1;
340                        }
341                    }
342                }
343
344                // now calc pfitness
345                pfitness = (this.errorType1 + this.errorType2Weight * this.errorType2) / this.instances.length;
346
347                //System.out.println("pfitness: " + pfitness);
348
349                // number of nodes in the programm, if lower then 10 we assign sFitness of 10
350                // we can set metadata with setProgramData to save this
351                if(program.getChromosome(0).getSize(0) < 10) {
352                    program.setApplicationData(10.0f);
353                }
354
355                return pfitness;
356            }
357        }
358       
359        /**
360         * Custom GT implementation used in the GP Algorithm.
361         */
362         public class GT extends MathCommand implements ICloneable {
363             
364             private static final long serialVersionUID = 113454184817L;
365
366             public GT(final GPConfiguration a_conf, java.lang.Class a_returnType) throws InvalidConfigurationException {
367                 super(a_conf, 2, a_returnType);
368             }
369
370             public String toString() {
371                 return "GT(&1, &2)";
372             }
373
374             public String getName() {
375                 return "GT";
376             }   
377
378             public float execute_float(ProgramChromosome c, int n, Object[] args) {
379                 float f1 = c.execute_float(n, 0, args);
380                 float f2 = c.execute_float(n, 1, args);
381
382                 float ret = 1.0f;
383                 if(f1 > f2) {
384                     ret = 0.0f;
385                 }
386
387                 return ret;
388             }
389
390             public double execute_double(ProgramChromosome c, int n, Object[] args) {
391                 double f1 = c.execute_double(n, 0, args);
392                 double f2 = c.execute_double(n, 1, args);
393
394                 double ret = 1;
395                 if(f1 > f2)  {
396                     ret = 0;
397                 }
398                 return ret;
399             }
400
401             public Object clone() {
402                 try {
403                     GT result = new GT(getGPConfiguration(), getReturnType());
404                     return result;
405                 }catch(Exception ex) {
406                     throw new CloneException(ex);
407                 }
408             }
409         }
410    }
411   
412    /**
413     * GP Multiple Data Sets Validation-Voting Classifier
414     *
415     * As the GP Multiple Data Sets Validation Classifier
416     * But here we do keep a model candidate for each training set which may later vote
417     *
418     */
419    public class GPVVClassifier extends GPVClassifier {
420
421        private static final long serialVersionUID = -654710583852839901L;
422        private List<Classifier> classifiers = null;
423       
424        @Override
425        public void buildClassifier(Instances arg0) throws Exception {
426            // TODO Auto-generated method stub
427           
428        }
429       
430        public void buildClassifier(SetUniqueList<Instances> traindataSet) throws Exception {
431
432            // each classifier is trained with one project from the set
433            // then is evaluated on the rest
434            classifiers = new LinkedList<>();
435            for(int i=0; i < traindataSet.size(); i++) {
436               
437                // candidates we get out of evaluation
438                LinkedList<Classifier> candidates = new LinkedList<>();
439               
440                // number of runs
441                for(int k=0; k < this.numberRuns; k++) {
442                    Classifier classifier = new GPRun();
443                    ((GPRun)classifier).configure(this.populationSize, this.initMinDepth, this.initMaxDepth, this.tournamentSize, this.maxGenerations, this.errorType2Weight, this.maxDepth, this.maxNodes);
444                   
445                    // one project is training data
446                    classifier.buildClassifier(traindataSet.get(i));
447                   
448                    double[] errors;
449                    // rest of the set is evaluation data, we evaluate now
450                    for(int j=0; j < traindataSet.size(); j++) {
451                        if(j != i) {
452                            // if type1 and type2 errors are < 0.5 we allow the model in the final voting
453                            errors = this.evaluate((GPRun)classifier, traindataSet.get(j));
454                            if((errors[0] < 0.5) && (errors[0] < 0.5)) {
455                                candidates.add(classifier);
456                            }
457                        }
458                    }
459                }
460               
461                // now after the evaluation we do a model selection where only one model remains for the given training data
462                double smallest_error_count = Double.MAX_VALUE;
463                double[] errors;
464                Classifier best = null;
465                for(int ii=0; ii < candidates.size(); ii++) {
466                    for(int j=0; j < traindataSet.size(); j++) {
467                        if(j != i) {
468                            errors = this.evaluate((GPRun)candidates.get(ii), traindataSet.get(j));
469                           
470                            if(errors[0]+errors[1] < smallest_error_count) {
471                                best = candidates.get(ii);
472                            }
473                        }
474                    }
475                }
476               
477                // now we have the best classifier for this training data
478                classifiers.add(best);
479            }
480        }
481       
482        /**
483         * Use the remaining classifiers for our voting
484         */
485        @Override
486        public double classifyInstance(Instance instance) {
487           
488            int vote_positive = 0;
489           
490            for (int i = 0; i < classifiers.size(); i++) {
491                Classifier classifier = classifiers.get(i);
492               
493                GPGenotype gp = ((GPRun)classifier).getGp();
494                Variable[] vars = ((GPRun)classifier).getVariables();
495               
496                IGPProgram fitest = gp.getAllTimeBest();  // all time fitest
497                for(int j = 0; j < instance.numAttributes()-1; j++) {
498                   vars[j].set(instance.value(j));
499                }
500               
501                if(fitest.execute_double(0, vars) < 0.5) {
502                    vote_positive += 1;
503                }
504            }
505           
506            if(vote_positive >= (classifiers.size()/2)) {
507                return 1.0;
508            }else {
509                return 0.0;
510            }
511        }
512    }
513   
514    /**
515     * GP Multiple Data Sets Validation Classifier
516     *
517     *
518     * for one test data set:
519     *   for one in X possible training data sets:
520     *     For Y GP Runs:
521     *       train one Classifier with this training data
522     *       then evaluate the classifier with the remaining project
523     *       if the candidate model performs bad (error type1 or type2 > 50%) discard it
524     * for the remaining model candidates the best one is used
525     *
526     */
527    public class GPVClassifier extends AbstractClassifier {
528       
529        private List<Classifier> classifiers = null;
530        private Classifier best = null;
531
532        private static final long serialVersionUID = 3708714057579101522L;
533
534        protected int populationSize;
535        protected int initMinDepth;
536        protected int initMaxDepth;
537        protected int tournamentSize;
538        protected int maxGenerations;
539        protected double errorType2Weight;
540        protected int numberRuns;
541        protected int maxDepth;
542        protected int maxNodes;
543
544        /**
545         * Configure the GP Params and number of Runs
546         *
547         * @param populationSize
548         * @param initMinDepth
549         * @param initMaxDepth
550         * @param tournamentSize
551         * @param maxGenerations
552         * @param errorType2Weight
553         */
554        public void configure(int populationSize, int initMinDepth, int initMaxDepth, int tournamentSize, int maxGenerations, double errorType2Weight, int numberRuns, int maxDepth, int maxNodes) {
555            this.populationSize = populationSize;
556            this.initMinDepth = initMinDepth;
557            this.initMaxDepth = initMaxDepth;
558            this.tournamentSize = tournamentSize;
559            this.maxGenerations = maxGenerations;
560            this.errorType2Weight = errorType2Weight;
561            this.numberRuns = numberRuns;
562            this.maxDepth = maxDepth;
563            this.maxNodes = maxNodes;
564        }
565       
566        /** Build the GP Multiple Data Sets Validation Classifier
567         *
568         * - Traindata one of the Instances of the Set (which one? The firsT? as it is a list?)
569         * - Testdata one other Instances of the Set (the next one? chose randomly?)
570         * - Evaluation the rest of the instances
571         *
572         * @param traindataSet
573         * @throws Exception
574         */
575        public void buildClassifier(SetUniqueList<Instances> traindataSet) throws Exception {
576
577            // each classifier is trained with one project from the set
578            // then is evaluated on the rest
579            for(int i=0; i < traindataSet.size(); i++) {
580               
581                // candidates we get out of evaluation
582                LinkedList<Classifier> candidates = new LinkedList<>();
583               
584                // 200 runs
585                for(int k=0; k < this.numberRuns; k++) {
586                    Classifier classifier = new GPRun();
587                    ((GPRun)classifier).configure(this.populationSize, this.initMinDepth, this.initMaxDepth, this.tournamentSize, this.maxGenerations, this.errorType2Weight, this.maxDepth, this.maxNodes);
588                   
589                    // one project is training data
590                    classifier.buildClassifier(traindataSet.get(i));
591                   
592                    double[] errors;
593                   
594                    // rest of the set is evaluation data, we evaluate now
595                    for(int j=0; j < traindataSet.size(); j++) {
596                        if(j != i) {
597                            // if type1 and type2 errors are < 0.5 we allow the model in the final voting
598                            errors = this.evaluate((GPRun)classifier, traindataSet.get(j));
599                            if((errors[0] < 0.5) && (errors[0] < 0.5)) {
600                                candidates.add(classifier);                           
601                            }
602                        }
603                    }
604                }
605               
606                // now after the evaluation we do a model selection where only one model remains per training data set
607                // from that we chose the best model
608               
609                // now after the evaluation we do a model selection where only one model remains for the given training data
610                double smallest_error_count = Double.MAX_VALUE;
611                double[] errors;
612                Classifier best = null;
613                for(int ii=0; ii < candidates.size(); ii++) {
614                    for(int j=0; j < traindataSet.size(); j++) {
615                        if(j != i) {
616                            errors = this.evaluate((GPRun)candidates.get(ii), traindataSet.get(j));
617                           
618                            if(errors[0]+errors[1] < smallest_error_count) {
619                                best = candidates.get(ii);
620                            }
621                        }
622                    }
623                }
624               
625                // now we have the best classifier for this training data
626                classifiers.add(best);
627            }
628           
629            // now determine the best classifier for all training data
630            double smallest_error_count = Double.MAX_VALUE;
631            double error_count;
632            double errors[];
633            for(int j=0; j < classifiers.size(); j++) {
634                error_count = 0;
635                Classifier current = classifiers.get(j);
636                for(int i=0; i < traindataSet.size(); i++) {
637                    errors = this.evaluate((GPRun)current, traindataSet.get(i));
638                    error_count = errors[0] + errors[1];
639                }
640               
641                if(error_count < smallest_error_count) {
642                    best = current;
643                }
644            }
645        }
646       
647        @Override
648        public void buildClassifier(Instances traindata) throws Exception {
649            final Classifier classifier = new GPRun();
650            ((GPRun)classifier).configure(populationSize, initMinDepth, initMaxDepth, tournamentSize, maxGenerations, errorType2Weight, this.maxDepth, this.maxNodes);
651            classifier.buildClassifier(traindata);
652            classifiers.add(classifier);
653        }
654       
655        public double[] evaluate(GPRun classifier, Instances evalData) {
656            GPGenotype gp = classifier.getGp();
657            Variable[] vars = classifier.getVariables();
658           
659            IGPProgram fitest = gp.getAllTimeBest();  // selects the fitest of all not just the last generation
660           
661            double classification;
662            int error_type1 = 0;
663            int error_type2 = 0;
664            int positive = 0;
665            int negative = 0;
666           
667            for(Instance instance: evalData) {
668               
669                for(int i = 0; i < instance.numAttributes()-1; i++) {
670                    vars[i].set(instance.value(i));
671                }
672               
673                classification = fitest.execute_double(0, vars);
674               
675                // we need to count the absolutes of positives for percentage
676                if(instance.classValue() == 1.0) {
677                    positive +=1;
678                }else {
679                    negative +=1;
680                }
681               
682                // classification < 0.5 we say defective
683                if(classification < 0.5) {
684                    if(instance.classValue() != 1.0) {
685                        error_type1 += 1;
686                    }
687                }else {
688                    if(instance.classValue() == 1.0) {
689                        error_type2 += 1;
690                    }
691                }
692            }
693           
694            // return error types percentages for the types
695            double et1_per = error_type1 / negative;
696            double et2_per = error_type2 / positive;
697            return new double[]{et1_per, et2_per};
698        }
699       
700        /**
701         * Use only the best classifier from our evaluation phase
702         */
703        @Override
704        public double classifyInstance(Instance instance) {
705            GPGenotype gp = ((GPRun)best).getGp();
706            Variable[] vars = ((GPRun)best).getVariables();
707           
708            IGPProgram fitest = gp.getAllTimeBest();  // all time fitest
709            for(int i = 0; i < instance.numAttributes()-1; i++) {
710               vars[i].set(instance.value(i));
711            }
712           
713            double classification = fitest.execute_double(0, vars);
714           
715            if(classification < 0.5) {
716                return 1.0;
717            }else {
718                return 0.0;
719            }
720        }
721    }
722}
Note: See TracBrowser for help on using the repository browser.