source: src/main/java/weka/classifiers/meta/GridSearch.java @ 9

Last change on this file since 9 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 95.9 KB
Line 
1/*
2 *    This program is free software; you can redistribute it and/or modify
3 *    it under the terms of the GNU General Public License as published by
4 *    the Free Software Foundation; either version 2 of the License, or
5 *    (at your option) any later version.
6 *
7 *    This program is distributed in the hope that it will be useful,
8 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
9 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 *    GNU General Public License for more details.
11 *
12 *    You should have received a copy of the GNU General Public License
13 *    along with this program; if not, write to the Free Software
14 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
15 */
16
17/*
18 * GridSearch.java
19 * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
20 */
21
22package weka.classifiers.meta;
23
24import weka.classifiers.Classifier;
25import weka.classifiers.AbstractClassifier;
26import weka.classifiers.Evaluation;
27import weka.classifiers.RandomizableSingleClassifierEnhancer;
28import weka.classifiers.functions.LinearRegression;
29import weka.core.AdditionalMeasureProducer;
30import weka.core.Capabilities;
31import weka.core.Debug;
32import weka.core.Instance;
33import weka.core.Instances;
34import weka.core.MathematicalExpression;
35import weka.core.Option;
36import weka.core.OptionHandler;
37import weka.core.PropertyPath;
38import weka.core.RevisionHandler;
39import weka.core.RevisionUtils;
40import weka.core.SelectedTag;
41import weka.core.SerializedObject;
42import weka.core.Summarizable;
43import weka.core.Tag;
44import weka.core.Utils;
45import weka.core.Capabilities.Capability;
46import weka.filters.Filter;
47import weka.filters.supervised.attribute.PLSFilter;
48import weka.filters.unsupervised.attribute.MathExpression;
49import weka.filters.unsupervised.attribute.NumericCleaner;
50import weka.filters.unsupervised.instance.Resample;
51
52import java.beans.PropertyDescriptor;
53import java.io.File;
54import java.io.Serializable;
55import java.util.Collections;
56import java.util.Comparator;
57import java.util.Enumeration;
58import java.util.HashMap;
59import java.util.Hashtable;
60import java.util.Iterator;
61import java.util.Random;
62import java.util.Vector;
63
64/**
65 <!-- globalinfo-start -->
66 * Performs a grid search of parameter pairs for the a classifier (Y-axis, default is LinearRegression with the "Ridge" parameter) and the PLSFilter (X-axis, "# of Components") and chooses the best pair found for the actual predicting.<br/>
67 * <br/>
68 * The initial grid is worked on with 2-fold CV to determine the values of the parameter pairs for the selected type of evaluation (e.g., accuracy). The best point in the grid is then taken and a 10-fold CV is performed with the adjacent parameter pairs. If a better pair is found, then this will act as new center and another 10-fold CV will be performed (kind of hill-climbing). This process is repeated until no better pair is found or the best pair is on the border of the grid.<br/>
69 * In case the best pair is on the border, one can let GridSearch automatically extend the grid and continue the search. Check out the properties 'gridIsExtendable' (option '-extend-grid') and 'maxGridExtensions' (option '-max-grid-extensions &lt;num&gt;').<br/>
70 * <br/>
71 * GridSearch can handle doubles, integers (values are just cast to int) and booleans (0 is false, otherwise true). float, char and long are supported as well.<br/>
72 * <br/>
73 * The best filter/classifier setup can be accessed after the buildClassifier call via the getBestFilter/getBestClassifier methods.<br/>
74 * Note on the implementation: after the data has been passed through the filter, a default NumericCleaner filter is applied to the data in order to avoid numbers that are getting too small and might produce NaNs in other schemes.
75 * <p/>
76 <!-- globalinfo-end -->
77 *
78 <!-- options-start -->
79 * Valid options are: <p/>
80 *
81 * <pre> -E &lt;CC|RMSE|RRSE|MAE|RAE|COMB|ACC|KAP&gt;
82 *  Determines the parameter used for evaluation:
83 *  CC = Correlation coefficient
84 *  RMSE = Root mean squared error
85 *  RRSE = Root relative squared error
86 *  MAE = Mean absolute error
87 *  RAE = Root absolute error
88 *  COMB = Combined = (1-abs(CC)) + RRSE + RAE
89 *  ACC = Accuracy
90 *  KAP = Kappa
91 *  (default: CC)</pre>
92 *
93 * <pre> -y-property &lt;option&gt;
94 *  The Y option to test (without leading dash).
95 *  (default: classifier.ridge)</pre>
96 *
97 * <pre> -y-min &lt;num&gt;
98 *  The minimum for Y.
99 *  (default: -10)</pre>
100 *
101 * <pre> -y-max &lt;num&gt;
102 *  The maximum for Y.
103 *  (default: +5)</pre>
104 *
105 * <pre> -y-step &lt;num&gt;
106 *  The step size for Y.
107 *  (default: 1)</pre>
108 *
109 * <pre> -y-base &lt;num&gt;
110 *  The base for Y.
111 *  (default: 10)</pre>
112 *
113 * <pre> -y-expression &lt;expr&gt;
114 *  The expression for Y.
115 *  Available parameters:
116 *   BASE
117 *   FROM
118 *   TO
119 *   STEP
120 *   I - the current iteration value
121 *   (from 'FROM' to 'TO' with stepsize 'STEP')
122 *  (default: 'pow(BASE,I)')</pre>
123 *
124 * <pre> -filter &lt;filter specification&gt;
125 *  The filter to use (on X axis). Full classname of filter to include,
126 *  followed by scheme options.
127 *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
128 *
129 * <pre> -x-property &lt;option&gt;
130 *  The X option to test (without leading dash).
131 *  (default: filter.numComponents)</pre>
132 *
133 * <pre> -x-min &lt;num&gt;
134 *  The minimum for X.
135 *  (default: +5)</pre>
136 *
137 * <pre> -x-max &lt;num&gt;
138 *  The maximum for X.
139 *  (default: +20)</pre>
140 *
141 * <pre> -x-step &lt;num&gt;
142 *  The step size for X.
143 *  (default: 1)</pre>
144 *
145 * <pre> -x-base &lt;num&gt;
146 *  The base for X.
147 *  (default: 10)</pre>
148 *
149 * <pre> -x-expression &lt;expr&gt;
150 *  The expression for the X value.
151 *  Available parameters:
152 *   BASE
153 *   MIN
154 *   MAX
155 *   STEP
156 *   I - the current iteration value
157 *   (from 'FROM' to 'TO' with stepsize 'STEP')
158 *  (default: 'pow(BASE,I)')</pre>
159 *
160 * <pre> -extend-grid
161 *  Whether the grid can be extended.
162 *  (default: no)</pre>
163 *
164 * <pre> -max-grid-extensions &lt;num&gt;
165 *  The maximum number of grid extensions (-1 is unlimited).
166 *  (default: 3)</pre>
167 *
168 * <pre> -sample-size &lt;num&gt;
169 *  The size (in percent) of the sample to search the inital grid with.
170 *  (default: 100)</pre>
171 *
172 * <pre> -traversal &lt;ROW-WISE|COLUMN-WISE&gt;
173 *  The type of traversal for the grid.
174 *  (default: COLUMN-WISE)</pre>
175 *
176 * <pre> -log-file &lt;filename&gt;
177 *  The log file to log the messages to.
178 *  (default: none)</pre>
179 *
180 * <pre> -S &lt;num&gt;
181 *  Random number seed.
182 *  (default 1)</pre>
183 *
184 * <pre> -D
185 *  If set, classifier is run in debug mode and
186 *  may output additional info to the console</pre>
187 *
188 * <pre> -W
189 *  Full name of base classifier.
190 *  (default: weka.classifiers.functions.LinearRegression)</pre>
191 *
192 * <pre>
193 * Options specific to classifier weka.classifiers.functions.LinearRegression:
194 * </pre>
195 *
196 * <pre> -D
197 *  Produce debugging output.
198 *  (default no debugging output)</pre>
199 *
200 * <pre> -S &lt;number of selection method&gt;
201 *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
202 *  (default 0 = M5' method)</pre>
203 *
204 * <pre> -C
205 *  Do not try to eliminate colinear attributes.
206 * </pre>
207 *
208 * <pre> -R &lt;double&gt;
209 *  Set ridge parameter (default 1.0e-8).
210 * </pre>
211 *
212 * <pre>
213 * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
214 * </pre>
215 *
216 * <pre> -D
217 *  Turns on output of debugging information.</pre>
218 *
219 * <pre> -C &lt;num&gt;
220 *  The number of components to compute.
221 *  (default: 20)</pre>
222 *
223 * <pre> -U
224 *  Updates the class attribute as well.
225 *  (default: off)</pre>
226 *
227 * <pre> -M
228 *  Turns replacing of missing values on.
229 *  (default: off)</pre>
230 *
231 * <pre> -A &lt;SIMPLS|PLS1&gt;
232 *  The algorithm to use.
233 *  (default: PLS1)</pre>
234 *
235 * <pre> -P &lt;none|center|standardize&gt;
236 *  The type of preprocessing that is applied to the data.
237 *  (default: center)</pre>
238 *
239 <!-- options-end -->
240 *
241 * Examples:
242 * <ul>
243 *   <li>
244 *     <b>Optimizing SMO with RBFKernel (C and gamma)</b>
245 *     <ul>
246 *       <li>Set the evaluation to <i>Accuracy</i>.</li>
247 *       <li>Set the filter to <code>weka.filters.AllFilter</code> since we
248 *           don't need any special data processing and we don't optimize the
249 *           filter in this case (data gets always passed through filter!).</li>
250 *       <li>Set <code>weka.classifiers.functions.SMO</code> as classifier
251 *           with <code>weka.classifiers.functions.supportVector.RBFKernel</code>
252 *           as kernel.
253 *       </li>
254 *       <li>Set the XProperty to "classifier.c", XMin to "1", XMax to "16",
255 *           XStep to "1" and the XExpression to "I". This will test the "C"
256 *           parameter of SMO for the values from 1 to 16.</li>
257 *       <li>Set the YProperty to "classifier.kernel.gamma", YMin to "-5",
258 *           YMax to "2", YStep to "1" YBase to "10" and YExpression to
259 *           "pow(BASE,I)". This will test the gamma of the RBFKernel with the
260 *           values 10^-5, 10^-4,..,10^2.</li>
261 *     </ul>
262 *   </li>
263 *   <li>
264 *     <b>Optimizing PLSFilter with LinearRegression (# of components and ridge) - default setup</b>
265 *     <ul>
266 *       <li>Set the evaluation to <i>Correlation coefficient</i>.</li>
267 *       <li>Set the filter to <code>weka.filters.supervised.attribute.PLSFilter</code>.</li>
268 *       <li>Set <code>weka.classifiers.functions.LinearRegression</code> as
269 *           classifier and use no attribute selection and no elimination of
270 *           colinear attributes.</li>
271 *       <li>Set the XProperty to "filter.numComponents", XMin to "5", XMax
272 *           to "20" (this depends heavily on your dataset, should be no more
273 *           than the number of attributes!), XStep to "1" and XExpression to
274 *           "I". This will test the number of components the PLSFilter will
275 *           produce from 5 to 20.</li>
276 *       <li>Set the YProperty to "classifier.ridge", XMin to "-10", XMax to
277 *           "5", YStep to "1" and YExpression to "pow(BASE,I)". This will
278 *           try ridge parameters from 10^-10 to 10^5.</li>
279 *     </ul>
280 *   </li>
281 * </ul>
282 *
283 * General notes:
284 * <ul>
285 *   <li>Turn the <i>debug</i> flag on in order to see some progress output in the
286 *       console</li>
287 *   <li>If you want to view the fitness landscape that GridSearch explores,
288 *       select a <i>log file</i>. This log will then contain Gnuplot data and
289 *       script block for viewing the landscape. Just copy paste those blocks
290 *       into files named accordingly and run Gnuplot with them.</li>
291 * </ul>
292 *
293 * @author  Bernhard Pfahringer (bernhard at cs dot waikato dot ac dot nz)
294 * @author  Geoff Holmes (geoff at cs dot waikato dot ac dot nz)
295 * @author  fracpete (fracpete at waikato dot ac dot nz)
296 * @version $Revision: 5928 $
297 * @see     PLSFilter
298 * @see     LinearRegression
299 * @see     NumericCleaner
300 */
301public class GridSearch
302  extends RandomizableSingleClassifierEnhancer
303  implements AdditionalMeasureProducer, Summarizable {
304
305  /**
306   * a serializable version of Point2D.Double
307   *
308   * @see java.awt.geom.Point2D.Double
309   */
310  protected class PointDouble
311    extends java.awt.geom.Point2D.Double
312    implements Serializable, RevisionHandler {
313
314    /** for serialization */
315    private static final long serialVersionUID = 7151661776161898119L;
316   
317    /**
318     * the default constructor
319     *
320     * @param x         the x value of the point
321     * @param y         the y value of the point
322     */
323    public PointDouble(double x, double y) {
324      super(x, y);
325    }
326
327    /**
328     * Determines whether or not two points are equal.
329     *
330     * @param obj       an object to be compared with this PointDouble
331     * @return          true if the object to be compared has the same values;
332     *                  false otherwise.
333     */
334    public boolean equals(Object obj) {
335      PointDouble       pd;
336     
337      pd = (PointDouble) obj;
338     
339      return (Utils.eq(this.getX(), pd.getX()) && Utils.eq(this.getY(), pd.getY()));
340    }
341   
342    /**
343     * returns a string representation of the Point
344     *
345     * @return the point as string
346     */
347    public String toString() {
348      return super.toString().replaceAll(".*\\[", "[");
349    }
350   
351    /**
352     * Returns the revision string.
353     *
354     * @return          the revision
355     */
356    public String getRevision() {
357      return RevisionUtils.extract("$Revision: 5928 $");
358    }
359  }
360
361  /**
362   * a serializable version of Point
363   *
364   * @see java.awt.Point
365   */
366  protected class PointInt
367    extends java.awt.Point
368    implements Serializable, RevisionHandler {
369
370    /** for serialization */
371    private static final long serialVersionUID = -5900415163698021618L;
372
373    /**
374     * the default constructor
375     *
376     * @param x         the x value of the point
377     * @param y         the y value of the point
378     */
379    public PointInt(int x, int y) {
380      super(x, y);
381    }
382   
383    /**
384     * returns a string representation of the Point
385     *
386     * @return the point as string
387     */
388    public String toString() {
389      return super.toString().replaceAll(".*\\[", "[");
390    }
391   
392    /**
393     * Returns the revision string.
394     *
395     * @return          the revision
396     */
397    public String getRevision() {
398      return RevisionUtils.extract("$Revision: 5928 $");
399    }
400  }
401 
402  /**
403   * for generating the parameter pairs in a grid
404   */
405  protected class Grid
406    implements Serializable, RevisionHandler {
407
408    /** for serialization */
409    private static final long serialVersionUID = 7290732613611243139L;
410   
411    /** the minimum on the X axis */
412    protected double m_MinX;
413   
414    /** the maximum on the X axis */
415    protected double m_MaxX;
416   
417    /** the step size for the X axis */
418    protected double m_StepX;
419
420    /** the label for the X axis */
421    protected String m_LabelX;
422   
423    /** the minimum on the Y axis */
424    protected double m_MinY;
425   
426    /** the maximum on the Y axis */
427    protected double m_MaxY;
428   
429    /** the step size for the Y axis */
430    protected double m_StepY;
431
432    /** the label for the Y axis */
433    protected String m_LabelY;
434   
435    /** the number of points on the X axis */
436    protected int m_Width;
437   
438    /** the number of points on the Y axis */
439    protected int m_Height;
440   
441    /**
442     * initializes the grid
443     *
444     * @param minX      the minimum on the X axis
445     * @param maxX      the maximum on the X axis
446     * @param stepX     the step size for the X axis
447     * @param minY      the minimum on the Y axis
448     * @param maxY      the maximum on the Y axis
449     * @param stepY     the step size for the Y axis
450     */
451    public Grid(double minX, double maxX, double stepX, 
452                double minY, double maxY, double stepY) {
453      this(minX, maxX, stepX, "", minY, maxY, stepY, "");
454    }
455
456   
457    /**
458     * initializes the grid
459     *
460     * @param minX      the minimum on the X axis
461     * @param maxX      the maximum on the X axis
462     * @param stepX     the step size for the X axis
463     * @param labelX    the label for the X axis
464     * @param minY      the minimum on the Y axis
465     * @param maxY      the maximum on the Y axis
466     * @param stepY     the step size for the Y axis
467     * @param labelY    the label for the Y axis
468     */
469    public Grid(double minX, double maxX, double stepX, String labelX,
470                double minY, double maxY, double stepY, String labelY) {
471
472      super();
473     
474      m_MinX   = minX;
475      m_MaxX   = maxX;
476      m_StepX  = stepX;
477      m_LabelX = labelX;
478      m_MinY   = minY;
479      m_MaxY   = maxY;
480      m_StepY  = stepY;
481      m_LabelY = labelY;
482      m_Height = (int) StrictMath.round((m_MaxY - m_MinY) / m_StepY) + 1;
483      m_Width  = (int) StrictMath.round((m_MaxX - m_MinX) / m_StepX) + 1;
484     
485      // is min < max?
486      if (m_MinX >= m_MaxX)
487        throw new IllegalArgumentException("XMin must be smaller than XMax!");
488      if (m_MinY >= m_MaxY)
489        throw new IllegalArgumentException("YMin must be smaller than YMax!");
490     
491      // steps positive?
492      if (m_StepX <= 0)
493        throw new IllegalArgumentException("XStep must be a positive number!");
494      if (m_StepY <= 0)
495        throw new IllegalArgumentException("YStep must be a positive number!");
496     
497      // check borders
498      if (!Utils.eq(m_MinX + (m_Width-1)*m_StepX, m_MaxX))
499        throw new IllegalArgumentException(
500            "X axis doesn't match! Provided max: " + m_MaxX
501            + ", calculated max via min and step size: " 
502            + (m_MinX + (m_Width-1)*m_StepX));
503      if (!Utils.eq(m_MinY + (m_Height-1)*m_StepY, m_MaxY))
504        throw new IllegalArgumentException(
505            "Y axis doesn't match! Provided max: " + m_MaxY
506            + ", calculated max via min and step size: " 
507            + (m_MinY + (m_Height-1)*m_StepY));
508    }
509
510    /**
511     * Tests itself against the provided grid object
512     *
513     * @param o         the grid object to compare against
514     * @return          if the two grids have the same setup
515     */
516    public boolean equals(Object o) {
517      boolean   result;
518      Grid      g;
519     
520      g = (Grid) o;
521     
522      result =    (width() == g.width())
523               && (height() == g.height())
524               && (getMinX() == g.getMinX())
525               && (getMinY() == g.getMinY())
526               && (getStepX() == g.getStepX())
527               && (getStepY() == g.getStepY())
528               && getLabelX().equals(g.getLabelX())
529               && getLabelY().equals(g.getLabelY());
530     
531      return result;
532    }
533   
534    /**
535     * returns the left border
536     *
537     * @return          the left border
538     */
539    public double getMinX() {
540      return m_MinX;
541    }
542   
543    /**
544     * returns the right border
545     *
546     * @return          the right border
547     */
548    public double getMaxX() {
549      return m_MaxX;
550    }
551   
552    /**
553     * returns the step size on the X axis
554     *
555     * @return          the step size
556     */
557    public double getStepX() {
558      return m_StepX;
559    }
560   
561    /**
562     * returns the label for the X axis
563     *
564     * @return          the label
565     */
566    public String getLabelX() {
567      return m_LabelX;
568    }
569   
570    /**
571     * returns the bottom border
572     *
573     * @return          the bottom border
574     */
575    public double getMinY() {
576      return m_MinY;
577    }
578   
579    /**
580     * returns the top border
581     *
582     * @return          the top border
583     */
584    public double getMaxY() {
585      return m_MaxY;
586    }
587   
588    /**
589     * returns the step size on the Y axis
590     *
591     * @return          the step size
592     */
593    public double getStepY() {
594      return m_StepY;
595    }
596   
597    /**
598     * returns the label for the Y axis
599     *
600     * @return          the label
601     */
602    public String getLabelY() {
603      return m_LabelY;
604    }
605   
606    /**
607     * returns the number of points in the grid on the Y axis (incl. borders)
608     *
609     * @return          the number of points in the grid on the Y axis
610     */
611    public int height() {
612      return m_Height;
613    }
614   
615    /**
616     * returns the number of points in the grid on the X axis (incl. borders)
617     *
618     * @return          the number of points in the grid on the X axis
619     */
620    public int width() {
621      return m_Width;
622    }
623
624    /**
625     * returns the values at the given point in the grid
626     *
627     * @param x         the x-th point on the X axis
628     * @param y         the y-th point on the Y axis
629     * @return          the value pair at the given position
630     */
631    public PointDouble getValues(int x, int y) {
632      if (x >= width())
633        throw new IllegalArgumentException("Index out of scope on X axis (" + x + " >= " + width() + ")!");
634      if (y >= height())
635        throw new IllegalArgumentException("Index out of scope on Y axis (" + y + " >= " + height() + ")!");
636     
637      return new PointDouble(m_MinX + m_StepX*x, m_MinY + m_StepY*y);
638    }
639
640    /**
641     * returns the closest index pair for the given value pair in the grid.
642     *
643     * @param values    the values to get the indices for
644     * @return          the closest indices in the grid
645     */
646    public PointInt getLocation(PointDouble values) {
647      PointInt  result;
648      int       x;
649      int       y;
650      double    distance;
651      double    currDistance;
652      int       i;
653
654      // determine x
655      x        = 0;
656      distance = m_StepX;
657      for (i = 0; i < width(); i++) {
658        currDistance = StrictMath.abs(values.getX() - getValues(i, 0).getX());
659        if (Utils.sm(currDistance, distance)) {
660          distance = currDistance;
661          x        = i;
662        }
663      }
664     
665      // determine y
666      y        = 0;
667      distance = m_StepY;
668      for (i = 0; i < height(); i++) {
669        currDistance = StrictMath.abs(values.getY() - getValues(0, i).getY());
670        if (Utils.sm(currDistance, distance)) {
671          distance = currDistance;
672          y        = i;
673        }
674      }
675     
676      result = new PointInt(x, y);
677      return result;
678    }
679
680    /**
681     * checks whether the given values are on the border of the grid
682     *
683     * @param values            the values to check
684     * @return                  true if the the values are on the border
685     */
686    public boolean isOnBorder(PointDouble values) {
687      return isOnBorder(getLocation(values));
688    }
689
690    /**
691     * checks whether the given location is on the border of the grid
692     *
693     * @param location          the location to check
694     * @return                  true if the the location is on the border
695     */
696    public boolean isOnBorder(PointInt location) {
697      if (location.getX() == 0)
698        return true;
699      else if (location.getX() == width() - 1)
700        return true;
701      if (location.getY() == 0)
702        return true;
703      else if (location.getY() == height() - 1)
704        return true;
705      else
706        return false;
707    }
708   
709    /**
710     * returns a subgrid with the same step sizes, but different borders
711     *
712     * @param top       the top index
713     * @param left      the left index
714     * @param bottom    the bottom index
715     * @param right     the right index
716     * @return          the Sub-Grid
717     */
718    public Grid subgrid(int top, int left, int bottom, int right) {
719      return new Grid(
720                   getValues(left, top).getX(), getValues(right, top).getX(), getStepX(), getLabelX(),
721                   getValues(left, bottom).getY(), getValues(left, top).getY(), getStepY(), getLabelY());
722    }
723   
724    /**
725     * returns an extended grid that encompasses the given point (won't be on
726     * the border of the grid).
727     *
728     * @param values    the point that the grid should contain
729     * @return          the extended grid
730     */
731    public Grid extend(PointDouble values) {
732      double    minX;
733      double    maxX;
734      double    minY;
735      double    maxY;
736      double    distance;
737      Grid      result;
738     
739      // left
740      if (Utils.smOrEq(values.getX(), getMinX())) {
741        distance = getMinX() - values.getX();
742        // exactly on grid point?
743        if (Utils.eq(distance, 0))
744          minX = getMinX() - getStepX() * (StrictMath.round(distance / getStepX()) + 1);
745        else
746          minX = getMinX() - getStepX() * (StrictMath.round(distance / getStepX()));
747      }
748      else {
749        minX = getMinX();
750      }
751     
752      // right
753      if (Utils.grOrEq(values.getX(), getMaxX())) {
754        distance = values.getX() - getMaxX();
755        // exactly on grid point?
756        if (Utils.eq(distance, 0))
757          maxX = getMaxX() + getStepX() * (StrictMath.round(distance / getStepX()) + 1);
758        else
759          maxX = getMaxX() + getStepX() * (StrictMath.round(distance / getStepX()));
760      }
761      else {
762        maxX = getMaxX();
763      }
764     
765      // bottom
766      if (Utils.smOrEq(values.getY(), getMinY())) {
767        distance = getMinY() - values.getY();
768        // exactly on grid point?
769        if (Utils.eq(distance, 0))
770          minY = getMinY() - getStepY() * (StrictMath.round(distance / getStepY()) + 1);
771        else
772          minY = getMinY() - getStepY() * (StrictMath.round(distance / getStepY()));
773      }
774      else {
775        minY = getMinY();
776      }
777     
778      // top
779      if (Utils.grOrEq(values.getY(), getMaxY())) {
780        distance = values.getY() - getMaxY();
781        // exactly on grid point?
782        if (Utils.eq(distance, 0))
783          maxY = getMaxY() + getStepY() * (StrictMath.round(distance / getStepY()) + 1);
784        else
785          maxY = getMaxY() + getStepY() * (StrictMath.round(distance / getStepY()));
786      }
787      else {
788        maxY = getMaxY();
789      }
790     
791      result = new Grid(minX, maxX, getStepX(), getLabelX(), minY, maxY, getStepY(), getLabelY());
792     
793      // did the grid really extend?
794      if (equals(result))
795        throw new IllegalStateException("Grid extension failed!");
796     
797      return result;
798    }
799   
800    /**
801     * returns an Enumeration over all pairs in the given row
802     *
803     * @param y         the row to retrieve
804     * @return          an Enumeration over all pairs
805     * @see #getValues(int, int)
806     */
807    public Enumeration<PointDouble> row(int y) {
808      Vector    result;
809      int       i;
810     
811      result = new Vector();
812     
813      for (i = 0; i < width(); i++)
814        result.add(getValues(i, y));
815     
816      return result.elements();
817    }
818   
819    /**
820     * returns an Enumeration over all pairs in the given column
821     *
822     * @param x         the column to retrieve
823     * @return          an Enumeration over all pairs
824     * @see #getValues(int, int)
825     */
826    public Enumeration<PointDouble> column(int x) {
827      Vector    result;
828      int       i;
829     
830      result = new Vector();
831     
832      for (i = 0; i < height(); i++)
833        result.add(getValues(x, i));
834     
835      return result.elements();
836    }
837   
838    /**
839     * returns a string representation of the grid
840     *
841     * @return a string representation
842     */
843    public String toString() {
844      String    result;
845     
846      result  = "X: " + m_MinX + " - " + m_MaxX + ", Step " + m_StepX;
847      if (m_LabelX.length() != 0)
848        result += " (" + m_LabelX + ")";
849      result += "\n";
850     
851      result += "Y: " + m_MinY + " - " + m_MaxY + ", Step " + m_StepY;
852      if (m_LabelY.length() != 0)
853        result += " (" + m_LabelY + ")";
854      result += "\n";
855
856      result += "Dimensions (Rows x Columns): " + height() + " x " + width();
857     
858      return result;
859    }
860   
861    /**
862     * Returns the revision string.
863     *
864     * @return          the revision
865     */
866    public String getRevision() {
867      return RevisionUtils.extract("$Revision: 5928 $");
868    }
869  }
870 
871 
872  /**
873   * A helper class for storing the performance of a values-pair.
874   * Can be sorted with the PerformanceComparator class.
875   *
876   * @see PerformanceComparator
877   */
878  protected class Performance
879    implements Serializable, RevisionHandler {
880
881    /** for serialization */
882    private static final long serialVersionUID = -4374706475277588755L;
883   
884    /** the value pair the classifier was built with */
885    protected PointDouble m_Values;
886   
887    /** the Correlation coefficient */
888    protected double m_CC;
889   
890    /** the Root mean squared error */
891    protected double m_RMSE;
892   
893    /** the Root relative squared error */
894    protected double m_RRSE;
895   
896    /** the Mean absolute error */
897    protected double m_MAE;
898   
899    /** the Relative absolute error */
900    protected double m_RAE;
901   
902    /** the Accuracy */
903    protected double m_ACC;
904   
905    /** the kappa value */
906    protected double m_Kappa;
907   
908    /**
909     * initializes the performance container
910     *
911     * @param values            the values-pair
912     * @param evaluation        the evaluation to extract the performance
913     *                          measures from
914     * @throws Exception        if retrieving of measures fails
915     */
916    public Performance(PointDouble values, Evaluation evaluation) throws Exception {
917      super();
918     
919      m_Values = values;
920     
921      m_RMSE  = evaluation.rootMeanSquaredError();
922      m_RRSE  = evaluation.rootRelativeSquaredError();
923      m_MAE   = evaluation.meanAbsoluteError();
924      m_RAE   = evaluation.relativeAbsoluteError();
925
926      try {
927        m_CC = evaluation.correlationCoefficient();
928      }
929      catch (Exception e) {
930        m_CC = Double.NaN;
931      }
932      try {
933        m_ACC = evaluation.pctCorrect();
934      }
935      catch (Exception e) {
936        m_ACC = Double.NaN;
937      }
938      try {
939        m_Kappa = evaluation.kappa();
940      }
941      catch (Exception e) {
942        m_Kappa = Double.NaN;
943      }
944    }
945   
946    /**
947     * returns the performance measure
948     *
949     * @param evaluation        the type of measure to return
950     * @return                  the performance measure
951     */
952    public double getPerformance(int evaluation) {
953      double    result;
954     
955      result = Double.NaN;
956     
957      switch (evaluation) {
958        case EVALUATION_CC:
959          result = m_CC;
960          break;
961        case EVALUATION_RMSE:
962          result = m_RMSE;
963          break;
964        case EVALUATION_RRSE:
965          result = m_RRSE;
966          break;
967        case EVALUATION_MAE:
968          result = m_MAE;
969          break;
970        case EVALUATION_RAE:
971          result = m_RAE;
972          break;
973        case EVALUATION_COMBINED:
974          result = (1 - StrictMath.abs(m_CC)) + m_RRSE + m_RAE;
975          break;
976        case EVALUATION_ACC:
977          result = m_ACC;
978          break;
979        case EVALUATION_KAPPA:
980          result = m_Kappa;
981          break;
982        default:
983          throw new IllegalArgumentException("Evaluation type '" + evaluation + "' not supported!");
984      }
985     
986      return result;
987    }
988   
989    /**
990     * returns the values-pair for this performance
991     *
992     * @return the values-pair
993     */
994    public PointDouble getValues() {
995      return m_Values;
996    }
997   
998    /**
999     * returns a string representation of this performance object
1000     *
1001     * @param evaluation        the type of performance to return
1002     * @return                  a string representation
1003     */
1004    public String toString(int evaluation) {
1005      String    result;
1006     
1007      result =   "Performance (" + getValues() + "): " 
1008               + getPerformance(evaluation) 
1009               + " (" + new SelectedTag(evaluation, TAGS_EVALUATION) + ")";
1010     
1011      return result;
1012    }
1013   
1014    /**
1015     * returns a Gnuplot string of this performance object
1016     *
1017     * @param evaluation        the type of performance to return
1018     * @return                  the gnuplot string (x, y, z)
1019     */
1020    public String toGnuplot(int evaluation) {
1021      String    result;
1022     
1023      result =   getValues().getX() + "\t" 
1024               + getValues().getY() + "\t"
1025               + getPerformance(evaluation);
1026     
1027      return result;
1028    }
1029   
1030    /**
1031     * returns a string representation of this performance object
1032     *
1033     * @return a string representation
1034     */
1035    public String toString() {
1036      String    result;
1037      int       i;
1038     
1039      result = "Performance (" + getValues() + "): ";
1040     
1041      for (i = 0; i < TAGS_EVALUATION.length; i++) {
1042        if (i > 0)
1043          result += ", ";
1044        result +=   getPerformance(TAGS_EVALUATION[i].getID()) 
1045                  + " (" + new SelectedTag(TAGS_EVALUATION[i].getID(), TAGS_EVALUATION) + ")";
1046      }
1047     
1048      return result;
1049    }
1050   
1051    /**
1052     * Returns the revision string.
1053     *
1054     * @return          the revision
1055     */
1056    public String getRevision() {
1057      return RevisionUtils.extract("$Revision: 5928 $");
1058    }
1059  }
1060 
1061  /**
1062   * A concrete Comparator for the Performance class.
1063   *
1064   * @see Performance
1065   */
1066  protected class PerformanceComparator
1067    implements Comparator<Performance>, Serializable, RevisionHandler {
1068   
1069    /** for serialization */
1070    private static final long serialVersionUID = 6507592831825393847L;
1071   
1072    /** the performance measure to use for comparison
1073     * @see GridSearch#TAGS_EVALUATION */
1074    protected int m_Evaluation;
1075   
1076    /**
1077     * initializes the comparator with the given performance measure
1078     *
1079     * @param evaluation        the performance measure to use
1080     * @see GridSearch#TAGS_EVALUATION
1081     */
1082    public PerformanceComparator(int evaluation) {
1083      super();
1084     
1085      m_Evaluation = evaluation;
1086    }
1087   
1088    /**
1089     * returns the performance measure that's used to compare the objects
1090     *
1091     * @return the performance measure
1092     * @see GridSearch#TAGS_EVALUATION
1093     */
1094    public int getEvaluation() {
1095      return m_Evaluation;
1096    }
1097   
1098    /**
1099     * Compares its two arguments for order. Returns a negative integer,
1100     * zero, or a positive integer as the first argument is less than,
1101     * equal to, or greater than the second.
1102     *
1103     * @param o1        the first performance
1104     * @param o2        the second performance
1105     * @return          the order
1106     */
1107    public int compare(Performance o1, Performance o2) {
1108      int       result;
1109      double    p1;
1110      double    p2;
1111     
1112      p1 = o1.getPerformance(getEvaluation());
1113      p2 = o2.getPerformance(getEvaluation());
1114     
1115      if (Utils.sm(p1, p2))
1116        result = -1;
1117      else if (Utils.gr(p1, p2))
1118        result = 1;
1119      else
1120        result = 0;
1121       
1122      // only correlation coefficient/accuracy/kappa obey to this order, for the
1123      // errors (and the combination of all three), the smaller the number the
1124      // better -> hence invert them
1125      if (    (getEvaluation() != EVALUATION_CC) 
1126           && (getEvaluation() != EVALUATION_ACC) 
1127           && (getEvaluation() != EVALUATION_KAPPA) )
1128        result = -result;
1129       
1130      return result;
1131    }
1132   
1133    /**
1134     * Indicates whether some other object is "equal to" this Comparator.
1135     *
1136     * @param obj       the object to compare with
1137     * @return          true if the same evaluation type is used
1138     */
1139    public boolean equals(Object obj) {
1140      if (!(obj instanceof PerformanceComparator))
1141        throw new IllegalArgumentException("Must be PerformanceComparator!");
1142     
1143      return (m_Evaluation == ((PerformanceComparator) obj).m_Evaluation);
1144    }
1145   
1146    /**
1147     * Returns the revision string.
1148     *
1149     * @return          the revision
1150     */
1151    public String getRevision() {
1152      return RevisionUtils.extract("$Revision: 5928 $");
1153    }
1154  }
1155 
1156  /**
1157   * Generates a 2-dim array for the performances from a grid for a certain
1158   * type. x-min/y-min is in the bottom-left corner, i.e., getTable()[0][0]
1159   * returns the performance for the x-min/y-max pair.
1160   * <pre>
1161   * x-min     x-max
1162   * |-------------|
1163   *                - y-max
1164   *                |
1165   *                |
1166   *                - y-min
1167   * </pre>
1168   */
1169  protected class PerformanceTable 
1170    implements Serializable, RevisionHandler {
1171   
1172    /** for serialization */
1173    private static final long serialVersionUID = 5486491313460338379L;
1174
1175    /** the corresponding grid */
1176    protected Grid m_Grid;
1177   
1178    /** the performances */
1179    protected Vector<Performance> m_Performances;
1180   
1181    /** the type of performance the table was generated for */
1182    protected int m_Type;
1183   
1184    /** the table with the values */
1185    protected double[][] m_Table;
1186   
1187    /** the minimum performance */
1188    protected double m_Min;
1189   
1190    /** the maximum performance */
1191    protected double m_Max;
1192   
1193    /**
1194     * initializes the table
1195     *
1196     * @param grid              the underlying grid
1197     * @param performances      the performances
1198     * @param type              the type of performance
1199     */
1200    public PerformanceTable(Grid grid, Vector<Performance> performances, int type) {
1201      super();
1202     
1203      m_Grid         = grid;
1204      m_Type         = type;
1205      m_Performances = performances;
1206     
1207      generate();
1208    }
1209   
1210    /**
1211     * generates the table
1212     */
1213    protected void generate() {
1214      Performance       perf;
1215      int               i;
1216      PointInt          location;
1217     
1218      m_Table = new double[getGrid().height()][getGrid().width()];
1219      m_Min   = 0;
1220      m_Max   = 0;
1221     
1222      for (i = 0; i < getPerformances().size(); i++) {
1223        perf     = (Performance) getPerformances().get(i);
1224        location = getGrid().getLocation(perf.getValues());
1225        m_Table[getGrid().height() - (int) location.getY() - 1][(int) location.getX()] = perf.getPerformance(getType());
1226       
1227        // determine min/max
1228        if (i == 0) {
1229          m_Min = perf.getPerformance(m_Type);
1230          m_Max = m_Min;
1231        }
1232        else {
1233          if (perf.getPerformance(m_Type) < m_Min)
1234            m_Min = perf.getPerformance(m_Type);
1235          if (perf.getPerformance(m_Type) > m_Max)
1236            m_Max = perf.getPerformance(m_Type);
1237        }
1238      }
1239    }
1240   
1241    /**
1242     * returns the corresponding grid
1243     *
1244     * @return          the underlying grid
1245     */
1246    public Grid getGrid() {
1247      return m_Grid;
1248    }
1249
1250    /**
1251     * returns the underlying performances
1252     *
1253     * @return          the underlying performances
1254     */
1255    public Vector<Performance> getPerformances() {
1256      return m_Performances;
1257    }
1258   
1259    /**
1260     * returns the type of performance
1261     *
1262     * @return          the type of performance
1263     */
1264    public int getType() {
1265      return m_Type;
1266    }
1267   
1268    /**
1269     * returns the generated table
1270     *
1271     * @return          the performance table
1272     * @see             #m_Table
1273     * @see             #generate()
1274     */
1275    public double[][] getTable() {
1276      return m_Table;
1277    }
1278   
1279    /**
1280     * the minimum performance
1281     *
1282     * @return          the performance
1283     */
1284    public double getMin() {
1285      return m_Min;
1286    }
1287   
1288    /**
1289     * the maximum performance
1290     *
1291     * @return          the performance
1292     */
1293    public double getMax() {
1294      return m_Max;
1295    }
1296   
1297    /**
1298     * returns the table as string
1299     *
1300     * @return          the table as string
1301     */
1302    public String toString() {
1303      String    result;
1304      int       i;
1305      int       n;
1306     
1307      result =   "Table (" 
1308               + new SelectedTag(getType(), TAGS_EVALUATION).getSelectedTag().getReadable() 
1309               + ") - "
1310               + "X: " + getGrid().getLabelX() + ", Y: " + getGrid().getLabelY()
1311               + ":\n";
1312     
1313      for (i = 0; i < getTable().length; i++) {
1314        if (i > 0)
1315          result += "\n";
1316       
1317        for (n = 0; n < getTable()[i].length; n++) {
1318          if (n > 0)
1319            result += ",";
1320          result += getTable()[i][n];
1321        }
1322      }
1323     
1324      return result;
1325    }
1326   
1327    /**
1328     * returns a string containing a gnuplot script+data file
1329     *
1330     * @return          the data in gnuplot format
1331     */
1332    public String toGnuplot() {
1333      StringBuffer      result;
1334      Tag               type;
1335      int               i;
1336     
1337      result = new StringBuffer();
1338      type   = new SelectedTag(getType(), TAGS_EVALUATION).getSelectedTag();
1339     
1340      result.append("Gnuplot (" + type.getReadable() + "):\n");
1341      result.append("# begin 'gridsearch.data'\n");
1342      result.append("# " + type.getReadable() + "\n");
1343      for (i = 0; i < getPerformances().size(); i++)
1344        result.append(getPerformances().get(i).toGnuplot(type.getID()) + "\n");
1345      result.append("# end 'gridsearch.data'\n\n");
1346     
1347      result.append("# begin 'gridsearch.plot'\n");
1348      result.append("# " + type.getReadable() + "\n");
1349      result.append("set data style lines\n");
1350      result.append("set contour base\n");
1351      result.append("set surface\n");
1352      result.append("set title '" + m_Data.relationName() + "'\n");
1353      result.append("set xrange [" + getGrid().getMinX() + ":" + getGrid().getMaxX() + "]\n");
1354      result.append("set xlabel 'x (" + getFilter().getClass().getName() + ": " + getXProperty() + ")'\n");
1355      result.append("set yrange [" + getGrid().getMinY() + ":" + getGrid().getMaxY() + "]\n");
1356      result.append("set ylabel 'y - (" + getClassifier().getClass().getName() + ": " + getYProperty() + ")'\n");
1357      result.append("set zrange [" + (getMin() - (getMax() - getMin())*0.1) + ":" + (getMax() + (getMax() - getMin())*0.1) + "]\n");
1358      result.append("set zlabel 'z - " + type.getReadable() + "'\n");
1359      result.append("set dgrid3d " + getGrid().height() + "," + getGrid().width() + ",1\n");
1360      result.append("show contour\n");
1361      result.append("splot 'gridsearch.data'\n");
1362      result.append("pause -1\n");
1363      result.append("# end 'gridsearch.plot'");
1364
1365      return result.toString();
1366    }
1367   
1368    /**
1369     * Returns the revision string.
1370     *
1371     * @return          the revision
1372     */
1373    public String getRevision() {
1374      return RevisionUtils.extract("$Revision: 5928 $");
1375    }
1376  }
1377 
1378  /**
1379   * Represents a simple cache for performance objects.
1380   */
1381  protected class PerformanceCache
1382    implements Serializable, RevisionHandler {
1383
1384    /** for serialization */
1385    private static final long serialVersionUID = 5838863230451530252L;
1386   
1387    /** the cache for points in the grid that got calculated */
1388    protected Hashtable m_Cache = new Hashtable();
1389   
1390    /**
1391     * returns the ID string for a cache item
1392     *
1393     * @param cv                the number of folds in the cross-validation
1394     * @param values    the point in the grid
1395     * @return          the ID string
1396     */
1397    protected String getID(int cv, PointDouble values) {
1398      return cv + "\t" + values.getX() + "\t" + values.getY();
1399    }
1400   
1401    /**
1402     * checks whether the point was already calculated ones
1403     *
1404     * @param cv        the number of folds in the cross-validation
1405     * @param values    the point in the grid
1406     * @return          true if the value is already cached
1407     */
1408    public boolean isCached(int cv, PointDouble values) {
1409      return (get(cv, values) != null);
1410    }
1411   
1412    /**
1413     * returns a cached performance object, null if not yet in the cache
1414     *
1415     * @param cv        the number of folds in the cross-validation
1416     * @param values    the point in the grid
1417     * @return          the cached performance item, null if not in cache
1418     */
1419    public Performance get(int cv, PointDouble values) {
1420      return (Performance) m_Cache.get(getID(cv, values));
1421    }
1422   
1423    /**
1424     * adds the performance to the cache
1425     *
1426     * @param cv        the number of folds in the cross-validation
1427     * @param p         the performance object to store
1428     */
1429    public void add(int cv, Performance p) {
1430      m_Cache.put(getID(cv, p.getValues()), p);
1431    }
1432   
1433    /**
1434     * returns a string representation of the cache
1435     *
1436     * @return          the string representation of the cache
1437     */
1438    public String toString() {
1439      return m_Cache.toString();
1440    }
1441   
1442    /**
1443     * Returns the revision string.
1444     *
1445     * @return          the revision
1446     */
1447    public String getRevision() {
1448      return RevisionUtils.extract("$Revision: 5928 $");
1449    }
1450  }
1451 
1452  /** for serialization */
1453  private static final long serialVersionUID = -3034773968581595348L;
1454
1455  /** evaluation via: Correlation coefficient */
1456  public static final int EVALUATION_CC = 0;
1457  /** evaluation via: Root mean squared error */
1458  public static final int EVALUATION_RMSE = 1;
1459  /** evaluation via: Root relative squared error */
1460  public static final int EVALUATION_RRSE = 2;
1461  /** evaluation via: Mean absolute error */
1462  public static final int EVALUATION_MAE = 3;
1463  /** evaluation via: Relative absolute error */
1464  public static final int EVALUATION_RAE = 4;
1465  /** evaluation via: Combined = (1-CC) + RRSE + RAE */
1466  public static final int EVALUATION_COMBINED = 5;
1467  /** evaluation via: Accuracy */
1468  public static final int EVALUATION_ACC = 6;
1469  /** evaluation via: kappa statistic */
1470  public static final int EVALUATION_KAPPA = 7;
1471  /** evaluation */
1472  public static final Tag[] TAGS_EVALUATION = {
1473    new Tag(EVALUATION_CC, "CC", "Correlation coefficient"),
1474    new Tag(EVALUATION_RMSE, "RMSE", "Root mean squared error"),
1475    new Tag(EVALUATION_RRSE, "RRSE", "Root relative squared error"),
1476    new Tag(EVALUATION_MAE, "MAE", "Mean absolute error"),
1477    new Tag(EVALUATION_RAE, "RAE", "Root absolute error"),
1478    new Tag(EVALUATION_COMBINED, "COMB", "Combined = (1-abs(CC)) + RRSE + RAE"),
1479    new Tag(EVALUATION_ACC, "ACC", "Accuracy"),
1480    new Tag(EVALUATION_KAPPA, "KAP", "Kappa")
1481  };
1482 
1483  /** row-wise grid traversal */
1484  public static final int TRAVERSAL_BY_ROW = 0;
1485  /** column-wise grid traversal */
1486  public static final int TRAVERSAL_BY_COLUMN = 1;
1487  /** traversal */
1488  public static final Tag[] TAGS_TRAVERSAL = {
1489    new Tag(TRAVERSAL_BY_ROW, "row-wise", "row-wise"),
1490    new Tag(TRAVERSAL_BY_COLUMN, "column-wise", "column-wise")
1491  };
1492
1493  /** the prefix to indicate that the option is for the classifier */
1494  public final static String PREFIX_CLASSIFIER = "classifier.";
1495
1496  /** the prefix to indicate that the option is for the filter */
1497  public final static String PREFIX_FILTER = "filter.";
1498 
1499  /** the Filter */
1500  protected Filter m_Filter;
1501 
1502  /** the Filter with the best setup */
1503  protected Filter m_BestFilter;
1504 
1505  /** the Classifier with the best setup */
1506  protected Classifier m_BestClassifier;
1507
1508  /** the best values */
1509  protected PointDouble m_Values = null;
1510 
1511  /** the type of evaluation */
1512  protected int m_Evaluation = EVALUATION_CC;
1513
1514  /** the Y option to work on (without leading dash, preceding 'classifier.'
1515   * means to set the option for the classifier 'filter.' for the filter) */
1516  protected String m_Y_Property = PREFIX_CLASSIFIER + "ridge";
1517 
1518  /** the minimum of Y */
1519  protected double m_Y_Min = -10;
1520 
1521  /** the maximum of Y */
1522  protected double m_Y_Max = +5;
1523 
1524  /** the step size of Y */
1525  protected double m_Y_Step = 1;
1526 
1527  /** the base for Y */
1528  protected double m_Y_Base = 10;
1529 
1530  /**
1531   * The expression for the Y property. Available parameters for the
1532   * expression:
1533   * <ul>
1534   *   <li>BASE</li>
1535   *   <li>FROM (= min)</li>
1536   *   <li>TO (= max)</li>
1537   *   <li>STEP</li>
1538   *   <li>I - the current value (from 'from' to 'to' with stepsize 'step')</li>
1539   * </ul>
1540   *
1541   * @see MathematicalExpression
1542   * @see MathExpression
1543   */
1544  protected String m_Y_Expression = "pow(BASE,I)";
1545
1546  /** the X option to work on (without leading dash, preceding 'classifier.'
1547   * means to set the option for the classifier 'filter.' for the filter) */
1548  protected String m_X_Property = PREFIX_FILTER + "numComponents";
1549 
1550  /** the minimum of X */
1551  protected double m_X_Min = +5;
1552 
1553  /** the maximum of X */
1554  protected double m_X_Max = +20;
1555 
1556  /** the step size of  */
1557  protected double m_X_Step = 1;
1558 
1559  /** the base for  */
1560  protected double m_X_Base = 10;
1561 
1562  /**
1563   * The expression for the X property. Available parameters for the
1564   * expression:
1565   * <ul>
1566   *   <li>BASE</li>
1567   *   <li>FROM (= min)</li>
1568   *   <li>TO (= max)</li>
1569   *   <li>STEP</li>
1570   *   <li>I - the current value (from 'from' to 'to' with stepsize 'step')</li>
1571   * </ul>
1572   *
1573   * @see MathematicalExpression
1574   * @see MathExpression
1575   */
1576  protected String m_X_Expression = "I";
1577
1578  /** whether the grid can be extended */
1579  protected boolean m_GridIsExtendable = false;
1580 
1581  /** maximum number of grid extensions (-1 means unlimited) */
1582  protected int m_MaxGridExtensions = 3;
1583 
1584  /** the number of extensions performed */
1585  protected int m_GridExtensionsPerformed = 0;
1586
1587  /** the sample size to search the initial grid with */
1588  protected double m_SampleSize = 100;
1589 
1590  /** the traversal */
1591  protected int m_Traversal = TRAVERSAL_BY_COLUMN;
1592
1593  /** the log file to use */
1594  protected File m_LogFile = new File(System.getProperty("user.dir"));
1595 
1596  /** the value-pairs grid */
1597  protected Grid m_Grid;
1598
1599  /** the training data */
1600  protected Instances m_Data;
1601
1602  /** the cache for points in the grid that got calculated */
1603  protected PerformanceCache m_Cache;
1604
1605  /** whether all performances in the grid are the same */
1606  protected boolean m_UniformPerformance = false;
1607 
1608  /**
1609   * the default constructor
1610   */
1611  public GridSearch() {
1612    super();
1613   
1614    // classifier
1615    m_Classifier = new LinearRegression();
1616    ((LinearRegression) m_Classifier).setAttributeSelectionMethod(new SelectedTag(LinearRegression.SELECTION_NONE, LinearRegression.TAGS_SELECTION));
1617    ((LinearRegression) m_Classifier).setEliminateColinearAttributes(false);
1618   
1619    // filter
1620    m_Filter = new PLSFilter();
1621    PLSFilter filter = new PLSFilter();
1622    filter.setPreprocessing(new SelectedTag(PLSFilter.PREPROCESSING_STANDARDIZE, PLSFilter.TAGS_PREPROCESSING));
1623    filter.setReplaceMissing(true);
1624   
1625    try {
1626      m_BestClassifier = AbstractClassifier.makeCopy(m_Classifier);
1627    }
1628    catch (Exception e) {
1629      e.printStackTrace();
1630    }
1631    try {
1632      m_BestFilter = Filter.makeCopy(filter);
1633    }
1634    catch (Exception e) {
1635      e.printStackTrace();
1636    }
1637  }
1638 
1639  /**
1640   * Returns a string describing classifier
1641   *
1642   * @return a description suitable for displaying in the
1643   *         explorer/experimenter gui
1644   */
1645  public String globalInfo() {
1646    return 
1647        "Performs a grid search of parameter pairs for the a classifier "
1648      + "(Y-axis, default is LinearRegression with the \"Ridge\" parameter) "
1649      + "and the PLSFilter (X-axis, \"# of Components\") and chooses the best "
1650      + "pair found for the actual predicting.\n\n"
1651      + "The initial grid is worked on with 2-fold CV to determine the values "
1652      + "of the parameter pairs for the selected type of evaluation (e.g., "
1653      + "accuracy). The best point in the grid is then taken and a 10-fold CV "
1654      + "is performed with the adjacent parameter pairs. If a better pair is "
1655      + "found, then this will act as new center and another 10-fold CV will "
1656      + "be performed (kind of hill-climbing). This process is repeated until "
1657      + "no better pair is found or the best pair is on the border of the grid.\n"
1658      + "In case the best pair is on the border, one can let GridSearch "
1659      + "automatically extend the grid and continue the search. Check out the "
1660      + "properties 'gridIsExtendable' (option '-extend-grid') and "
1661      + "'maxGridExtensions' (option '-max-grid-extensions <num>').\n\n"
1662      + "GridSearch can handle doubles, integers (values are just cast to int) "
1663      + "and booleans (0 is false, otherwise true). float, char and long are "
1664      + "supported as well.\n\n"
1665      + "The best filter/classifier setup can be accessed after the buildClassifier "
1666      + "call via the getBestFilter/getBestClassifier methods.\n"
1667      + "Note on the implementation: after the data has been passed through "
1668      + "the filter, a default NumericCleaner filter is applied to the data in "
1669      + "order to avoid numbers that are getting too small and might produce "
1670      + "NaNs in other schemes.";
1671  }
1672
1673  /**
1674   * String describing default classifier.
1675   *
1676   * @return            the classname of the default classifier
1677   */
1678  protected String defaultClassifierString() {
1679    return LinearRegression.class.getName();
1680  }
1681
1682  /**
1683   * Gets an enumeration describing the available options.
1684   *
1685   * @return an enumeration of all the available options.
1686   */
1687  public Enumeration listOptions(){
1688    Vector              result;
1689    Enumeration         en;
1690    String              desc;
1691    SelectedTag         tag;
1692    int                 i;
1693
1694    result = new Vector();
1695
1696    desc  = "";
1697    for (i = 0; i < TAGS_EVALUATION.length; i++) {
1698      tag = new SelectedTag(TAGS_EVALUATION[i].getID(), TAGS_EVALUATION);
1699      desc  +=   "\t" + tag.getSelectedTag().getIDStr() 
1700               + " = " + tag.getSelectedTag().getReadable()
1701               + "\n";
1702    }
1703    result.addElement(new Option(
1704        "\tDetermines the parameter used for evaluation:\n"
1705        + desc
1706        + "\t(default: " + new SelectedTag(EVALUATION_CC, TAGS_EVALUATION) + ")",
1707        "E", 1, "-E " + Tag.toOptionList(TAGS_EVALUATION)));
1708
1709    result.addElement(new Option(
1710        "\tThe Y option to test (without leading dash).\n"
1711        + "\t(default: " + PREFIX_CLASSIFIER + "ridge)",
1712        "y-property", 1, "-y-property <option>"));
1713
1714    result.addElement(new Option(
1715        "\tThe minimum for Y.\n"
1716        + "\t(default: -10)",
1717        "y-min", 1, "-y-min <num>"));
1718
1719    result.addElement(new Option(
1720        "\tThe maximum for Y.\n"
1721        + "\t(default: +5)",
1722        "y-max", 1, "-y-max <num>"));
1723
1724    result.addElement(new Option(
1725        "\tThe step size for Y.\n"
1726        + "\t(default: 1)",
1727        "y-step", 1, "-y-step <num>"));
1728
1729    result.addElement(new Option(
1730        "\tThe base for Y.\n"
1731        + "\t(default: 10)",
1732        "y-base", 1, "-y-base <num>"));
1733
1734    result.addElement(new Option(
1735        "\tThe expression for Y.\n"
1736        + "\tAvailable parameters:\n"
1737        + "\t\tBASE\n"
1738        + "\t\tFROM\n"
1739        + "\t\tTO\n"
1740        + "\t\tSTEP\n"
1741        + "\t\tI - the current iteration value\n"
1742        + "\t\t(from 'FROM' to 'TO' with stepsize 'STEP')\n"
1743        + "\t(default: 'pow(BASE,I)')",
1744        "y-expression", 1, "-y-expression <expr>"));
1745
1746    result.addElement(new Option(
1747        "\tThe filter to use (on X axis). Full classname of filter to include, \n"
1748        + "\tfollowed by scheme options.\n"
1749        + "\t(default: weka.filters.supervised.attribute.PLSFilter)",
1750        "filter", 1, "-filter <filter specification>"));
1751
1752    result.addElement(new Option(
1753        "\tThe X option to test (without leading dash).\n"
1754        + "\t(default: " + PREFIX_FILTER + "numComponents)",
1755        "x-property", 1, "-x-property <option>"));
1756
1757    result.addElement(new Option(
1758        "\tThe minimum for X.\n"
1759        + "\t(default: +5)",
1760        "x-min", 1, "-x-min <num>"));
1761
1762    result.addElement(new Option(
1763        "\tThe maximum for X.\n"
1764        + "\t(default: +20)",
1765        "x-max", 1, "-x-max <num>"));
1766
1767    result.addElement(new Option(
1768        "\tThe step size for X.\n"
1769        + "\t(default: 1)",
1770        "x-step", 1, "-x-step <num>"));
1771
1772    result.addElement(new Option(
1773        "\tThe base for X.\n"
1774        + "\t(default: 10)",
1775        "x-base", 1, "-x-base <num>"));
1776
1777    result.addElement(new Option(
1778        "\tThe expression for the X value.\n"
1779        + "\tAvailable parameters:\n"
1780        + "\t\tBASE\n"
1781        + "\t\tMIN\n"
1782        + "\t\tMAX\n"
1783        + "\t\tSTEP\n"
1784        + "\t\tI - the current iteration value\n"
1785        + "\t\t(from 'FROM' to 'TO' with stepsize 'STEP')\n"
1786        + "\t(default: 'pow(BASE,I)')",
1787        "x-expression", 1, "-x-expression <expr>"));
1788
1789    result.addElement(new Option(
1790        "\tWhether the grid can be extended.\n"
1791        + "\t(default: no)",
1792        "extend-grid", 0, "-extend-grid"));
1793
1794    result.addElement(new Option(
1795        "\tThe maximum number of grid extensions (-1 is unlimited).\n"
1796        + "\t(default: 3)",
1797        "max-grid-extensions", 1, "-max-grid-extensions <num>"));
1798
1799    result.addElement(new Option(
1800        "\tThe size (in percent) of the sample to search the inital grid with.\n"
1801        + "\t(default: 100)",
1802        "sample-size", 1, "-sample-size <num>"));
1803
1804    result.addElement(new Option(
1805        "\tThe type of traversal for the grid.\n"
1806        + "\t(default: " + new SelectedTag(TRAVERSAL_BY_COLUMN, TAGS_TRAVERSAL) + ")",
1807        "traversal", 1, "-traversal " + Tag.toOptionList(TAGS_TRAVERSAL)));
1808
1809    result.addElement(new Option(
1810        "\tThe log file to log the messages to.\n"
1811        + "\t(default: none)",
1812        "log-file", 1, "-log-file <filename>"));
1813
1814    en = super.listOptions();
1815    while (en.hasMoreElements())
1816      result.addElement(en.nextElement());
1817
1818    if (getFilter() instanceof OptionHandler) {
1819      result.addElement(new Option(
1820          "",
1821          "", 0, "\nOptions specific to filter "
1822          + getFilter().getClass().getName() + " ('-filter'):"));
1823     
1824      en = ((OptionHandler) getFilter()).listOptions();
1825      while (en.hasMoreElements())
1826        result.addElement(en.nextElement());
1827    }
1828
1829
1830    return result.elements();
1831  }
1832 
1833  /**
1834   * returns the options of the current setup
1835   *
1836   * @return            the current options
1837   */
1838  public String[] getOptions(){
1839    int         i;
1840    Vector      result;
1841    String[]    options;
1842
1843    result = new Vector();
1844
1845    result.add("-E");
1846    result.add("" + getEvaluation());
1847
1848    result.add("-y-property");
1849    result.add("" + getYProperty());
1850
1851    result.add("-y-min");
1852    result.add("" + getYMin());
1853
1854    result.add("-y-max");
1855    result.add("" + getYMax());
1856
1857    result.add("-y-step");
1858    result.add("" + getYStep());
1859
1860    result.add("-y-base");
1861    result.add("" + getYBase());
1862
1863    result.add("-y-expression");
1864    result.add("" + getYExpression());
1865
1866    result.add("-filter");
1867    if (getFilter() instanceof OptionHandler)
1868      result.add(
1869            getFilter().getClass().getName() 
1870          + " " 
1871          + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()));
1872    else
1873      result.add(
1874          getFilter().getClass().getName());
1875
1876    result.add("-x-property");
1877    result.add("" + getXProperty());
1878
1879    result.add("-x-min");
1880    result.add("" + getXMin());
1881
1882    result.add("-x-max");
1883    result.add("" + getXMax());
1884
1885    result.add("-x-step");
1886    result.add("" + getXStep());
1887
1888    result.add("-x-base");
1889    result.add("" + getXBase());
1890
1891    result.add("-x-expression");
1892    result.add("" + getXExpression());
1893
1894    if (getGridIsExtendable()) {
1895      result.add("-extend-grid");
1896      result.add("-max-grid-extensions");
1897      result.add("" + getMaxGridExtensions());
1898    }
1899   
1900    result.add("-sample-size");
1901    result.add("" + getSampleSizePercent());
1902
1903    result.add("-traversal");
1904    result.add("" + getTraversal());
1905
1906    result.add("-log-file");
1907    result.add("" + getLogFile());
1908
1909    options = super.getOptions();
1910    for (i = 0; i < options.length; i++)
1911      result.add(options[i]);
1912
1913    return (String[]) result.toArray(new String[result.size()]);         
1914  }
1915
1916  /**
1917   * Parses the options for this object. <p/>
1918   *
1919   <!-- options-start -->
1920   * Valid options are: <p/>
1921   *
1922   * <pre> -E &lt;CC|RMSE|RRSE|MAE|RAE|COMB|ACC|KAP&gt;
1923   *  Determines the parameter used for evaluation:
1924   *  CC = Correlation coefficient
1925   *  RMSE = Root mean squared error
1926   *  RRSE = Root relative squared error
1927   *  MAE = Mean absolute error
1928   *  RAE = Root absolute error
1929   *  COMB = Combined = (1-abs(CC)) + RRSE + RAE
1930   *  ACC = Accuracy
1931   *  KAP = Kappa
1932   *  (default: CC)</pre>
1933   *
1934   * <pre> -y-property &lt;option&gt;
1935   *  The Y option to test (without leading dash).
1936   *  (default: classifier.ridge)</pre>
1937   *
1938   * <pre> -y-min &lt;num&gt;
1939   *  The minimum for Y.
1940   *  (default: -10)</pre>
1941   *
1942   * <pre> -y-max &lt;num&gt;
1943   *  The maximum for Y.
1944   *  (default: +5)</pre>
1945   *
1946   * <pre> -y-step &lt;num&gt;
1947   *  The step size for Y.
1948   *  (default: 1)</pre>
1949   *
1950   * <pre> -y-base &lt;num&gt;
1951   *  The base for Y.
1952   *  (default: 10)</pre>
1953   *
1954   * <pre> -y-expression &lt;expr&gt;
1955   *  The expression for Y.
1956   *  Available parameters:
1957   *   BASE
1958   *   FROM
1959   *   TO
1960   *   STEP
1961   *   I - the current iteration value
1962   *   (from 'FROM' to 'TO' with stepsize 'STEP')
1963   *  (default: 'pow(BASE,I)')</pre>
1964   *
1965   * <pre> -filter &lt;filter specification&gt;
1966   *  The filter to use (on X axis). Full classname of filter to include,
1967   *  followed by scheme options.
1968   *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
1969   *
1970   * <pre> -x-property &lt;option&gt;
1971   *  The X option to test (without leading dash).
1972   *  (default: filter.numComponents)</pre>
1973   *
1974   * <pre> -x-min &lt;num&gt;
1975   *  The minimum for X.
1976   *  (default: +5)</pre>
1977   *
1978   * <pre> -x-max &lt;num&gt;
1979   *  The maximum for X.
1980   *  (default: +20)</pre>
1981   *
1982   * <pre> -x-step &lt;num&gt;
1983   *  The step size for X.
1984   *  (default: 1)</pre>
1985   *
1986   * <pre> -x-base &lt;num&gt;
1987   *  The base for X.
1988   *  (default: 10)</pre>
1989   *
1990   * <pre> -x-expression &lt;expr&gt;
1991   *  The expression for the X value.
1992   *  Available parameters:
1993   *   BASE
1994   *   MIN
1995   *   MAX
1996   *   STEP
1997   *   I - the current iteration value
1998   *   (from 'FROM' to 'TO' with stepsize 'STEP')
1999   *  (default: 'pow(BASE,I)')</pre>
2000   *
2001   * <pre> -extend-grid
2002   *  Whether the grid can be extended.
2003   *  (default: no)</pre>
2004   *
2005   * <pre> -max-grid-extensions &lt;num&gt;
2006   *  The maximum number of grid extensions (-1 is unlimited).
2007   *  (default: 3)</pre>
2008   *
2009   * <pre> -sample-size &lt;num&gt;
2010   *  The size (in percent) of the sample to search the inital grid with.
2011   *  (default: 100)</pre>
2012   *
2013   * <pre> -traversal &lt;ROW-WISE|COLUMN-WISE&gt;
2014   *  The type of traversal for the grid.
2015   *  (default: COLUMN-WISE)</pre>
2016   *
2017   * <pre> -log-file &lt;filename&gt;
2018   *  The log file to log the messages to.
2019   *  (default: none)</pre>
2020   *
2021   * <pre> -S &lt;num&gt;
2022   *  Random number seed.
2023   *  (default 1)</pre>
2024   *
2025   * <pre> -D
2026   *  If set, classifier is run in debug mode and
2027   *  may output additional info to the console</pre>
2028   *
2029   * <pre> -W
2030   *  Full name of base classifier.
2031   *  (default: weka.classifiers.functions.LinearRegression)</pre>
2032   *
2033   * <pre>
2034   * Options specific to classifier weka.classifiers.functions.LinearRegression:
2035   * </pre>
2036   *
2037   * <pre> -D
2038   *  Produce debugging output.
2039   *  (default no debugging output)</pre>
2040   *
2041   * <pre> -S &lt;number of selection method&gt;
2042   *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
2043   *  (default 0 = M5' method)</pre>
2044   *
2045   * <pre> -C
2046   *  Do not try to eliminate colinear attributes.
2047   * </pre>
2048   *
2049   * <pre> -R &lt;double&gt;
2050   *  Set ridge parameter (default 1.0e-8).
2051   * </pre>
2052   *
2053   * <pre>
2054   * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
2055   * </pre>
2056   *
2057   * <pre> -D
2058   *  Turns on output of debugging information.</pre>
2059   *
2060   * <pre> -C &lt;num&gt;
2061   *  The number of components to compute.
2062   *  (default: 20)</pre>
2063   *
2064   * <pre> -U
2065   *  Updates the class attribute as well.
2066   *  (default: off)</pre>
2067   *
2068   * <pre> -M
2069   *  Turns replacing of missing values on.
2070   *  (default: off)</pre>
2071   *
2072   * <pre> -A &lt;SIMPLS|PLS1&gt;
2073   *  The algorithm to use.
2074   *  (default: PLS1)</pre>
2075   *
2076   * <pre> -P &lt;none|center|standardize&gt;
2077   *  The type of preprocessing that is applied to the data.
2078   *  (default: center)</pre>
2079   *
2080   <!-- options-end -->
2081   *
2082   * @param options     the options to use
2083   * @throws Exception  if setting of options fails
2084   */
2085  public void setOptions(String[] options) throws Exception {
2086    String      tmpStr;
2087    String[]    tmpOptions;
2088
2089    tmpStr = Utils.getOption('E', options);
2090    if (tmpStr.length() != 0)
2091      setEvaluation(new SelectedTag(tmpStr, TAGS_EVALUATION));
2092    else
2093      setEvaluation(new SelectedTag(EVALUATION_CC, TAGS_EVALUATION));
2094   
2095    tmpStr = Utils.getOption("y-property", options);
2096    if (tmpStr.length() != 0)
2097      setYProperty(tmpStr);
2098    else
2099      setYProperty(PREFIX_CLASSIFIER + "ridge");
2100   
2101    tmpStr = Utils.getOption("y-min", options);
2102    if (tmpStr.length() != 0)
2103      setYMin(Double.parseDouble(tmpStr));
2104    else
2105      setYMin(-10);
2106   
2107    tmpStr = Utils.getOption("y-max", options);
2108    if (tmpStr.length() != 0)
2109      setYMax(Double.parseDouble(tmpStr));
2110    else
2111      setYMax(10);
2112   
2113    tmpStr = Utils.getOption("y-step", options);
2114    if (tmpStr.length() != 0)
2115      setYStep(Double.parseDouble(tmpStr));
2116    else
2117      setYStep(1);
2118   
2119    tmpStr = Utils.getOption("y-base", options);
2120    if (tmpStr.length() != 0)
2121      setYBase(Double.parseDouble(tmpStr));
2122    else
2123      setYBase(10);
2124   
2125    tmpStr = Utils.getOption("y-expression", options);
2126    if (tmpStr.length() != 0)
2127      setYExpression(tmpStr);
2128    else
2129      setYExpression("pow(BASE,I)");
2130   
2131    tmpStr     = Utils.getOption("filter", options);
2132    tmpOptions = Utils.splitOptions(tmpStr);
2133    if (tmpOptions.length != 0) {
2134      tmpStr        = tmpOptions[0];
2135      tmpOptions[0] = "";
2136      setFilter((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
2137    }
2138   
2139    tmpStr = Utils.getOption("x-property", options);
2140    if (tmpStr.length() != 0)
2141      setXProperty(tmpStr);
2142    else
2143      setXProperty(PREFIX_FILTER + "filters[0].kernel.gamma");
2144   
2145    tmpStr = Utils.getOption("x-min", options);
2146    if (tmpStr.length() != 0)
2147      setXMin(Double.parseDouble(tmpStr));
2148    else
2149      setXMin(-10);
2150   
2151    tmpStr = Utils.getOption("x-max", options);
2152    if (tmpStr.length() != 0)
2153      setXMax(Double.parseDouble(tmpStr));
2154    else
2155      setXMax(10);
2156   
2157    tmpStr = Utils.getOption("x-step", options);
2158    if (tmpStr.length() != 0)
2159      setXStep(Double.parseDouble(tmpStr));
2160    else
2161      setXStep(1);
2162   
2163    tmpStr = Utils.getOption("x-base", options);
2164    if (tmpStr.length() != 0)
2165      setXBase(Double.parseDouble(tmpStr));
2166    else
2167      setXBase(10);
2168   
2169    tmpStr = Utils.getOption("x-expression", options);
2170    if (tmpStr.length() != 0)
2171      setXExpression(tmpStr);
2172    else
2173      setXExpression("pow(BASE,I)");
2174   
2175    setGridIsExtendable(Utils.getFlag("extend-grid", options));
2176    if (getGridIsExtendable()) {
2177      tmpStr = Utils.getOption("max-grid-extensions", options);
2178      if (tmpStr.length() != 0)
2179        setMaxGridExtensions(Integer.parseInt(tmpStr));
2180      else
2181        setMaxGridExtensions(3);
2182    }
2183   
2184    tmpStr = Utils.getOption("sample-size", options);
2185    if (tmpStr.length() != 0)
2186      setSampleSizePercent(Double.parseDouble(tmpStr));
2187    else
2188      setSampleSizePercent(100);
2189   
2190    tmpStr = Utils.getOption("traversal", options);
2191    if (tmpStr.length() != 0)
2192      setTraversal(new SelectedTag(tmpStr, TAGS_TRAVERSAL));
2193    else
2194      setTraversal(new SelectedTag(TRAVERSAL_BY_ROW, TAGS_TRAVERSAL));
2195   
2196    tmpStr = Utils.getOption("log-file", options);
2197    if (tmpStr.length() != 0)
2198      setLogFile(new File(tmpStr));
2199    else
2200      setLogFile(new File(System.getProperty("user.dir")));
2201   
2202    super.setOptions(options);
2203  }
2204
2205  /**
2206   * Set the base learner.
2207   *
2208   * @param newClassifier       the classifier to use.
2209   */
2210  public void setClassifier(Classifier newClassifier) {
2211    boolean     numeric;
2212    boolean     nominal;
2213   
2214    Capabilities cap = newClassifier.getCapabilities();
2215
2216    numeric =    cap.handles(Capability.NUMERIC_CLASS) 
2217              || cap.hasDependency(Capability.NUMERIC_CLASS);
2218   
2219    nominal =    cap.handles(Capability.NOMINAL_CLASS)
2220              || cap.hasDependency(Capability.NOMINAL_CLASS)
2221              || cap.handles(Capability.BINARY_CLASS)
2222              || cap.hasDependency(Capability.BINARY_CLASS)
2223              || cap.handles(Capability.UNARY_CLASS)
2224              || cap.hasDependency(Capability.UNARY_CLASS);
2225   
2226    if ((m_Evaluation == EVALUATION_CC) && !numeric)
2227      throw new IllegalArgumentException(
2228          "Classifier needs to handle numeric class for chosen type of evaluation!");
2229
2230    if (((m_Evaluation == EVALUATION_ACC) || (m_Evaluation == EVALUATION_KAPPA)) && !nominal)
2231      throw new IllegalArgumentException(
2232          "Classifier needs to handle nominal class for chosen type of evaluation!");
2233   
2234    super.setClassifier(newClassifier);
2235   
2236    try {
2237      m_BestClassifier = AbstractClassifier.makeCopy(m_Classifier);
2238    }
2239    catch (Exception e) {
2240      e.printStackTrace();
2241    }
2242  }
2243 
2244  /**
2245   * Returns the tip text for this property
2246   *
2247   * @return            tip text for this property suitable for
2248   *                    displaying in the explorer/experimenter gui
2249   */
2250  public String filterTipText() {
2251    return "The filter to be used (only used for setup).";
2252  }
2253
2254  /**
2255   * Set the kernel filter (only used for setup).
2256   *
2257   * @param value       the kernel filter.
2258   */
2259  public void setFilter(Filter value) {
2260    m_Filter = value;
2261
2262    try {
2263      m_BestFilter = Filter.makeCopy(m_Filter);
2264    }
2265    catch (Exception e) {
2266      e.printStackTrace();
2267    }
2268  }
2269
2270  /**
2271   * Get the kernel filter.
2272   *
2273   * @return            the kernel filter
2274   */
2275  public Filter getFilter() {
2276    return m_Filter;
2277  }
2278
2279  /**
2280   * Returns the tip text for this property
2281   *
2282   * @return            tip text for this property suitable for
2283   *                    displaying in the explorer/experimenter gui
2284   */
2285  public String evaluationTipText() {
2286    return 
2287        "Sets the criterion for evaluating the classifier performance and "
2288      + "choosing the best one.";
2289  }
2290
2291  /**
2292   * Sets the criterion to use for evaluating the classifier performance.
2293   *
2294   * @param value       .the evaluation criterion
2295   */
2296  public void setEvaluation(SelectedTag value) {
2297    if (value.getTags() == TAGS_EVALUATION) {
2298      m_Evaluation = value.getSelectedTag().getID();
2299    }
2300  }
2301
2302  /**
2303   * Gets the criterion used for evaluating the classifier performance.
2304   *
2305   * @return            the current evaluation criterion.
2306   */
2307  public SelectedTag getEvaluation() {
2308    return new SelectedTag(m_Evaluation, TAGS_EVALUATION);
2309  }
2310 
2311  /**
2312   * Returns the tip text for this property
2313   *
2314   * @return            tip text for this property suitable for
2315   *                    displaying in the explorer/experimenter gui
2316   */
2317  public String YPropertyTipText() {
2318    return "The Y property to test (normally the classifier).";
2319  }
2320
2321  /**
2322   * Get the Y property (normally the classifier).
2323   *
2324   * @return            Value of the property.
2325   */
2326  public String getYProperty() {
2327    return m_Y_Property;
2328  }
2329 
2330  /**
2331   * Set the Y property (normally the classifier).
2332   *
2333   * @param value       the Y property.
2334   */
2335  public void setYProperty(String value) {
2336    m_Y_Property = value;
2337  }
2338 
2339  /**
2340   * Returns the tip text for this property
2341   *
2342   * @return            tip text for this property suitable for
2343   *                    displaying in the explorer/experimenter gui
2344   */
2345  public String YMinTipText() {
2346    return "The minimum of Y (normally the classifier).";
2347  }
2348
2349  /**
2350   * Get the value of the minimum of Y.
2351   *
2352   * @return            Value of the minimum of Y.
2353   */
2354  public double getYMin() {
2355    return m_Y_Min;
2356  }
2357 
2358  /**
2359   * Set the value of the minimum of Y.
2360   *
2361   * @param value       Value to use as minimum of Y.
2362   */
2363  public void setYMin(double value) {
2364    m_Y_Min = value;
2365  }
2366 
2367  /**
2368   * Returns the tip text for this property
2369   *
2370   * @return            tip text for this property suitable for
2371   *                    displaying in the explorer/experimenter gui
2372   */
2373  public String YMaxTipText() {
2374    return "The maximum of Y.";
2375  }
2376
2377  /**
2378   * Get the value of the Maximum of Y.
2379   *
2380   * @return            Value of the Maximum of Y.
2381   */
2382  public double getYMax() {
2383    return m_Y_Max;
2384  }
2385 
2386  /**
2387   * Set the value of the Maximum of Y.
2388   *
2389   * @param value       Value to use as Maximum of Y.
2390   */
2391  public void setYMax(double value) {
2392    m_Y_Max = value;
2393  }
2394 
2395  /**
2396   * Returns the tip text for this property
2397   *
2398   * @return            tip text for this property suitable for
2399   *                    displaying in the explorer/experimenter gui
2400   */
2401  public String YStepTipText() {
2402    return "The step size of Y.";
2403  }
2404
2405  /**
2406   * Get the value of the step size for Y.
2407   *
2408   * @return            Value of the step size for Y.
2409   */
2410  public double getYStep() {
2411    return m_Y_Step;
2412  }
2413 
2414  /**
2415   * Set the value of the step size for Y.
2416   *
2417   * @param value       Value to use as the step size for Y.
2418   */
2419  public void setYStep(double value) {
2420    m_Y_Step = value;
2421  }
2422 
2423  /**
2424   * Returns the tip text for this property
2425   *
2426   * @return            tip text for this property suitable for
2427   *                    displaying in the explorer/experimenter gui
2428   */
2429  public String YBaseTipText() {
2430    return "The base of Y.";
2431  }
2432
2433  /**
2434   * Get the value of the base for Y.
2435   *
2436   * @return            Value of the base for Y.
2437   */
2438  public double getYBase() {
2439    return m_Y_Base;
2440  }
2441 
2442  /**
2443   * Set the value of the base for Y.
2444   *
2445   * @param value Value to use as the base for Y.
2446   */
2447  public void setYBase(double value) {
2448    m_Y_Base = value;
2449  }
2450 
2451  /**
2452   * Returns the tip text for this property
2453   *
2454   * @return            tip text for this property suitable for
2455   *                    displaying in the explorer/experimenter gui
2456   */
2457  public String YExpressionTipText() {
2458    return "The expression for the Y value (parameters: BASE, FROM, TO, STEP, I).";
2459  }
2460
2461  /**
2462   * Get the expression for the Y value.
2463   *
2464   * @return Expression for the Y value.
2465   */
2466  public String getYExpression() {
2467    return m_Y_Expression;
2468  }
2469 
2470  /**
2471   * Set the expression for the Y value.
2472   *
2473   * @param value Expression for the Y value.
2474   */
2475  public void setYExpression(String value) {
2476    m_Y_Expression = value;
2477  }
2478 
2479  /**
2480   * Returns the tip text for this property
2481   *
2482   * @return            tip text for this property suitable for
2483   *                    displaying in the explorer/experimenter gui
2484   */
2485  public String XPropertyTipText() {
2486    return "The X property to test (normally the filter).";
2487  }
2488
2489  /**
2490   * Get the X property to test (normally the filter).
2491   *
2492   * @return            Value of the X property.
2493   */
2494  public String getXProperty() {
2495    return m_X_Property;
2496  }
2497 
2498  /**
2499   * Set the X property.
2500   *
2501   * @param value       the X property.
2502   */
2503  public void setXProperty(String value) {
2504    m_X_Property = value;
2505  }
2506 
2507  /**
2508   * Returns the tip text for this property
2509   *
2510   * @return            tip text for this property suitable for
2511   *                    displaying in the explorer/experimenter gui
2512   */
2513  public String XMinTipText() {
2514    return "The minimum of X.";
2515  }
2516
2517  /**
2518   * Get the value of the minimum of X.
2519   *
2520   * @return Value of the minimum of X.
2521   */
2522  public double getXMin() {
2523    return m_X_Min;
2524  }
2525 
2526  /**
2527   * Set the value of the minimum of X.
2528   *
2529   * @param value Value to use as minimum of X.
2530   */
2531  public void setXMin(double value) {
2532    m_X_Min = value;
2533  }
2534 
2535  /**
2536   * Returns the tip text for this property
2537   *
2538   * @return            tip text for this property suitable for
2539   *                    displaying in the explorer/experimenter gui
2540   */
2541  public String XMaxTipText() {
2542    return "The maximum of X.";
2543  }
2544
2545  /**
2546   * Get the value of the Maximum of X.
2547   *
2548   * @return Value of the Maximum of X.
2549   */
2550  public double getXMax() {
2551    return m_X_Max;
2552  }
2553 
2554  /**
2555   * Set the value of the Maximum of X.
2556   *
2557   * @param value Value to use as Maximum of X.
2558   */
2559  public void setXMax(double value) {
2560    m_X_Max = value;
2561  }
2562 
2563  /**
2564   * Returns the tip text for this property
2565   *
2566   * @return            tip text for this property suitable for
2567   *                    displaying in the explorer/experimenter gui
2568   */
2569  public String XStepTipText() {
2570    return "The step size of X.";
2571  }
2572
2573  /**
2574   * Get the value of the step size for X.
2575   *
2576   * @return Value of the step size for X.
2577   */
2578  public double getXStep() {
2579    return m_X_Step;
2580  }
2581 
2582  /**
2583   * Set the value of the step size for X.
2584   *
2585   * @param value Value to use as the step size for X.
2586   */
2587  public void setXStep(double value) {
2588    m_X_Step = value;
2589  }
2590 
2591  /**
2592   * Returns the tip text for this property
2593   *
2594   * @return            tip text for this property suitable for
2595   *                    displaying in the explorer/experimenter gui
2596   */
2597  public String XBaseTipText() {
2598    return "The base of X.";
2599  }
2600
2601  /**
2602   * Get the value of the base for X.
2603   *
2604   * @return Value of the base for X.
2605   */
2606  public double getXBase() {
2607    return m_X_Base;
2608  }
2609 
2610  /**
2611   * Set the value of the base for X.
2612   *
2613   * @param value Value to use as the base for X.
2614   */
2615  public void setXBase(double value) {
2616    m_X_Base = value;
2617  }
2618 
2619  /**
2620   * Returns the tip text for this property
2621   *
2622   * @return            tip text for this property suitable for
2623   *                    displaying in the explorer/experimenter gui
2624   */
2625  public String XExpressionTipText() {
2626    return "The expression for the X value (parameters: BASE, FROM, TO, STEP, I).";
2627  }
2628
2629  /**
2630   * Get the expression for the X value.
2631   *
2632   * @return Expression for the X value.
2633   */
2634  public String getXExpression() {
2635    return m_X_Expression;
2636  }
2637 
2638  /**
2639   * Set the expression for the X value.
2640   *
2641   * @param value Expression for the X value.
2642   */
2643  public void setXExpression(String value) {
2644    m_X_Expression = value;
2645  }
2646 
2647  /**
2648   * Returns the tip text for this property
2649   *
2650   * @return            tip text for this property suitable for
2651   *                    displaying in the explorer/experimenter gui
2652   */
2653  public String gridIsExtendableTipText() {
2654    return "Whether the grid can be extended.";
2655  }
2656
2657  /**
2658   * Get whether the grid can be extended dynamically.
2659   *
2660   * @return true if the grid can be extended.
2661   */
2662  public boolean getGridIsExtendable() {
2663    return m_GridIsExtendable;
2664  }
2665 
2666  /**
2667   * Set whether the grid can be extended dynamically.
2668   *
2669   * @param value whether the grid can be extended dynamically.
2670   */
2671  public void setGridIsExtendable(boolean value) {
2672    m_GridIsExtendable = value;
2673  }
2674 
2675  /**
2676   * Returns the tip text for this property
2677   *
2678   * @return            tip text for this property suitable for
2679   *                    displaying in the explorer/experimenter gui
2680   */
2681  public String maxGridExtensionsTipText() {
2682    return "The maximum number of grid extensions, -1 for unlimited.";
2683  }
2684
2685  /**
2686   * Gets the maximum number of grid extensions, -1 for unlimited.
2687   *
2688   * @return the max number of grid extensions
2689   */
2690  public int getMaxGridExtensions() {
2691    return m_MaxGridExtensions;
2692  }
2693 
2694  /**
2695   * Sets the maximum number of grid extensions, -1 for unlimited.
2696   *
2697   * @param value the maximum of grid extensions.
2698   */
2699  public void setMaxGridExtensions(int value) {
2700    m_MaxGridExtensions = value;
2701  }
2702 
2703  /**
2704   * Returns the tip text for this property
2705   *
2706   * @return            tip text for this property suitable for
2707   *                    displaying in the explorer/experimenter gui
2708   */
2709  public String sampleSizePercentTipText() {
2710    return "The sample size (in percent) to use in the initial grid search.";
2711  }
2712
2713  /**
2714   * Gets the sample size for the initial grid search.
2715   *
2716   * @return the sample size.
2717   */
2718  public double getSampleSizePercent() {
2719    return m_SampleSize;
2720  }
2721 
2722  /**
2723   * Sets the sample size for the initial grid search.
2724   *
2725   * @param value the sample size for the initial grid search.
2726   */
2727  public void setSampleSizePercent(double value) {
2728    m_SampleSize = value;
2729  }
2730
2731  /**
2732   * Returns the tip text for this property
2733   *
2734   * @return            tip text for this property suitable for
2735   *                    displaying in the explorer/experimenter gui
2736   */
2737  public String traversalTipText() {
2738    return "Sets type of traversal of the grid, either by rows or columns.";
2739  }
2740
2741  /**
2742   * Sets the type of traversal for the grid.
2743   *
2744   * @param value       the traversal type
2745   */
2746  public void setTraversal(SelectedTag value) {
2747    if (value.getTags() == TAGS_TRAVERSAL) {
2748      m_Traversal = value.getSelectedTag().getID();
2749    }
2750  }
2751
2752  /**
2753   * Gets the type of traversal for the grid.
2754   *
2755   * @return            the current traversal type.
2756   */
2757  public SelectedTag getTraversal() {
2758    return new SelectedTag(m_Traversal, TAGS_TRAVERSAL);
2759  }
2760 
2761  /**
2762   * Returns the tip text for this property
2763   *
2764   * @return            tip text for this property suitable for
2765   *                    displaying in the explorer/experimenter gui
2766   */
2767  public String logFileTipText() {
2768    return "The log file to log the messages to.";
2769  }
2770
2771  /**
2772   * Gets current log file.
2773   *
2774   * @return            the log file.
2775   */
2776  public File getLogFile() {
2777    return m_LogFile;
2778  }
2779 
2780  /**
2781   * Sets the log file to use.
2782   *
2783   * @param value       the log file.
2784   */
2785  public void setLogFile(File value) {
2786    m_LogFile = value;
2787  }
2788
2789  /**
2790   * returns the best filter setup
2791   *
2792   * @return            the best filter setup
2793   */
2794  public Filter getBestFilter() {
2795    return m_BestFilter;
2796  }
2797
2798  /**
2799   * returns the best Classifier setup
2800   *
2801   * @return            the best Classifier setup
2802   */
2803  public Classifier getBestClassifier() {
2804    return m_BestClassifier;
2805  }
2806 
2807  /**
2808   * Returns an enumeration of the measure names.
2809   *
2810   * @return an enumeration of the measure names
2811   */
2812  public Enumeration enumerateMeasures() {
2813    Vector      result;
2814   
2815    result = new Vector();
2816   
2817    result.add("measureX");
2818    result.add("measureY");
2819    result.add("measureGridExtensionsPerformed");
2820   
2821    return result.elements();
2822  }
2823
2824  /**
2825   * Returns the value of the named measure
2826   *
2827   * @param measureName the name of the measure to query for its value
2828   * @return the value of the named measure
2829   */
2830  public double getMeasure(String measureName) {
2831    if (measureName.equalsIgnoreCase("measureX"))
2832      return evaluate(getValues().getX(), true);
2833    else if (measureName.equalsIgnoreCase("measureY"))
2834      return evaluate(getValues().getY(), false);
2835    else if (measureName.equalsIgnoreCase("measureGridExtensionsPerformed"))
2836      return getGridExtensionsPerformed();
2837    else
2838      throw new IllegalArgumentException("Measure '" + measureName + "' not supported!");
2839  }
2840 
2841  /**
2842   * returns the parameter pair that was found to work best
2843   *
2844   * @return            the best parameter combination
2845   */
2846  public PointDouble getValues() {
2847    return m_Values;
2848  }
2849
2850  /**
2851   * returns the number of grid extensions that took place during the search
2852   * (only applicable if the grid was extendable).
2853   *
2854   * @return            the number of grid extensions that were performed
2855   * @see               #getGridIsExtendable()
2856   */
2857  public int getGridExtensionsPerformed() {
2858    return m_GridExtensionsPerformed;
2859  }
2860 
2861  /**
2862   * Returns default capabilities of the classifier.
2863   *
2864   * @return            the capabilities of this classifier
2865   */
2866  public Capabilities getCapabilities() {
2867    Capabilities        result;
2868    Capabilities        classes;
2869    Iterator            iter;
2870    Capability          capab;
2871   
2872    if (getFilter() == null)
2873      result = super.getCapabilities();
2874    else
2875      result = getFilter().getCapabilities();
2876   
2877    // only nominal and numeric classes allowed
2878    classes = result.getClassCapabilities();
2879    iter = classes.capabilities();
2880    while (iter.hasNext()) {
2881      capab = (Capability) iter.next();
2882      if (    (capab != Capability.BINARY_CLASS)
2883           && (capab != Capability.NOMINAL_CLASS)
2884           && (capab != Capability.NUMERIC_CLASS)
2885           && (capab != Capability.DATE_CLASS) )
2886        result.disable(capab);
2887    }
2888   
2889    result.enable(Capability.MISSING_CLASS_VALUES);
2890   
2891    // set dependencies
2892    for (Capability cap: Capability.values())
2893      result.enableDependency(cap);
2894   
2895    if (result.getMinimumNumberInstances() < 1)
2896      result.setMinimumNumberInstances(1);
2897
2898    result.setOwner(this);
2899   
2900    return result;
2901  }
2902
2903  /**
2904   * prints the specified message to stdout if debug is on and can also dump
2905   * the message to a log file
2906   *
2907   * @param message     the message to print or store in a log file
2908   */
2909  protected void log(String message) {
2910    log(message, false);
2911  }
2912
2913  /**
2914   * prints the specified message to stdout if debug is on and can also dump
2915   * the message to a log file
2916   *
2917   * @param message     the message to print or store in a log file
2918   * @param onlyLog     if true the message will only be put into the log file
2919   *                    but not to stdout
2920   */
2921  protected void log(String message, boolean onlyLog) {
2922    // print to stdout?
2923    if (getDebug() && (!onlyLog))
2924      System.out.println(message);
2925   
2926    // log file?
2927    if (!getLogFile().isDirectory())
2928      Debug.writeToFile(getLogFile().getAbsolutePath(), message, true);
2929  }
2930 
2931  /**
2932   * replaces the current option in the options array with a new value
2933   *
2934   * @param options     the current options
2935   * @param option      the option to set a new value for
2936   * @param value       the value to set
2937   * @return            the updated array
2938   * @throws Exception  if something goes wrong
2939   */
2940  protected String[] updateOption(String[] options, String option, String value) 
2941    throws Exception {
2942   
2943    String[]            result;
2944    Vector              tmpOptions;
2945    int                 i;
2946
2947    // remove old option
2948    Utils.getOption(option, options);
2949   
2950    // add option with new value at the beginning (to avoid clashes with "--")
2951    tmpOptions = new Vector();
2952    tmpOptions.add("-" + option);
2953    tmpOptions.add("" + value);
2954
2955    // move options into vector
2956    for (i = 0; i < options.length; i++) {
2957      if (options[i].length() != 0)
2958        tmpOptions.add(options[i]);
2959    }
2960   
2961    result = (String[]) tmpOptions.toArray(new String[tmpOptions.size()]);
2962   
2963    return result;
2964  }
2965 
2966  /**
2967   * evalutes the expression for the current iteration
2968   *
2969   * @param value       the current iteration value (from 'min' to 'max' with 
2970   *                    stepsize 'step')
2971   * @param isX         true if X is to be evaluated otherwise Y
2972   * @return            the generated value, NaN if the evaluation fails
2973   */
2974  protected double evaluate(double value, boolean isX) {
2975    double      result;
2976    HashMap     symbols;
2977    String      expr;
2978    double      base;
2979    double      min;
2980    double      max;
2981    double      step;
2982
2983    if (isX) {
2984      expr = getXExpression();
2985      base = getXBase();
2986      min  = getXMin();
2987      max  = getXMax();
2988      step = getXStep();
2989    }
2990    else {
2991      expr = getYExpression();
2992      base = getYBase();
2993      min  = getYMin();
2994      max  = getYMax();
2995      step = getYStep();
2996    }
2997
2998    try {
2999      symbols = new HashMap();
3000      symbols.put("BASE", new Double(base));
3001      symbols.put("FROM", new Double(min));
3002      symbols.put("TO",   new Double(max));
3003      symbols.put("STEP", new Double(step));
3004      symbols.put("I",    new Double(value));
3005      result = MathematicalExpression.evaluate(expr, symbols);
3006    }
3007    catch (Exception e) {
3008      result = Double.NaN;
3009    }
3010   
3011    return result;
3012  }
3013
3014  /**
3015   * tries to set the value as double, integer (just casts it to int!) or
3016   * boolean (false if 0, otherwise true) in the object according to the
3017   * specified path. float, char and long are also supported.
3018   *
3019   * @param o           the object to modify
3020   * @param path        the property path
3021   * @param value       the value to set
3022   * @return            the modified object
3023   * @throws Exception  if neither double nor int could be set
3024   */
3025  protected Object setValue(Object o, String path, double value) throws Exception {
3026    PropertyDescriptor  desc;
3027    Class               c;
3028   
3029    desc = PropertyPath.getPropertyDescriptor(o, path);
3030    c    = desc.getPropertyType();
3031
3032    // float
3033    if ((c == Float.class) || (c == Float.TYPE))
3034      PropertyPath.setValue(o, path, new Float((float) value));
3035    // double
3036    else if ((c == Double.class) || (c == Double.TYPE))
3037      PropertyPath.setValue(o, path, new Double(value));
3038    // char
3039    else if ((c == Character.class) || (c == Character.TYPE))
3040      PropertyPath.setValue(o, path, new Integer((char) value));
3041    // int
3042    else if ((c == Integer.class) || (c == Integer.TYPE))
3043      PropertyPath.setValue(o, path, new Integer((int) value));
3044    // long
3045    else if ((c == Long.class) || (c == Long.TYPE))
3046      PropertyPath.setValue(o, path, new Long((long) value));
3047    // boolean
3048    else if ((c == Boolean.class) || (c == Boolean.TYPE))
3049      PropertyPath.setValue(o, path, (value == 0 ? new Boolean(false) : new Boolean(true)));
3050    else throw new Exception(
3051        "Could neither set double nor integer nor boolean value for '" + path + "'!");
3052   
3053    return o;
3054  }
3055 
3056  /**
3057   * returns a fully configures object (a copy of the provided one)
3058   *
3059   * @param original    the object to create a copy from and set the parameters
3060   * @param valueX      the current iteration value for X
3061   * @param valueY      the current iteration value for Y
3062   * @return            the configured classifier
3063   * @throws Exception  if setup fails
3064   */
3065  protected Object setup(Object original, double valueX, double valueY) throws Exception {
3066    Object      result;
3067   
3068    result = new SerializedObject(original).getObject();
3069   
3070    if (original instanceof Classifier) {
3071      if (getXProperty().startsWith(PREFIX_CLASSIFIER))
3072        setValue(
3073            result,
3074            getXProperty().substring(PREFIX_CLASSIFIER.length()),
3075            valueX);
3076     
3077      if (getYProperty().startsWith(PREFIX_CLASSIFIER))
3078        setValue(
3079            result,
3080            getYProperty().substring(PREFIX_CLASSIFIER.length()), 
3081            valueY);
3082    }
3083    else if (original instanceof Filter) {
3084      if (getXProperty().startsWith(PREFIX_FILTER))
3085        setValue(
3086            result,
3087            getXProperty().substring(PREFIX_FILTER.length()), 
3088            valueX);
3089     
3090      if (getYProperty().startsWith(PREFIX_FILTER))
3091        setValue(
3092            result,
3093            getYProperty().substring(PREFIX_FILTER.length()), 
3094            valueY);
3095    }
3096    else {
3097      throw new IllegalArgumentException("Object must be either classifier or filter!");
3098    }
3099   
3100    return result;
3101  }
3102 
3103  /**
3104   * generates a table string for all the performances in the grid and returns
3105   * that.
3106   *
3107   * @param grid                the current grid to align the performances to
3108   * @param performances        the performances to align
3109   * @param type                the type of performance
3110   * @return                    the table string
3111   */
3112  protected String logPerformances(Grid grid, Vector<Performance> performances, Tag type) {
3113    StringBuffer        result;
3114    PerformanceTable    table;
3115   
3116    result = new StringBuffer(type.getReadable() + ":\n");
3117    table  = new PerformanceTable(grid, performances, type.getID());
3118   
3119    result.append(table.toString() + "\n");
3120    result.append("\n");
3121    result.append(table.toGnuplot() + "\n");
3122    result.append("\n");
3123   
3124    return result.toString();
3125  }
3126 
3127  /**
3128   * aligns all performances in the grid and prints those tables to the log
3129   * file.
3130   *
3131   * @param grid                the current grid to align the performances to
3132   * @param performances        the performances to align
3133   */
3134  protected void logPerformances(Grid grid, Vector performances) {
3135    int         i;
3136   
3137    for (i = 0; i < TAGS_EVALUATION.length; i++)
3138      log("\n" + logPerformances(grid, performances, TAGS_EVALUATION[i]), true);
3139  }
3140 
3141  /**
3142   * determines the best values-pair for the given grid, using CV with
3143   * specified number of folds.
3144   *
3145   * @param grid        the grid to work on
3146   * @param inst        the data to work with
3147   * @param cv          the number of folds for the cross-validation
3148   * @return            the best values pair
3149   * @throws Exception  if setup or training fails
3150   */
3151  protected PointDouble determineBestInGrid(Grid grid, Instances inst, int cv) throws Exception {
3152    int                         i;
3153    Enumeration<PointDouble>    enm;
3154    Vector<Performance>         performances;
3155    PointDouble                 values;
3156    Instances                   data;
3157    Evaluation                  eval;
3158    PointDouble                 result;
3159    Classifier                  classifier;
3160    Filter                      filter;
3161    int                         size;
3162    boolean                     cached;
3163    boolean                     allCached;
3164    Performance                 p1;
3165    Performance                 p2;
3166    double                      x;
3167    double                      y;
3168   
3169    performances = new Vector();
3170   
3171    log("Determining best pair with " + cv + "-fold CV in Grid:\n" + grid + "\n");
3172   
3173    if (m_Traversal == TRAVERSAL_BY_COLUMN)
3174      size = grid.width();
3175    else
3176      size = grid.height();
3177   
3178    allCached = true;
3179
3180    for (i = 0; i < size; i++) {
3181      if (m_Traversal == TRAVERSAL_BY_COLUMN)
3182        enm = grid.column(i);
3183      else
3184        enm = grid.row(i);
3185     
3186      filter = null;
3187      data   = null;
3188     
3189      while (enm.hasMoreElements()) {
3190        values = enm.nextElement();
3191       
3192        // already calculated?
3193        cached = m_Cache.isCached(cv, values);
3194        if (cached) {
3195          performances.add(m_Cache.get(cv, values));
3196        }
3197        else {
3198          allCached = false;
3199         
3200          x = evaluate(values.getX(), true);
3201          y = evaluate(values.getY(), false);
3202         
3203          // data pass through filter
3204          if (filter == null) {
3205            filter = (Filter) setup(getFilter(), x, y);
3206            filter.setInputFormat(inst);
3207            data = Filter.useFilter(inst, filter);
3208            // make sure that the numbers don't get too small - otherwise NaNs!
3209            Filter cleaner = new NumericCleaner();
3210            cleaner.setInputFormat(data);
3211            data = Filter.useFilter(data, cleaner);
3212          }
3213
3214          // setup classifier
3215          classifier = (Classifier) setup(getClassifier(), x, y);
3216
3217          // evaluate
3218          eval = new Evaluation(data);
3219          eval.crossValidateModel(classifier, data, cv, new Random(getSeed()));
3220          performances.add(new Performance(values, eval));
3221         
3222          // add to cache
3223          m_Cache.add(cv, new Performance(values, eval));
3224        }
3225
3226        log("" + performances.get(performances.size() - 1) + ": cached=" + cached);
3227      }
3228    }
3229
3230    if (allCached) {
3231      log("All points were already cached - abnormal state!");
3232      throw new IllegalStateException("All points were already cached - abnormal state!");
3233    }
3234   
3235    // sort list
3236    Collections.sort(performances, new PerformanceComparator(m_Evaluation));
3237
3238    result = performances.get(performances.size() - 1).getValues();
3239
3240    // check whether all performances are the same
3241    m_UniformPerformance = true;
3242    p1 = performances.get(0);
3243    for (i = 1; i < performances.size(); i++) {
3244      p2 = performances.get(i);
3245      if (p2.getPerformance(m_Evaluation) != p1.getPerformance(m_Evaluation)) {
3246        m_UniformPerformance = false;
3247        break;
3248      }
3249    }
3250    if (m_UniformPerformance)
3251      log("All performances are the same!");
3252   
3253    logPerformances(grid, performances);
3254    log("\nBest performance:\n" + performances.get(performances.size() - 1));
3255   
3256    return result;
3257  }
3258 
3259  /**
3260   * returns the best values-pair in the grid
3261   *
3262   * @return            the best values pair
3263   * @throws Exception  if something goes wrong
3264   */
3265  protected PointDouble findBest() throws Exception {
3266    PointInt            center;
3267    Grid                neighborGrid;
3268    boolean             finished;
3269    PointDouble         result;
3270    PointDouble         resultOld;
3271    int                 iteration;
3272    Instances           sample;
3273    Resample            resample;
3274
3275    log("Step 1:\n");
3276
3277    // generate sample?
3278    if (getSampleSizePercent() == 100) {
3279      sample = m_Data;
3280    }
3281    else {
3282      log("Generating sample (" + getSampleSizePercent() + "%)");
3283      resample = new Resample();
3284      resample.setRandomSeed(getSeed());
3285      resample.setSampleSizePercent(getSampleSizePercent());
3286      resample.setInputFormat(m_Data);
3287      sample = Filter.useFilter(m_Data, resample);
3288    }
3289   
3290    finished                  = false;
3291    iteration                 = 0;
3292    m_GridExtensionsPerformed = 0;
3293    m_UniformPerformance      = false;
3294   
3295    // find first center
3296    log("\n=== Initial grid - Start ===");
3297    result = determineBestInGrid(m_Grid, sample, 2);
3298    log("\nResult of Step 1: " + result + "\n");
3299    log("=== Initial grid - End ===\n");
3300
3301    finished = m_UniformPerformance;
3302   
3303    if (!finished) {
3304      do {
3305        iteration++;
3306        resultOld = (PointDouble) result.clone();
3307        center    = m_Grid.getLocation(result);
3308        // on border? -> finished (if it cannot be extended)
3309        if (m_Grid.isOnBorder(center)) {
3310          log("Center is on border of grid.");
3311
3312          // can we extend grid?
3313          if (getGridIsExtendable()) {
3314            // max number of extensions reached?
3315            if (m_GridExtensionsPerformed == getMaxGridExtensions()) {
3316              log("Maximum number of extensions reached!\n");
3317              finished = true;
3318            }
3319            else {
3320              m_GridExtensionsPerformed++;
3321              m_Grid = m_Grid.extend(result);
3322              center = m_Grid.getLocation(result);
3323              log("Extending grid (" + m_GridExtensionsPerformed + "/" 
3324                  + getMaxGridExtensions() + "):\n" + m_Grid + "\n");
3325            }
3326          }
3327          else {
3328            finished = true;
3329          }
3330        }
3331
3332        // new grid with current best one at center and immediate neighbors
3333        // around it
3334        if (!finished) {
3335          neighborGrid = m_Grid.subgrid(
3336              (int) center.getY() + 1, (int) center.getX() - 1, 
3337              (int) center.getY() - 1, (int) center.getX() + 1);
3338          result = determineBestInGrid(neighborGrid, sample, 10);
3339          log("\nResult of Step 2/Iteration " + (iteration) + ":\n" + result);
3340          finished = m_UniformPerformance;
3341
3342          // no improvement?
3343          if (result.equals(resultOld)) {
3344            finished = true;
3345            log("\nNo better point found.");
3346          }
3347        }
3348      }
3349      while (!finished);
3350    }
3351   
3352    log("\nFinal result: " + result);
3353
3354    return result;
3355  }
3356 
3357  /**
3358   * builds the classifier
3359   *
3360   * @param data        the training instances
3361   * @throws Exception  if something goes wrong
3362   */
3363  public void buildClassifier(Instances data) throws Exception {
3364    String      strX;
3365    String      strY;
3366    double      x;
3367    double      y;
3368   
3369    // can classifier handle the data?
3370    getCapabilities().testWithFail(data);
3371
3372    // remove instances with missing class
3373    m_Data = new Instances(data);
3374    m_Data.deleteWithMissingClass();
3375   
3376    m_Cache = new PerformanceCache();
3377   
3378    if (getXProperty().startsWith(PREFIX_FILTER))
3379      strX = m_Filter.getClass().getName();
3380    else
3381      strX = m_Classifier.getClass().getName();
3382   
3383    if (getYProperty().startsWith(PREFIX_CLASSIFIER))
3384      strY = m_Classifier.getClass().getName();
3385    else
3386      strY = m_Filter.getClass().getName();
3387   
3388    m_Grid = new Grid(getXMin(), getXMax(), getXStep(), 
3389                      strX + ", property " + getXProperty() + ", expr. " + getXExpression() + ", base " + getXBase(),
3390                      getYMin(), getYMax(), getYStep(),
3391                      strY + ", property " + getYProperty() + ", expr. " + getYExpression() + ", base " + getYBase());
3392
3393    log("\n" 
3394        + this.getClass().getName() + "\n" 
3395        + this.getClass().getName().replaceAll(".", "=") + "\n"
3396        + "Options: " + Utils.joinOptions(getOptions()) + "\n");
3397   
3398    // find best
3399    m_Values = findBest();
3400
3401    // setup best configurations
3402    x                = evaluate(m_Values.getX(), true);
3403    y                = evaluate(m_Values.getY(), false);
3404    m_BestFilter     = (Filter) setup(getFilter(), x, y);
3405    m_BestClassifier = (Classifier) setup(getClassifier(), x, y);
3406   
3407    // process data
3408    m_Filter = (Filter) setup(getFilter(), x, y);
3409    m_Filter.setInputFormat(m_Data);
3410    Instances transformed = Filter.useFilter(m_Data, m_Filter);
3411   
3412    // train classifier
3413    m_Classifier = (Classifier) setup(getClassifier(), x, y);
3414    m_Classifier.buildClassifier(transformed);
3415  }
3416
3417  /**
3418   * Classifies the given instance.
3419   *
3420   * @param instance    the test instance
3421   * @return            the classification
3422   * @throws Exception  if classification can't be done successfully
3423   */
3424  public double classifyInstance(Instance instance) throws Exception {
3425    // transform instance
3426    m_Filter.input(instance);
3427    m_Filter.batchFinished();
3428    Instance transformed = m_Filter.output();
3429   
3430    // classify instance
3431    return m_Classifier.classifyInstance(transformed);
3432  }
3433
3434  /**
3435   * returns a string representation of the classifier
3436   *
3437   * @return a string representation of the classifier
3438   */
3439  public String toString() {
3440    String      result;
3441   
3442    result = "";
3443   
3444    if (m_Values == null) {
3445      result = "No search performed yet.";
3446    }
3447    else {
3448      result = 
3449          this.getClass().getName() + ":\n"
3450        + "Filter: " + getFilter().getClass().getName() 
3451        + (getFilter() instanceof OptionHandler ? " " + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()) : "") + "\n"
3452        + "Classifier: " + getClassifier().getClass().getName() 
3453        + " " + Utils.joinOptions(((OptionHandler)getClassifier()).getOptions()) + "\n\n"
3454        + "X property: " + getXProperty() + "\n"
3455        + "Y property: " + getYProperty() + "\n\n"
3456        + "Evaluation: " + getEvaluation().getSelectedTag().getReadable() + "\n"
3457        + "Coordinates: " + getValues() + "\n";
3458     
3459      if (getGridIsExtendable())
3460        result += "Grid-Extensions: " + getGridExtensionsPerformed() + "\n";
3461     
3462      result += 
3463        "Values: "
3464        + evaluate(getValues().getX(), true) + " (X coordinate)" 
3465        + ", "
3466        + evaluate(getValues().getY(), false) + " (Y coordinate)"
3467        + "\n\n"
3468        + m_Classifier.toString();
3469    }
3470   
3471    return result;
3472  }
3473
3474  /**
3475   * Returns a string that summarizes the object.
3476   *
3477   * @return            the object summarized as a string
3478   */
3479  public String toSummaryString() {
3480    String      result;
3481   
3482    result = 
3483        "Best filter: " + getBestFilter().getClass().getName() 
3484      + (getBestFilter() instanceof OptionHandler ? " " + Utils.joinOptions(((OptionHandler) getBestFilter()).getOptions()) : "") + "\n"
3485      + "Best classifier: " + getBestClassifier().getClass().getName() 
3486      + " " + Utils.joinOptions(((OptionHandler)getBestClassifier()).getOptions());
3487   
3488    return result;
3489  }
3490 
3491  /**
3492   * Returns the revision string.
3493   *
3494   * @return            the revision
3495   */
3496  public String getRevision() {
3497    return RevisionUtils.extract("$Revision: 5928 $");
3498  }
3499 
3500  /**
3501   * Main method for running this classifier from commandline.
3502   *
3503   * @param args        the options
3504   */
3505  public static void main(String[] args) {
3506    runClassifier(new GridSearch(), args);
3507  }
3508}
Note: See TracBrowser for help on using the repository browser.