Logo Search packages:      
Sourcecode: weka version File versions

GridSearch.java

/*
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
 * GridSearch.java
 * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
 */

package weka.classifiers.meta;

import weka.classifiers.Classifier;
import weka.classifiers.Evaluation;
import weka.classifiers.RandomizableSingleClassifierEnhancer;
import weka.classifiers.functions.LinearRegression;
import weka.core.AdditionalMeasureProducer;
import weka.core.Capabilities;
import weka.core.Debug;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.MathematicalExpression;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.PropertyPath;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import weka.core.SelectedTag;
import weka.core.SerializedObject;
import weka.core.Summarizable;
import weka.core.Tag;
import weka.core.Utils;
import weka.core.Capabilities.Capability;
import weka.filters.Filter;
import weka.filters.supervised.attribute.PLSFilter;
import weka.filters.unsupervised.attribute.MathExpression;
import weka.filters.unsupervised.attribute.NumericCleaner;
import weka.filters.unsupervised.instance.Resample;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Random;
import java.util.Vector;

/**
 <!-- globalinfo-start -->
 * 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/>
 * <br/>
 * 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/>
 * 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/>
 * <br/>
 * 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/>
 * <br/>
 * The best filter/classifier setup can be accessed after the buildClassifier call via the getBestFilter/getBestClassifier methods.<br/>
 * 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.
 * <p/>
 <!-- globalinfo-end -->
 * 
 <!-- options-start -->
 * Valid options are: <p/>
 * 
 * <pre> -E &lt;CC|RMSE|RRSE|MAE|RAE|COMB|ACC&gt;
 *  Determines the parameter used for evaluation:
 *  CC = Correlation coefficient
 *  RMSE = Root mean squared error
 *  RRSE = Root relative squared error
 *  MAE = Mean absolute error
 *  RAE = Root absolute error
 *  COMB = Combined = (1-abs(CC)) + RRSE + RAE
 *  ACC = Accuracy
 *  (default: CC)</pre>
 * 
 * <pre> -y-property &lt;option&gt;
 *  The Y option to test (without leading dash).
 *  (default: classifier.ridge)</pre>
 * 
 * <pre> -y-min &lt;num&gt;
 *  The minimum for Y.
 *  (default: -10)</pre>
 * 
 * <pre> -y-max &lt;num&gt;
 *  The maximum for Y.
 *  (default: +5)</pre>
 * 
 * <pre> -y-step &lt;num&gt;
 *  The step size for Y.
 *  (default: 1)</pre>
 * 
 * <pre> -y-base &lt;num&gt;
 *  The base for Y.
 *  (default: 10)</pre>
 * 
 * <pre> -y-expression &lt;expr&gt;
 *  The expression for Y.
 *  Available parameters:
 *   BASE
 *   FROM
 *   TO
 *   STEP
 *   I - the current iteration value
 *   (from 'FROM' to 'TO' with stepsize 'STEP')
 *  (default: 'pow(BASE,I)')</pre>
 * 
 * <pre> -filter &lt;filter specification&gt;
 *  The filter to use (on X axis). Full classname of filter to include, 
 *  followed by scheme options.
 *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
 * 
 * <pre> -x-property &lt;option&gt;
 *  The X option to test (without leading dash).
 *  (default: filter.numComponents)</pre>
 * 
 * <pre> -x-min &lt;num&gt;
 *  The minimum for X.
 *  (default: +5)</pre>
 * 
 * <pre> -x-max &lt;num&gt;
 *  The maximum for X.
 *  (default: +20)</pre>
 * 
 * <pre> -x-step &lt;num&gt;
 *  The step size for X.
 *  (default: 1)</pre>
 * 
 * <pre> -x-base &lt;num&gt;
 *  The base for X.
 *  (default: 10)</pre>
 * 
 * <pre> -x-expression &lt;expr&gt;
 *  The expression for the X value.
 *  Available parameters:
 *   BASE
 *   MIN
 *   MAX
 *   STEP
 *   I - the current iteration value
 *   (from 'FROM' to 'TO' with stepsize 'STEP')
 *  (default: 'pow(BASE,I)')</pre>
 * 
 * <pre> -extend-grid
 *  Whether the grid can be extended.
 *  (default: no)</pre>
 * 
 * <pre> -max-grid-extensions &lt;num&gt;
 *  The maximum number of grid extensions (-1 is unlimited).
 *  (default: 3)</pre>
 * 
 * <pre> -sample-size &lt;num&gt;
 *  The size (in percent) of the sample to search the inital grid with.
 *  (default: 100)</pre>
 * 
 * <pre> -traversal &lt;ROW-WISE|COLUMN-WISE&gt;
 *  The type of traversal for the grid.
 *  (default: COLUMN-WISE)</pre>
 * 
 * <pre> -log-file &lt;filename&gt;
 *  The log file to log the messages to.
 *  (default: none)</pre>
 * 
 * <pre> -S &lt;num&gt;
 *  Random number seed.
 *  (default 1)</pre>
 * 
 * <pre> -D
 *  If set, classifier is run in debug mode and
 *  may output additional info to the console</pre>
 * 
 * <pre> -W
 *  Full name of base classifier.
 *  (default: weka.classifiers.functions.LinearRegression)</pre>
 * 
 * <pre> 
 * Options specific to classifier weka.classifiers.functions.LinearRegression:
 * </pre>
 * 
 * <pre> -D
 *  Produce debugging output.
 *  (default no debugging output)</pre>
 * 
 * <pre> -S &lt;number of selection method&gt;
 *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
 *  (default 0 = M5' method)</pre>
 * 
 * <pre> -C
 *  Do not try to eliminate colinear attributes.
 * </pre>
 * 
 * <pre> -R &lt;double&gt;
 *  Set ridge parameter (default 1.0e-8).
 * </pre>
 * 
 * <pre> 
 * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
 * </pre>
 * 
 * <pre> -D
 *  Turns on output of debugging information.</pre>
 * 
 * <pre> -C &lt;num&gt;
 *  The number of components to compute.
 *  (default: 20)</pre>
 * 
 * <pre> -U
 *  Updates the class attribute as well.
 *  (default: off)</pre>
 * 
 * <pre> -M
 *  Turns replacing of missing values on.
 *  (default: off)</pre>
 * 
 * <pre> -A &lt;SIMPLS|PLS1&gt;
 *  The algorithm to use.
 *  (default: PLS1)</pre>
 * 
 * <pre> -P &lt;none|center|standardize&gt;
 *  The type of preprocessing that is applied to the data.
 *  (default: center)</pre>
 * 
 <!-- options-end -->
 *
 * Examples:
 * <ul>
 *   <li>
 *     <b>Optimizing SMO with RBFKernel (C and gamma)</b>
 *     <ul>
 *       <li>Set the evaluation to <i>Accuracy</i>.</li>
 *       <li>Set the filter to <code>weka.filters.AllFilter</code> since we
 *           don't need any special data processing and we don't optimize the
 *           filter in this case (data gets always passed through filter!).</li>
 *       <li>Set <code>weka.classifiers.functions.SMO</code> as classifier
 *           with <code>weka.classifiers.functions.supportVector.RBFKernel</code>
 *           as kernel.
 *       </li>
 *       <li>Set the XProperty to "classifier.c", XMin to "1", XMax to "16", 
 *           XStep to "1" and the XExpression to "I". This will test the "C"
 *           parameter of SMO for the values from 1 to 16.</li>
 *       <li>Set the YProperty to "classifier.kernel.gamma", YMin to "-5",
 *           YMax to "2", YStep to "1" YBase to "10" and YExpression to 
 *           "pow(BASE,I)". This will test the gamma of the RBFKernel with the
 *           values 10^-5, 10^-4,..,10^2.</li>
 *     </ul>
 *   </li>
 *   <li>
 *     <b>Optimizing PLSFilter with LinearRegression (# of components and ridge) - default setup</b>
 *     <ul>
 *       <li>Set the evaluation to <i>Correlation coefficient</i>.</li>
 *       <li>Set the filter to <code>weka.filters.supervised.attribute.PLSFilter</code>.</li>
 *       <li>Set <code>weka.classifiers.functions.LinearRegression</code> as 
 *           classifier and use no attribute selection and no elimination of
 *           colinear attributes.</li>
 *       <li>Set the XProperty to "filter.numComponents", XMin to "5", XMax 
 *           to "20" (this depends heavily on your dataset, should be no more
 *           than the number of attributes!), XStep to "1" and XExpression to
 *           "I". This will test the number of components the PLSFilter will
 *           produce from 5 to 20.</li>
 *       <li>Set the YProperty to "classifier.ridge", XMin to "-10", XMax to 
 *           "5", YStep to "1" and YExpression to "pow(BASE,I)". This will
 *           try ridge parameters from 10^-10 to 10^5.</li>
 *     </ul>
 *   </li>
 * </ul>
 * 
 * General notes:
 * <ul>
 *   <li>Turn the <i>debug</i> flag on in order to see some progress output in the
 *       console</li>
 *   <li>If you want to view the fitness landscape that GridSearch explores,
 *       select a <i>log file</i>. This log will then contain Gnuplot data and 
 *       script block for viewing the landscape. Just copy paste those blocks 
 *       into files named accordingly and run Gnuplot with them.</li>
 * </ul>
 *
 * @author  Bernhard Pfahringer (bernhard at cs dot waikato dot ac dot nz)
 * @author  Geoff Holmes (geoff at cs dot waikato dot ac dot nz)
 * @author  fracpete (fracpete at waikato dot ac dot nz)
 * @version $Revision: 1.9 $
 * @see     PLSFilter
 * @see     LinearRegression
 * @see         NumericCleaner
 */
00299 public class GridSearch
  extends RandomizableSingleClassifierEnhancer
  implements AdditionalMeasureProducer, Summarizable {

  /**
   * a serializable version of Point2D.Double
   * 
   * @see java.awt.geom.Point2D.Double
   */
00308   protected class PointDouble
    extends java.awt.geom.Point2D.Double
    implements Serializable, RevisionHandler {

    /** for serialization */
00313     private static final long serialVersionUID = 7151661776161898119L;
    
    /**
     * the default constructor
     * 
     * @param x         the x value of the point
     * @param y         the y value of the point
     */
00321     public PointDouble(double x, double y) {
      super(x, y);
    }

    /**
     * Determines whether or not two points are equal.
     * 
     * @param obj       an object to be compared with this PointDouble
     * @return          true if the object to be compared has the same values; 
     *                  false otherwise.
     */
00332     public boolean equals(Object obj) {
      PointDouble       pd;
      
      pd = (PointDouble) obj;
      
      return (Utils.eq(this.getX(), pd.getX()) && Utils.eq(this.getY(), pd.getY()));
    }
    
    /**
     * returns a string representation of the Point
     * 
     * @return the point as string
     */
00345     public String toString() {
      return super.toString().replaceAll(".*\\[", "[");
    }
    
    /**
     * Returns the revision string.
     * 
     * @return          the revision
     */
00354     public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.9 $");
    }
  }

  /**
   * a serializable version of Point
   * 
   * @see java.awt.Point
   */
00364   protected class PointInt
    extends java.awt.Point
    implements Serializable, RevisionHandler {

    /** for serialization */
00369     private static final long serialVersionUID = -5900415163698021618L;

    /**
     * the default constructor
     * 
     * @param x         the x value of the point
     * @param y         the y value of the point
     */
00377     public PointInt(int x, int y) {
      super(x, y);
    }
    
    /**
     * returns a string representation of the Point
     * 
     * @return the point as string
     */
00386     public String toString() {
      return super.toString().replaceAll(".*\\[", "[");
    }
    
    /**
     * Returns the revision string.
     * 
     * @return          the revision
     */
00395     public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.9 $");
    }
  }
  
  /**
   * for generating the parameter pairs in a grid
   */
00403   protected class Grid
    implements Serializable, RevisionHandler {

    /** for serialization */
00407     private static final long serialVersionUID = 7290732613611243139L;
    
    /** the minimum on the X axis */
00410     protected double m_MinX;
    
    /** the maximum on the X axis */
00413     protected double m_MaxX;
    
    /** the step size for the X axis */
00416     protected double m_StepX;

    /** the label for the X axis */
00419     protected String m_LabelX;
    
    /** the minimum on the Y axis */
00422     protected double m_MinY;
    
    /** the maximum on the Y axis */
00425     protected double m_MaxY;
    
    /** the step size for the Y axis */
00428     protected double m_StepY;

    /** the label for the Y axis */
00431     protected String m_LabelY;
    
    /** the number of points on the X axis */
00434     protected int m_Width;
    
    /** the number of points on the Y axis */
00437     protected int m_Height;
    
    /**
     * initializes the grid
     * 
     * @param minX      the minimum on the X axis
     * @param maxX      the maximum on the X axis
     * @param stepX     the step size for the X axis
     * @param minY      the minimum on the Y axis
     * @param maxY      the maximum on the Y axis
     * @param stepY     the step size for the Y axis
     */
00449     public Grid(double minX, double maxX, double stepX, 
              double minY, double maxY, double stepY) {
      this(minX, maxX, stepX, "", minY, maxY, stepY, "");
    }

    
    /**
     * initializes the grid
     * 
     * @param minX      the minimum on the X axis
     * @param maxX      the maximum on the X axis
     * @param stepX     the step size for the X axis
     * @param labelX    the label for the X axis
     * @param minY      the minimum on the Y axis
     * @param maxY      the maximum on the Y axis
     * @param stepY     the step size for the Y axis
     * @param labelY    the label for the Y axis
     */
00467     public Grid(double minX, double maxX, double stepX, String labelX,
              double minY, double maxY, double stepY, String labelY) {

      super();
      
      m_MinX   = minX;
      m_MaxX   = maxX;
      m_StepX  = stepX;
      m_LabelX = labelX;
      m_MinY   = minY;
      m_MaxY   = maxY;
      m_StepY  = stepY;
      m_LabelY = labelY;
      m_Height = (int) StrictMath.round((m_MaxY - m_MinY) / m_StepY) + 1;
      m_Width  = (int) StrictMath.round((m_MaxX - m_MinX) / m_StepX) + 1;
      
      // is min < max?
      if (m_MinX >= m_MaxX)
      throw new IllegalArgumentException("XMin must be smaller than XMax!");
      if (m_MinY >= m_MaxY)
      throw new IllegalArgumentException("YMin must be smaller than YMax!");
      
      // steps positive?
      if (m_StepX <= 0)
      throw new IllegalArgumentException("XStep must be a positive number!");
      if (m_StepY <= 0)
      throw new IllegalArgumentException("YStep must be a positive number!");
      
      // check borders
      if (!Utils.eq(m_MinX + (m_Width-1)*m_StepX, m_MaxX))
      throw new IllegalArgumentException(
          "X axis doesn't match! Provided max: " + m_MaxX 
          + ", calculated max via min and step size: " 
          + (m_MinX + (m_Width-1)*m_StepX));
      if (!Utils.eq(m_MinY + (m_Height-1)*m_StepY, m_MaxY))
      throw new IllegalArgumentException(
          "Y axis doesn't match! Provided max: " + m_MaxY 
          + ", calculated max via min and step size: " 
          + (m_MinY + (m_Height-1)*m_StepY));
    }

    /**
     * Tests itself against the provided grid object
     * 
     * @param o         the grid object to compare against
     * @return          if the two grids have the same setup
     */
00514     public boolean equals(Object o) {
      boolean     result;
      Grid  g;
      
      g = (Grid) o;
      
      result =    (width() == g.width())
               && (height() == g.height())
               && (getMinX() == g.getMinX())
               && (getMinY() == g.getMinY())
               && (getStepX() == g.getStepX())
               && (getStepY() == g.getStepY())
               && getLabelX().equals(g.getLabelX())
               && getLabelY().equals(g.getLabelY());
      
      return result;
    }
    
    /**
     * returns the left border
     * 
     * @return          the left border
     */
00537     public double getMinX() {
      return m_MinX;
    }
    
    /**
     * returns the right border
     * 
     * @return          the right border
     */
00546     public double getMaxX() {
      return m_MaxX;
    }
    
    /**
     * returns the step size on the X axis
     * 
     * @return          the step size
     */
00555     public double getStepX() {
      return m_StepX;
    }
    
    /**
     * returns the label for the X axis
     * 
     * @return          the label
     */
00564     public String getLabelX() {
      return m_LabelX;
    }
    
    /**
     * returns the bottom border
     * 
     * @return          the bottom border
     */
00573     public double getMinY() {
      return m_MinY;
    }
    
    /**
     * returns the top border
     * 
     * @return          the top border
     */
00582     public double getMaxY() {
      return m_MaxY;
    }
    
    /**
     * returns the step size on the Y axis
     * 
     * @return          the step size
     */
00591     public double getStepY() {
      return m_StepY;
    }
    
    /**
     * returns the label for the Y axis
     * 
     * @return          the label
     */
00600     public String getLabelY() {
      return m_LabelY;
    }
    
    /**
     * returns the number of points in the grid on the Y axis (incl. borders)
     * 
     * @return          the number of points in the grid on the Y axis
     */
00609     public int height() {
      return m_Height;
    }
    
    /**
     * returns the number of points in the grid on the X axis (incl. borders)
     * 
     * @return          the number of points in the grid on the X axis
     */
00618     public int width() {
      return m_Width;
    }

    /**
     * returns the values at the given point in the grid
     * 
     * @param x         the x-th point on the X axis
     * @param y         the y-th point on the Y axis
     * @return          the value pair at the given position
     */
00629     public PointDouble getValues(int x, int y) {
      if (x >= width())
      throw new IllegalArgumentException("Index out of scope on X axis (" + x + " >= " + width() + ")!");
      if (y >= height())
      throw new IllegalArgumentException("Index out of scope on Y axis (" + y + " >= " + height() + ")!");
      
      return new PointDouble(m_MinX + m_StepX*x, m_MinY + m_StepY*y);
    }

    /**
     * returns the closest index pair for the given value pair in the grid.
     * 
     * @param values    the values to get the indices for
     * @return          the closest indices in the grid
     */
00644     public PointInt getLocation(PointDouble values) {
      PointInt    result;
      int   x;
      int   y;
      double      distance;
      double      currDistance;
      int   i;

      // determine x
      x        = 0;
      distance = m_StepX;
      for (i = 0; i < width(); i++) {
      currDistance = StrictMath.abs(values.getX() - getValues(i, 0).getX());
      if (Utils.sm(currDistance, distance)) {
        distance = currDistance;
        x        = i;
      }
      }
      
      // determine y
      y        = 0;
      distance = m_StepY;
      for (i = 0; i < height(); i++) {
      currDistance = StrictMath.abs(values.getY() - getValues(0, i).getY());
      if (Utils.sm(currDistance, distance)) {
        distance = currDistance;
        y        = i;
      }
      }
      
      result = new PointInt(x, y);
      return result;
    }

    /**
     * checks whether the given values are on the border of the grid
     * 
     * @param values          the values to check
     * @return                true if the the values are on the border
     */
00684     public boolean isOnBorder(PointDouble values) {
      return isOnBorder(getLocation(values));
    }

    /**
     * checks whether the given location is on the border of the grid
     * 
     * @param location        the location to check
     * @return                true if the the location is on the border
     */
00694     public boolean isOnBorder(PointInt location) {
      if (location.getX() == 0)
      return true;
      else if (location.getX() == width() - 1)
      return true;
      if (location.getY() == 0)
      return true;
      else if (location.getY() == height() - 1)
      return true;
      else
      return false;
    }
    
    /**
     * returns a subgrid with the same step sizes, but different borders
     * 
     * @param top the top index
     * @param left      the left index
     * @param bottom    the bottom index
     * @param right     the right index
     * @return          the Sub-Grid
     */
00716     public Grid subgrid(int top, int left, int bottom, int right) {
      return new Grid(
                   getValues(left, top).getX(), getValues(right, top).getX(), getStepX(), getLabelX(),
                   getValues(left, bottom).getY(), getValues(left, top).getY(), getStepY(), getLabelY());
    }
    
    /**
     * returns an extended grid that encompasses the given point (won't be on
     * the border of the grid).
     * 
     * @param values    the point that the grid should contain
     * @return          the extended grid
     */
00729     public Grid extend(PointDouble values) {
      double      minX;
      double      maxX;
      double      minY;
      double      maxY;
      double      distance;
      Grid  result;
      
      // left
      if (Utils.smOrEq(values.getX(), getMinX())) {
      distance = getMinX() - values.getX();
      // exactly on grid point?
      if (Utils.eq(distance, 0))
        minX = getMinX() - getStepX() * (StrictMath.round(distance / getStepX()) + 1);
      else
        minX = getMinX() - getStepX() * (StrictMath.round(distance / getStepX()));
      }
      else {
      minX = getMinX();
      }
      
      // right
      if (Utils.grOrEq(values.getX(), getMaxX())) {
      distance = values.getX() - getMaxX();
      // exactly on grid point?
      if (Utils.eq(distance, 0))
        maxX = getMaxX() + getStepX() * (StrictMath.round(distance / getStepX()) + 1);
      else
        maxX = getMaxX() + getStepX() * (StrictMath.round(distance / getStepX()));
      }
      else {
      maxX = getMaxX();
      }
      
      // bottom
      if (Utils.smOrEq(values.getY(), getMinY())) {
      distance = getMinY() - values.getY();
      // exactly on grid point?
      if (Utils.eq(distance, 0))
        minY = getMinY() - getStepY() * (StrictMath.round(distance / getStepY()) + 1);
      else
        minY = getMinY() - getStepY() * (StrictMath.round(distance / getStepY()));
      }
      else {
      minY = getMinY();
      }
      
      // top
      if (Utils.grOrEq(values.getY(), getMaxY())) {
      distance = values.getY() - getMaxY();
      // exactly on grid point?
      if (Utils.eq(distance, 0))
        maxY = getMaxY() + getStepY() * (StrictMath.round(distance / getStepY()) + 1);
      else
        maxY = getMaxY() + getStepY() * (StrictMath.round(distance / getStepY()));
      }
      else {
      maxY = getMaxY();
      }
      
      result = new Grid(minX, maxX, getStepX(), getLabelX(), minY, maxY, getStepY(), getLabelY());
      
      // did the grid really extend?
      if (equals(result))
      throw new IllegalStateException("Grid extension failed!");
      
      return result;
    }
    
    /**
     * returns an Enumeration over all pairs in the given row
     * 
     * @param y         the row to retrieve
     * @return          an Enumeration over all pairs
     * @see #getValues(int, int)
     */
00805     public Enumeration<PointDouble> row(int y) {
      Vector      result;
      int   i;
      
      result = new Vector();
      
      for (i = 0; i < width(); i++)
      result.add(getValues(i, y));
      
      return result.elements();
    }
    
    /**
     * returns an Enumeration over all pairs in the given column
     * 
     * @param x         the column to retrieve
     * @return          an Enumeration over all pairs
     * @see #getValues(int, int)
     */
00824     public Enumeration<PointDouble> column(int x) {
      Vector      result;
      int   i;
      
      result = new Vector();
      
      for (i = 0; i < height(); i++)
      result.add(getValues(x, i));
      
      return result.elements();
    }
    
    /**
     * returns a string representation of the grid
     * 
     * @return a string representation
     */
00841     public String toString() {
      String      result;
      
      result  = "X: " + m_MinX + " - " + m_MaxX + ", Step " + m_StepX;
      if (m_LabelX.length() != 0)
      result += " (" + m_LabelX + ")";
      result += "\n";
      
      result += "Y: " + m_MinY + " - " + m_MaxY + ", Step " + m_StepY;
      if (m_LabelY.length() != 0)
      result += " (" + m_LabelY + ")";
      result += "\n";

      result += "Dimensions (Rows x Columns): " + height() + " x " + width();
      
      return result;
    }
    
    /**
     * Returns the revision string.
     * 
     * @return          the revision
     */
00864     public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.9 $");
    }
  }
  
  
  /**
   * A helper class for storing the performance of a values-pair.
   * Can be sorted with the PerformanceComparator class.
   * 
   * @see PerformanceComparator
   */
00876   protected class Performance
    implements Serializable, RevisionHandler {

    /** for serialization */
00880     private static final long serialVersionUID = -4374706475277588755L;
    
    /** the value pair the classifier was built with */
00883     protected PointDouble m_Values;
    
    /** the Correlation coefficient */
00886     protected double m_CC;
    
    /** the Root mean squared error */
00889     protected double m_RMSE;
    
    /** the Root relative squared error */
00892     protected double m_RRSE;
    
    /** the Mean absolute error */
00895     protected double m_MAE;
    
    /** the Relative absolute error */
00898     protected double m_RAE;
    
    /** the Accuracy */
00901     protected double m_ACC;
    
    /**
     * initializes the performance container
     * 
     * @param values          the values-pair
     * @param evaluation      the evaluation to extract the performance
     *                        measures from
     * @throws Exception      if retrieving of measures fails
     */
00911     public Performance(PointDouble values, Evaluation evaluation) throws Exception {
      super();
      
      m_Values = values;
      
      m_RMSE  = evaluation.rootMeanSquaredError();
      m_RRSE  = evaluation.rootRelativeSquaredError();
      m_MAE   = evaluation.meanAbsoluteError();
      m_RAE   = evaluation.relativeAbsoluteError();

      try {
      m_CC = evaluation.correlationCoefficient();
      }
      catch (Exception e) {
      m_CC = Double.NaN;
      }
      try {
      m_ACC = evaluation.pctCorrect();
      }
      catch (Exception e) {
      m_ACC = Double.NaN;
      }
    }
    
    /**
     * returns the performance measure
     * 
     * @param evaluation      the type of measure to return
     * @return                the performance measure
     */
00941     public double getPerformance(int evaluation) {
      double      result;
      
      result = Double.NaN;
      
      switch (evaluation) {
      case EVALUATION_CC:
        result = m_CC;
        break;
      case EVALUATION_RMSE:
        result = m_RMSE;
        break;
      case EVALUATION_RRSE:
        result = m_RRSE;
        break;
      case EVALUATION_MAE:
        result = m_MAE;
        break;
      case EVALUATION_RAE:
        result = m_RAE;
        break;
      case EVALUATION_COMBINED:
        result = (1 - StrictMath.abs(m_CC)) + m_RRSE + m_RAE;
        break;
      case EVALUATION_ACC:
        result = m_ACC;
        break;
      default:
        throw new IllegalArgumentException("Evaluation type '" + evaluation + "' not supported!");
      }
      
      return result;
    }
    
    /**
     * returns the values-pair for this performance
     * 
     * @return the values-pair
     */
00980     public PointDouble getValues() {
      return m_Values;
    }
    
    /**
     * returns a string representation of this performance object
     * 
     * @param evaluation      the type of performance to return
     * @return                a string representation
     */
00990     public String toString(int evaluation) {
      String      result;
      
      result =   "Performance (" + getValues() + "): " 
                   + getPerformance(evaluation) 
                   + " (" + new SelectedTag(evaluation, TAGS_EVALUATION) + ")";
      
      return result;
    }
    
    /**
     * returns a Gnuplot string of this performance object
     * 
     * @param evaluation      the type of performance to return
     * @return                the gnuplot string (x, y, z)
     */
01006     public String toGnuplot(int evaluation) {
      String      result;
      
      result =   getValues().getX() + "\t" 
                   + getValues().getY() + "\t"
                   + getPerformance(evaluation);
      
      return result;
    }
    
    /**
     * returns a string representation of this performance object
     * 
     * @return a string representation
     */
01021     public String toString() {
      String      result;
      int   i;
      
      result = "Performance (" + getValues() + "): ";
      
      for (i = 0; i < TAGS_EVALUATION.length; i++) {
      if (i > 0)
        result += ", ";
        result +=   getPerformance(TAGS_EVALUATION[i].getID()) 
              + " (" + new SelectedTag(TAGS_EVALUATION[i].getID(), TAGS_EVALUATION) + ")";
      }
      
      return result;
    }
    
    /**
     * Returns the revision string.
     * 
     * @return          the revision
     */
01042     public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.9 $");
    }
  }
  
  /**
   * A concrete Comparator for the Performance class.
   * 
   * @see Performance
   */
01052   protected class PerformanceComparator
    implements Comparator<Performance>, Serializable, RevisionHandler {
    
    /** for serialization */
01056     private static final long serialVersionUID = 6507592831825393847L;
    
    /** the performance measure to use for comparison 
     * @see GridSearch#TAGS_EVALUATION */
01060     protected int m_Evaluation;
    
    /**
     * initializes the comparator with the given performance measure
     * 
     * @param evaluation      the performance measure to use
     * @see GridSearch#TAGS_EVALUATION
     */
01068     public PerformanceComparator(int evaluation) {
      super();
      
      m_Evaluation = evaluation;
    }
    
    /**
     * returns the performance measure that's used to compare the objects
     * 
     * @return the performance measure
     * @see GridSearch#TAGS_EVALUATION
     */
01080     public int getEvaluation() {
      return m_Evaluation;
    }
    
    /**
     * Compares its two arguments for order. Returns a negative integer, 
     * zero, or a positive integer as the first argument is less than, 
     * equal to, or greater than the second.
     * 
     * @param o1  the first performance
     * @param o2  the second performance
     * @return          the order
     */
01093     public int compare(Performance o1, Performance o2) {
      int   result;
      double      p1;
      double      p2;
      
      p1 = o1.getPerformance(getEvaluation());
      p2 = o2.getPerformance(getEvaluation());
      
      if (Utils.sm(p1, p2))
      result = -1;
      else if (Utils.gr(p1, p2))
      result = 1;
      else
      result = 0;
      
      // only correlation coefficient and accuracy obey to this order, for the
      // errors (and the combination of all three), the smaller the number the
      // better -> hence invert them
      if (    (getEvaluation() != EVALUATION_CC) 
           && (getEvaluation() != EVALUATION_ACC) )
      result = -result;
      
      return result;
    }
    
    /**
     * Indicates whether some other object is "equal to" this Comparator.
     * 
     * @param obj the object to compare with
     * @return          true if the same evaluation type is used
     */
01124     public boolean equals(Object obj) {
      if (!(obj instanceof PerformanceComparator))
      throw new IllegalArgumentException("Must be PerformanceComparator!");
      
      return (m_Evaluation == ((PerformanceComparator) obj).m_Evaluation);
    }
    
    /**
     * Returns the revision string.
     * 
     * @return          the revision
     */
01136     public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.9 $");
    }
  }
  
  /**
   * Generates a 2-dim array for the performances from a grid for a certain 
   * type. x-min/y-min is in the bottom-left corner, i.e., getTable()[0][0]
   * returns the performance for the x-min/y-max pair.
   * <pre>
   * x-min     x-max
   * |-------------|
   *                - y-max
   *                |
   *                |
   *                - y-min
   * </pre>
   */
01154   protected class PerformanceTable 
    implements Serializable, RevisionHandler {
    
    /** for serialization */
01158     private static final long serialVersionUID = 5486491313460338379L;

    /** the corresponding grid */
01161     protected Grid m_Grid;
    
    /** the performances */
01164     protected Vector<Performance> m_Performances;
    
    /** the type of performance the table was generated for */
01167     protected int m_Type;
    
    /** the table with the values */
01170     protected double[][] m_Table;
    
    /** the minimum performance */
01173     protected double m_Min;
    
    /** the maximum performance */
01176     protected double m_Max;
    
    /**
     * initializes the table
     * 
     * @param grid            the underlying grid
     * @param performances    the performances
     * @param type            the type of performance
     */
01185     public PerformanceTable(Grid grid, Vector<Performance> performances, int type) {
      super();
      
      m_Grid         = grid;
      m_Type         = type;
      m_Performances = performances;
      
      generate();
    }
    
    /**
     * generates the table
     */
01198     protected void generate() {
      Performance       perf;
      int         i;
      PointInt          location;
      
      m_Table = new double[getGrid().height()][getGrid().width()];
      m_Min   = 0;
      m_Max   = 0;
      
      for (i = 0; i < getPerformances().size(); i++) {
      perf     = (Performance) getPerformances().get(i);
      location = getGrid().getLocation(perf.getValues());
      m_Table[getGrid().height() - (int) location.getY() - 1][(int) location.getX()] = perf.getPerformance(getType());
      
      // determine min/max
      if (i == 0) {
        m_Min = perf.getPerformance(m_Type);
        m_Max = m_Min;
      }
      else {
        if (perf.getPerformance(m_Type) < m_Min)
          m_Min = perf.getPerformance(m_Type);
        if (perf.getPerformance(m_Type) > m_Max)
          m_Max = perf.getPerformance(m_Type);
      }
      }
    }
    
    /**
     * returns the corresponding grid
     * 
     * @return          the underlying grid
     */
01231     public Grid getGrid() {
      return m_Grid;
    }

    /**
     * returns the underlying performances
     * 
     * @return          the underlying performances
     */
01240     public Vector<Performance> getPerformances() {
      return m_Performances;
    }
    
    /**
     * returns the type of performance
     * 
     * @return          the type of performance
     */
01249     public int getType() {
      return m_Type;
    }
    
    /**
     * returns the generated table
     * 
     * @return          the performance table
     * @see       #m_Table
     * @see       #generate()
     */
01260     public double[][] getTable() {
      return m_Table;
    }
    
    /**
     * the minimum performance
     * 
     * @return          the performance
     */
01269     public double getMin() {
      return m_Min;
    }
    
    /**
     * the maximum performance
     * 
     * @return          the performance
     */
01278     public double getMax() {
      return m_Max;
    }
    
    /**
     * returns the table as string
     * 
     * @return          the table as string
     */
01287     public String toString() {
      String      result;
      int   i;
      int   n;
      
      result =   "Table (" 
             + new SelectedTag(getType(), TAGS_EVALUATION).getSelectedTag().getReadable() 
               + ") - "
               + "X: " + getGrid().getLabelX() + ", Y: " + getGrid().getLabelY()
               + ":\n";
      
      for (i = 0; i < getTable().length; i++) {
      if (i > 0)
        result += "\n";
      
      for (n = 0; n < getTable()[i].length; n++) {
        if (n > 0)
          result += ",";
        result += getTable()[i][n];
      }
      }
      
      return result;
    }
    
    /**
     * returns a string containing a gnuplot script+data file
     * 
     * @return          the data in gnuplot format
     */
01317     public String toGnuplot() {
      StringBuffer      result;
      Tag         type;
      int         i;
      
      result = new StringBuffer();
      type   = new SelectedTag(getType(), TAGS_EVALUATION).getSelectedTag();
      
      result.append("Gnuplot (" + type.getReadable() + "):\n");
      result.append("# begin 'gridsearch.data'\n");
      result.append("# " + type.getReadable() + "\n");
      for (i = 0; i < getPerformances().size(); i++)
        result.append(getPerformances().get(i).toGnuplot(type.getID()) + "\n");
      result.append("# end 'gridsearch.data'\n\n");
      
      result.append("# begin 'gridsearch.plot'\n");
      result.append("# " + type.getReadable() + "\n");
      result.append("set data style lines\n");
      result.append("set contour base\n");
      result.append("set surface\n");
      result.append("set title '" + m_Data.relationName() + "'\n");
      result.append("set xrange [" + getGrid().getMinX() + ":" + getGrid().getMaxX() + "]\n");
      result.append("set xlabel 'x (" + getFilter().getClass().getName() + ": " + getXProperty() + ")'\n");
      result.append("set yrange [" + getGrid().getMinY() + ":" + getGrid().getMaxY() + "]\n");
      result.append("set ylabel 'y - (" + getClassifier().getClass().getName() + ": " + getYProperty() + ")'\n");
      result.append("set zrange [" + (getMin() - (getMax() - getMin())*0.1) + ":" + (getMax() + (getMax() - getMin())*0.1) + "]\n");
      result.append("set zlabel 'z - " + type.getReadable() + "'\n");
      result.append("set dgrid3d " + getGrid().height() + "," + getGrid().width() + ",1\n");
      result.append("show contour\n");
      result.append("splot 'gridsearch.data'\n");
      result.append("pause -1\n");
      result.append("# end 'gridsearch.plot'");

      return result.toString();
    }
    
    /**
     * Returns the revision string.
     * 
     * @return          the revision
     */
01358     public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.9 $");
    }
  }
  
  /**
   * Represents a simple cache for performance objects.
   */
01366   protected class PerformanceCache
    implements Serializable, RevisionHandler {

    /** for serialization */
01370     private static final long serialVersionUID = 5838863230451530252L;
    
    /** the cache for points in the grid that got calculated */
01373     protected Hashtable m_Cache = new Hashtable();
    
    /**
     * returns the ID string for a cache item
     * 
     * @param cv        the number of folds in the cross-validation
     * @param values    the point in the grid
     * @return          the ID string
     */
01382     protected String getID(int cv, PointDouble values) {
      return cv + "\t" + values.getX() + "\t" + values.getY();
    }
    
    /**
     * checks whether the point was already calculated ones
     * 
     * @param cv  the number of folds in the cross-validation
     * @param values    the point in the grid
     * @return          true if the value is already cached
     */
01393     public boolean isCached(int cv, PointDouble values) {
      return (get(cv, values) != null);
    }
    
    /**
     * returns a cached performance object, null if not yet in the cache
     * 
     * @param cv  the number of folds in the cross-validation
     * @param values    the point in the grid
     * @return          the cached performance item, null if not in cache
     */
01404     public Performance get(int cv, PointDouble values) {
      return (Performance) m_Cache.get(getID(cv, values));
    }
    
    /**
     * adds the performance to the cache
     * 
     * @param cv  the number of folds in the cross-validation
     * @param p         the performance object to store
     */
01414     public void add(int cv, Performance p) {
      m_Cache.put(getID(cv, p.getValues()), p);
    }
    
    /**
     * returns a string representation of the cache
     * 
     * @return          the string representation of the cache
     */
01423     public String toString() {
      return m_Cache.toString();
    }
    
    /**
     * Returns the revision string.
     * 
     * @return          the revision
     */
01432     public String getRevision() {
      return RevisionUtils.extract("$Revision: 1.9 $");
    }
  }
  
  /** for serialization */
01438   private static final long serialVersionUID = -3034773968581595348L;

  /** evaluation via: Correlation coefficient */
01441   public static final int EVALUATION_CC = 0;
  /** evaluation via: Root mean squared error */
01443   public static final int EVALUATION_RMSE = 1;
  /** evaluation via: Root relative squared error */
01445   public static final int EVALUATION_RRSE = 2;
  /** evaluation via: Mean absolute error */
01447   public static final int EVALUATION_MAE = 3;
  /** evaluation via: Relative absolute error */
01449   public static final int EVALUATION_RAE = 4;
  /** evaluation via: Combined = (1-CC) + RRSE + RAE */
01451   public static final int EVALUATION_COMBINED = 5;
  /** evaluation via: Accuracy */
01453   public static final int EVALUATION_ACC = 6;
  /** evaluation */
01455   public static final Tag[] TAGS_EVALUATION = {
    new Tag(EVALUATION_CC, "CC", "Correlation coefficient"),
    new Tag(EVALUATION_RMSE, "RMSE", "Root mean squared error"),
    new Tag(EVALUATION_RRSE, "RRSE", "Root relative squared error"),
    new Tag(EVALUATION_MAE, "MAE", "Mean absolute error"),
    new Tag(EVALUATION_RAE, "RAE", "Root absolute error"),
    new Tag(EVALUATION_COMBINED, "COMB", "Combined = (1-abs(CC)) + RRSE + RAE"),
    new Tag(EVALUATION_ACC, "ACC", "Accuracy")
  };
  
  /** row-wise grid traversal */
01466   public static final int TRAVERSAL_BY_ROW = 0;
  /** column-wise grid traversal */
01468   public static final int TRAVERSAL_BY_COLUMN = 1;
  /** traversal */
01470   public static final Tag[] TAGS_TRAVERSAL = {
    new Tag(TRAVERSAL_BY_ROW, "row-wise", "row-wise"),
    new Tag(TRAVERSAL_BY_COLUMN, "column-wise", "column-wise")
  };

  /** the prefix to indicate that the option is for the classifier */
01476   public final static String PREFIX_CLASSIFIER = "classifier.";

  /** the prefix to indicate that the option is for the filter */
01479   public final static String PREFIX_FILTER = "filter.";
  
  /** the Filter */
01482   protected Filter m_Filter;
  
  /** the Filter with the best setup */
01485   protected Filter m_BestFilter;
  
  /** the Classifier with the best setup */
01488   protected Classifier m_BestClassifier;

  /** the best values */
01491   protected PointDouble m_Values = null;
  
  /** the type of evaluation */
01494   protected int m_Evaluation = EVALUATION_CC;

  /** the Y option to work on (without leading dash, preceding 'classifier.' 
   * means to set the option for the classifier 'filter.' for the filter) */
01498   protected String m_Y_Property = PREFIX_CLASSIFIER + "ridge";
  
  /** the minimum of Y */
01501   protected double m_Y_Min = -10;
  
  /** the maximum of Y */
01504   protected double m_Y_Max = +5;
  
  /** the step size of Y */
01507   protected double m_Y_Step = 1;
  
  /** the base for Y */
01510   protected double m_Y_Base = 10;
  
  /** 
   * The expression for the Y property. Available parameters for the
   * expression:
   * <ul>
   *   <li>BASE</li>
   *   <li>FROM (= min)</li>
   *   <li>TO (= max)</li>
   *   <li>STEP</li>
   *   <li>I - the current value (from 'from' to 'to' with stepsize 'step')</li>
   * </ul>
   * 
   * @see MathematicalExpression
   * @see MathExpression
   */
01526   protected String m_Y_Expression = "pow(BASE,I)";

  /** the X option to work on (without leading dash, preceding 'classifier.' 
   * means to set the option for the classifier 'filter.' for the filter) */
01530   protected String m_X_Property = PREFIX_FILTER + "numComponents";
  
  /** the minimum of X */
01533   protected double m_X_Min = +5;
  
  /** the maximum of X */
01536   protected double m_X_Max = +20;
  
  /** the step size of  */
01539   protected double m_X_Step = 1;
  
  /** the base for  */
01542   protected double m_X_Base = 10;
  
  /** 
   * The expression for the X property. Available parameters for the
   * expression:
   * <ul>
   *   <li>BASE</li>
   *   <li>FROM (= min)</li>
   *   <li>TO (= max)</li>
   *   <li>STEP</li>
   *   <li>I - the current value (from 'from' to 'to' with stepsize 'step')</li>
   * </ul>
   * 
   * @see MathematicalExpression
   * @see MathExpression
   */
01558   protected String m_X_Expression = "I";

  /** whether the grid can be extended */
01561   protected boolean m_GridIsExtendable = false;
  
  /** maximum number of grid extensions (-1 means unlimited) */
01564   protected int m_MaxGridExtensions = 3;
  
  /** the number of extensions performed */
01567   protected int m_GridExtensionsPerformed = 0;

  /** the sample size to search the initial grid with */
01570   protected double m_SampleSize = 100;
  
  /** the traversal */
01573   protected int m_Traversal = TRAVERSAL_BY_COLUMN;

  /** the log file to use */
01576   protected File m_LogFile = new File(System.getProperty("user.dir"));
  
  /** the value-pairs grid */
01579   protected Grid m_Grid;

  /** the training data */
01582   protected Instances m_Data;

  /** the cache for points in the grid that got calculated */
01585   protected PerformanceCache m_Cache;

  /** whether all performances in the grid are the same */
01588   protected boolean m_UniformPerformance = false;
  
  /**
   * the default constructor
   */
01593   public GridSearch() {
    super();
    
    // classifier
    m_Classifier = new LinearRegression();
    ((LinearRegression) m_Classifier).setAttributeSelectionMethod(new SelectedTag(LinearRegression.SELECTION_NONE, LinearRegression.TAGS_SELECTION));
    ((LinearRegression) m_Classifier).setEliminateColinearAttributes(false);
    
    // filter
    m_Filter = new PLSFilter();
    PLSFilter filter = new PLSFilter();
    filter.setPreprocessing(new SelectedTag(PLSFilter.PREPROCESSING_STANDARDIZE, PLSFilter.TAGS_PREPROCESSING));
    filter.setReplaceMissing(true);
    
    try {
      m_BestClassifier = Classifier.makeCopy(m_Classifier);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
    try {
      m_BestFilter = Filter.makeCopy(filter);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  /**
   * Returns a string describing classifier
   * 
   * @return a description suitable for displaying in the
   *         explorer/experimenter gui
   */
01627   public String globalInfo() {
    return 
        "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.\n\n"
      + "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.\n"
      + "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 <num>').\n\n"
      + "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.\n\n"
      + "The best filter/classifier setup can be accessed after the buildClassifier "
      + "call via the getBestFilter/getBestClassifier methods.\n"
      + "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.";
  }

  /**
   * String describing default classifier.
   * 
   * @return            the classname of the default classifier
   */
01660   protected String defaultClassifierString() {
    return LinearRegression.class.getName();
  }

  /**
   * Gets an enumeration describing the available options.
   *
   * @return an enumeration of all the available options.
   */
01669   public Enumeration listOptions(){
    Vector              result;
    Enumeration         en;
    String        desc;
    SelectedTag         tag;
    int                 i;

    result = new Vector();

    desc  = "";
    for (i = 0; i < TAGS_EVALUATION.length; i++) {
      tag = new SelectedTag(TAGS_EVALUATION[i].getID(), TAGS_EVALUATION);
      desc  +=   "\t" + tag.getSelectedTag().getIDStr() 
                   + " = " + tag.getSelectedTag().getReadable()
                   + "\n";
    }
    result.addElement(new Option(
      "\tDetermines the parameter used for evaluation:\n"
      + desc
      + "\t(default: " + new SelectedTag(EVALUATION_CC, TAGS_EVALUATION) + ")",
      "E", 1, "-E " + Tag.toOptionList(TAGS_EVALUATION)));

    result.addElement(new Option(
      "\tThe Y option to test (without leading dash).\n"
      + "\t(default: " + PREFIX_CLASSIFIER + "ridge)",
      "y-property", 1, "-y-property <option>"));

    result.addElement(new Option(
      "\tThe minimum for Y.\n"
      + "\t(default: -10)",
      "y-min", 1, "-y-min <num>"));

    result.addElement(new Option(
      "\tThe maximum for Y.\n"
      + "\t(default: +5)",
      "y-max", 1, "-y-max <num>"));

    result.addElement(new Option(
      "\tThe step size for Y.\n"
      + "\t(default: 1)",
      "y-step", 1, "-y-step <num>"));

    result.addElement(new Option(
      "\tThe base for Y.\n"
      + "\t(default: 10)",
      "y-base", 1, "-y-base <num>"));

    result.addElement(new Option(
      "\tThe expression for Y.\n"
      + "\tAvailable parameters:\n"
      + "\t\tBASE\n"
      + "\t\tFROM\n"
      + "\t\tTO\n"
      + "\t\tSTEP\n"
      + "\t\tI - the current iteration value\n"
      + "\t\t(from 'FROM' to 'TO' with stepsize 'STEP')\n"
      + "\t(default: 'pow(BASE,I)')",
      "y-expression", 1, "-y-expression <expr>"));

    result.addElement(new Option(
      "\tThe filter to use (on X axis). Full classname of filter to include, \n"
      + "\tfollowed by scheme options.\n"
      + "\t(default: weka.filters.supervised.attribute.PLSFilter)",
      "filter", 1, "-filter <filter specification>"));

    result.addElement(new Option(
      "\tThe X option to test (without leading dash).\n"
      + "\t(default: " + PREFIX_FILTER + "numComponents)",
      "x-property", 1, "-x-property <option>"));

    result.addElement(new Option(
      "\tThe minimum for X.\n"
      + "\t(default: +5)",
      "x-min", 1, "-x-min <num>"));

    result.addElement(new Option(
      "\tThe maximum for X.\n"
      + "\t(default: +20)",
      "x-max", 1, "-x-max <num>"));

    result.addElement(new Option(
      "\tThe step size for X.\n"
      + "\t(default: 1)",
      "x-step", 1, "-x-step <num>"));

    result.addElement(new Option(
      "\tThe base for X.\n"
      + "\t(default: 10)",
      "x-base", 1, "-x-base <num>"));

    result.addElement(new Option(
      "\tThe expression for the X value.\n"
      + "\tAvailable parameters:\n"
      + "\t\tBASE\n"
      + "\t\tMIN\n"
      + "\t\tMAX\n"
      + "\t\tSTEP\n"
      + "\t\tI - the current iteration value\n"
      + "\t\t(from 'FROM' to 'TO' with stepsize 'STEP')\n"
      + "\t(default: 'pow(BASE,I)')",
      "x-expression", 1, "-x-expression <expr>"));

    result.addElement(new Option(
      "\tWhether the grid can be extended.\n"
      + "\t(default: no)",
      "extend-grid", 0, "-extend-grid"));

    result.addElement(new Option(
      "\tThe maximum number of grid extensions (-1 is unlimited).\n"
      + "\t(default: 3)",
      "max-grid-extensions", 1, "-max-grid-extensions <num>"));

    result.addElement(new Option(
      "\tThe size (in percent) of the sample to search the inital grid with.\n"
      + "\t(default: 100)",
      "sample-size", 1, "-sample-size <num>"));

    result.addElement(new Option(
      "\tThe type of traversal for the grid.\n"
      + "\t(default: " + new SelectedTag(TRAVERSAL_BY_COLUMN, TAGS_TRAVERSAL) + ")",
      "traversal", 1, "-traversal " + Tag.toOptionList(TAGS_TRAVERSAL)));

    result.addElement(new Option(
      "\tThe log file to log the messages to.\n"
      + "\t(default: none)",
      "log-file", 1, "-log-file <filename>"));

    en = super.listOptions();
    while (en.hasMoreElements())
      result.addElement(en.nextElement());

    if (getFilter() instanceof OptionHandler) {
      result.addElement(new Option(
        "",
        "", 0, "\nOptions specific to filter "
        + getFilter().getClass().getName() + " ('-filter'):"));
      
      en = ((OptionHandler) getFilter()).listOptions();
      while (en.hasMoreElements())
      result.addElement(en.nextElement());
    }


    return result.elements();
  }
  
  /**
   * returns the options of the current setup
   *
   * @return            the current options
   */
01820   public String[] getOptions(){
    int           i;
    Vector        result;
    String[]      options;

    result = new Vector();

    result.add("-E");
    result.add("" + getEvaluation());

    result.add("-y-property");
    result.add("" + getYProperty());

    result.add("-y-min");
    result.add("" + getYMin());

    result.add("-y-max");
    result.add("" + getYMax());

    result.add("-y-step");
    result.add("" + getYStep());

    result.add("-y-base");
    result.add("" + getYBase());

    result.add("-y-expression");
    result.add("" + getYExpression());

    result.add("-filter");
    if (getFilter() instanceof OptionHandler)
      result.add(
          getFilter().getClass().getName() 
        + " " 
        + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()));
    else
      result.add(
        getFilter().getClass().getName());

    result.add("-x-property");
    result.add("" + getXProperty());

    result.add("-x-min");
    result.add("" + getXMin());

    result.add("-x-max");
    result.add("" + getXMax());

    result.add("-x-step");
    result.add("" + getXStep());

    result.add("-x-base");
    result.add("" + getXBase());

    result.add("-x-expression");
    result.add("" + getXExpression());

    if (getGridIsExtendable()) {
      result.add("-extend-grid");
      result.add("-max-grid-extensions");
      result.add("" + getMaxGridExtensions());
    }
    
    result.add("-sample-size");
    result.add("" + getSampleSizePercent());

    result.add("-traversal");
    result.add("" + getTraversal());

    result.add("-log-file");
    result.add("" + getLogFile());

    options = super.getOptions();
    for (i = 0; i < options.length; i++)
      result.add(options[i]);

    return (String[]) result.toArray(new String[result.size()]);    
  }

  /**
   * Parses the options for this object. <p/>
   *
   <!-- options-start -->
   * Valid options are: <p/>
   * 
   * <pre> -E &lt;CC|RMSE|RRSE|MAE|RAE|COMB|ACC&gt;
   *  Determines the parameter used for evaluation:
   *  CC = Correlation coefficient
   *  RMSE = Root mean squared error
   *  RRSE = Root relative squared error
   *  MAE = Mean absolute error
   *  RAE = Root absolute error
   *  COMB = Combined = (1-abs(CC)) + RRSE + RAE
   *  ACC = Accuracy
   *  (default: CC)</pre>
   * 
   * <pre> -y-property &lt;option&gt;
   *  The Y option to test (without leading dash).
   *  (default: classifier.ridge)</pre>
   * 
   * <pre> -y-min &lt;num&gt;
   *  The minimum for Y.
   *  (default: -10)</pre>
   * 
   * <pre> -y-max &lt;num&gt;
   *  The maximum for Y.
   *  (default: +5)</pre>
   * 
   * <pre> -y-step &lt;num&gt;
   *  The step size for Y.
   *  (default: 1)</pre>
   * 
   * <pre> -y-base &lt;num&gt;
   *  The base for Y.
   *  (default: 10)</pre>
   * 
   * <pre> -y-expression &lt;expr&gt;
   *  The expression for Y.
   *  Available parameters:
   *   BASE
   *   FROM
   *   TO
   *   STEP
   *   I - the current iteration value
   *   (from 'FROM' to 'TO' with stepsize 'STEP')
   *  (default: 'pow(BASE,I)')</pre>
   * 
   * <pre> -filter &lt;filter specification&gt;
   *  The filter to use (on X axis). Full classname of filter to include, 
   *  followed by scheme options.
   *  (default: weka.filters.supervised.attribute.PLSFilter)</pre>
   * 
   * <pre> -x-property &lt;option&gt;
   *  The X option to test (without leading dash).
   *  (default: filter.numComponents)</pre>
   * 
   * <pre> -x-min &lt;num&gt;
   *  The minimum for X.
   *  (default: +5)</pre>
   * 
   * <pre> -x-max &lt;num&gt;
   *  The maximum for X.
   *  (default: +20)</pre>
   * 
   * <pre> -x-step &lt;num&gt;
   *  The step size for X.
   *  (default: 1)</pre>
   * 
   * <pre> -x-base &lt;num&gt;
   *  The base for X.
   *  (default: 10)</pre>
   * 
   * <pre> -x-expression &lt;expr&gt;
   *  The expression for the X value.
   *  Available parameters:
   *   BASE
   *   MIN
   *   MAX
   *   STEP
   *   I - the current iteration value
   *   (from 'FROM' to 'TO' with stepsize 'STEP')
   *  (default: 'pow(BASE,I)')</pre>
   * 
   * <pre> -extend-grid
   *  Whether the grid can be extended.
   *  (default: no)</pre>
   * 
   * <pre> -max-grid-extensions &lt;num&gt;
   *  The maximum number of grid extensions (-1 is unlimited).
   *  (default: 3)</pre>
   * 
   * <pre> -sample-size &lt;num&gt;
   *  The size (in percent) of the sample to search the inital grid with.
   *  (default: 100)</pre>
   * 
   * <pre> -traversal &lt;ROW-WISE|COLUMN-WISE&gt;
   *  The type of traversal for the grid.
   *  (default: COLUMN-WISE)</pre>
   * 
   * <pre> -log-file &lt;filename&gt;
   *  The log file to log the messages to.
   *  (default: none)</pre>
   * 
   * <pre> -S &lt;num&gt;
   *  Random number seed.
   *  (default 1)</pre>
   * 
   * <pre> -D
   *  If set, classifier is run in debug mode and
   *  may output additional info to the console</pre>
   * 
   * <pre> -W
   *  Full name of base classifier.
   *  (default: weka.classifiers.functions.LinearRegression)</pre>
   * 
   * <pre> 
   * Options specific to classifier weka.classifiers.functions.LinearRegression:
   * </pre>
   * 
   * <pre> -D
   *  Produce debugging output.
   *  (default no debugging output)</pre>
   * 
   * <pre> -S &lt;number of selection method&gt;
   *  Set the attribute selection method to use. 1 = None, 2 = Greedy.
   *  (default 0 = M5' method)</pre>
   * 
   * <pre> -C
   *  Do not try to eliminate colinear attributes.
   * </pre>
   * 
   * <pre> -R &lt;double&gt;
   *  Set ridge parameter (default 1.0e-8).
   * </pre>
   * 
   * <pre> 
   * Options specific to filter weka.filters.supervised.attribute.PLSFilter ('-filter'):
   * </pre>
   * 
   * <pre> -D
   *  Turns on output of debugging information.</pre>
   * 
   * <pre> -C &lt;num&gt;
   *  The number of components to compute.
   *  (default: 20)</pre>
   * 
   * <pre> -U
   *  Updates the class attribute as well.
   *  (default: off)</pre>
   * 
   * <pre> -M
   *  Turns replacing of missing values on.
   *  (default: off)</pre>
   * 
   * <pre> -A &lt;SIMPLS|PLS1&gt;
   *  The algorithm to use.
   *  (default: PLS1)</pre>
   * 
   * <pre> -P &lt;none|center|standardize&gt;
   *  The type of preprocessing that is applied to the data.
   *  (default: center)</pre>
   * 
   <!-- options-end -->
   *
   * @param options     the options to use
   * @throws Exception  if setting of options fails
   */
02066   public void setOptions(String[] options) throws Exception {
    String  tmpStr;
    String[]      tmpOptions;

    tmpStr = Utils.getOption('E', options);
    if (tmpStr.length() != 0)
      setEvaluation(new SelectedTag(tmpStr, TAGS_EVALUATION));
    else
      setEvaluation(new SelectedTag(EVALUATION_CC, TAGS_EVALUATION));
    
    tmpStr = Utils.getOption("y-property", options);
    if (tmpStr.length() != 0)
      setYProperty(tmpStr);
    else
      setYProperty(PREFIX_CLASSIFIER + "ridge");
    
    tmpStr = Utils.getOption("y-min", options);
    if (tmpStr.length() != 0)
      setYMin(Double.parseDouble(tmpStr));
    else
      setYMin(-10);
    
    tmpStr = Utils.getOption("y-max", options);
    if (tmpStr.length() != 0)
      setYMax(Double.parseDouble(tmpStr));
    else
      setYMax(10);
    
    tmpStr = Utils.getOption("y-step", options);
    if (tmpStr.length() != 0)
      setYStep(Double.parseDouble(tmpStr));
    else
      setYStep(1);
    
    tmpStr = Utils.getOption("y-base", options);
    if (tmpStr.length() != 0)
      setYBase(Double.parseDouble(tmpStr));
    else
      setYBase(10);
    
    tmpStr = Utils.getOption("y-expression", options);
    if (tmpStr.length() != 0)
      setYExpression(tmpStr);
    else
      setYExpression("pow(BASE,I)");
    
    tmpStr     = Utils.getOption("filter", options);
    tmpOptions = Utils.splitOptions(tmpStr);
    if (tmpOptions.length != 0) {
      tmpStr        = tmpOptions[0];
      tmpOptions[0] = "";
      setFilter((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
    }
    
    tmpStr = Utils.getOption("x-property", options);
    if (tmpStr.length() != 0)
      setXProperty(tmpStr);
    else
      setXProperty(PREFIX_FILTER + "filters[0].kernel.gamma");
    
    tmpStr = Utils.getOption("x-min", options);
    if (tmpStr.length() != 0)
      setXMin(Double.parseDouble(tmpStr));
    else
      setXMin(-10);
    
    tmpStr = Utils.getOption("x-max", options);
    if (tmpStr.length() != 0)
      setXMax(Double.parseDouble(tmpStr));
    else
      setXMax(10);
    
    tmpStr = Utils.getOption("x-step", options);
    if (tmpStr.length() != 0)
      setXStep(Double.parseDouble(tmpStr));
    else
      setXStep(1);
    
    tmpStr = Utils.getOption("x-base", options);
    if (tmpStr.length() != 0)
      setXBase(Double.parseDouble(tmpStr));
    else
      setXBase(10);
    
    tmpStr = Utils.getOption("x-expression", options);
    if (tmpStr.length() != 0)
      setXExpression(tmpStr);
    else
      setXExpression("pow(BASE,I)");
    
    setGridIsExtendable(Utils.getFlag("extend-grid", options));
    if (getGridIsExtendable()) {
      tmpStr = Utils.getOption("max-grid-extensions", options);
      if (tmpStr.length() != 0)
        setMaxGridExtensions(Integer.parseInt(tmpStr));
      else
        setMaxGridExtensions(3);
    }
    
    tmpStr = Utils.getOption("sample-size", options);
    if (tmpStr.length() != 0)
      setSampleSizePercent(Double.parseDouble(tmpStr));
    else
      setSampleSizePercent(100);
    
    tmpStr = Utils.getOption("traversal", options);
    if (tmpStr.length() != 0)
      setTraversal(new SelectedTag(tmpStr, TAGS_TRAVERSAL));
    else
      setTraversal(new SelectedTag(TRAVERSAL_BY_ROW, TAGS_TRAVERSAL));
    
    tmpStr = Utils.getOption("log-file", options);
    if (tmpStr.length() != 0)
      setLogFile(new File(tmpStr));
    else
      setLogFile(new File(System.getProperty("user.dir")));
    
    super.setOptions(options);
  }

  /**
   * Set the base learner.
   *
   * @param newClassifier     the classifier to use.
   */
02191   public void setClassifier(Classifier newClassifier) {
    boolean numeric;
    boolean nominal;
    
    Capabilities cap = newClassifier.getCapabilities();

    numeric =    cap.handles(Capability.NUMERIC_CLASS) 
            || cap.hasDependency(Capability.NUMERIC_CLASS);
    
    nominal =    cap.handles(Capability.NOMINAL_CLASS)
              || cap.hasDependency(Capability.NOMINAL_CLASS)
              || cap.handles(Capability.BINARY_CLASS)
              || cap.hasDependency(Capability.BINARY_CLASS)
              || cap.handles(Capability.UNARY_CLASS)
              || cap.hasDependency(Capability.UNARY_CLASS);
    
    if ((m_Evaluation == EVALUATION_CC) && !numeric)
      throw new IllegalArgumentException(
        "Classifier needs to handle numeric class for chosen type of evaluation!");

    if ((m_Evaluation == EVALUATION_ACC) && !nominal)
      throw new IllegalArgumentException(
        "Classifier needs to handle nominal class for chosen type of evaluation!");
    
    super.setClassifier(newClassifier);
    
    try {
      m_BestClassifier = Classifier.makeCopy(m_Classifier);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02231   public String filterTipText() {
    return "The filter to be used (only used for setup).";
  }

  /**
   * Set the kernel filter (only used for setup).
   *
   * @param value the kernel filter.
   */
02240   public void setFilter(Filter value) {
    m_Filter = value;

    try {
      m_BestFilter = Filter.makeCopy(m_Filter);
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Get the kernel filter.
   *
   * @return            the kernel filter
   */
02256   public Filter getFilter() {
    return m_Filter;
  }

  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02266   public String evaluationTipText() {
    return 
        "Sets the criterion for evaluating the classifier performance and "
      + "choosing the best one.";
  }

  /**
   * Sets the criterion to use for evaluating the classifier performance. 
   *
   * @param value       .the evaluation criterion
   */
02277   public void setEvaluation(SelectedTag value) {
    if (value.getTags() == TAGS_EVALUATION) {
      m_Evaluation = value.getSelectedTag().getID();
    }
  }

  /**
   * Gets the criterion used for evaluating the classifier performance. 
   *
   * @return            the current evaluation criterion.
   */
02288   public SelectedTag getEvaluation() {
    return new SelectedTag(m_Evaluation, TAGS_EVALUATION);
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02298   public String YPropertyTipText() {
    return "The Y property to test (normally the classifier).";
  }

  /**
   * Get the Y property (normally the classifier).
   *
   * @return            Value of the property.
   */
02307   public String getYProperty() {
    return m_Y_Property;
  }
  
  /**
   * Set the Y property (normally the classifier).
   *
   * @param value       the Y property.
   */
02316   public void setYProperty(String value) {
    m_Y_Property = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02326   public String YMinTipText() {
    return "The minimum of Y (normally the classifier).";
  }

  /**
   * Get the value of the minimum of Y.
   *
   * @return            Value of the minimum of Y.
   */
02335   public double getYMin() {
    return m_Y_Min;
  }
  
  /**
   * Set the value of the minimum of Y.
   *
   * @param value       Value to use as minimum of Y.
   */
02344   public void setYMin(double value) {
    m_Y_Min = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02354   public String YMaxTipText() {
    return "The maximum of Y.";
  }

  /**
   * Get the value of the Maximum of Y.
   *
   * @return            Value of the Maximum of Y.
   */
02363   public double getYMax() {
    return m_Y_Max;
  }
  
  /**
   * Set the value of the Maximum of Y.
   *
   * @param value       Value to use as Maximum of Y.
   */
02372   public void setYMax(double value) {
    m_Y_Max = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02382   public String YStepTipText() {
    return "The step size of Y.";
  }

  /**
   * Get the value of the step size for Y.
   *
   * @return            Value of the step size for Y.
   */
02391   public double getYStep() {
    return m_Y_Step;
  }
  
  /**
   * Set the value of the step size for Y.
   *
   * @param value       Value to use as the step size for Y.
   */
02400   public void setYStep(double value) {
    m_Y_Step = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02410   public String YBaseTipText() {
    return "The base of Y.";
  }

  /**
   * Get the value of the base for Y.
   *
   * @return            Value of the base for Y.
   */
02419   public double getYBase() {
    return m_Y_Base;
  }
  
  /**
   * Set the value of the base for Y.
   *
   * @param value Value to use as the base for Y.
   */
02428   public void setYBase(double value) {
    m_Y_Base = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02438   public String YExpressionTipText() {
    return "The expression for the Y value (parameters: BASE, FROM, TO, STEP, I).";
  }

  /**
   * Get the expression for the Y value.
   *
   * @return Expression for the Y value.
   */
02447   public String getYExpression() {
    return m_Y_Expression;
  }
  
  /**
   * Set the expression for the Y value.
   *
   * @param value Expression for the Y value.
   */
02456   public void setYExpression(String value) {
    m_Y_Expression = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02466   public String XPropertyTipText() {
    return "The X property to test (normally the filter).";
  }

  /**
   * Get the X property to test (normally the filter).
   *
   * @return            Value of the X property.
   */
02475   public String getXProperty() {
    return m_X_Property;
  }
  
  /**
   * Set the X property.
   *
   * @param value       the X property.
   */
02484   public void setXProperty(String value) {
    m_X_Property = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02494   public String XMinTipText() {
    return "The minimum of X.";
  }

  /**
   * Get the value of the minimum of X.
   *
   * @return Value of the minimum of X.
   */
02503   public double getXMin() {
    return m_X_Min;
  }
  
  /**
   * Set the value of the minimum of X.
   *
   * @param value Value to use as minimum of X.
   */
02512   public void setXMin(double value) {
    m_X_Min = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02522   public String XMaxTipText() {
    return "The maximum of X.";
  }

  /**
   * Get the value of the Maximum of X.
   *
   * @return Value of the Maximum of X.
   */
02531   public double getXMax() {
    return m_X_Max;
  }
  
  /**
   * Set the value of the Maximum of X.
   *
   * @param value Value to use as Maximum of X.
   */
02540   public void setXMax(double value) {
    m_X_Max = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02550   public String XStepTipText() {
    return "The step size of X.";
  }

  /**
   * Get the value of the step size for X.
   *
   * @return Value of the step size for X.
   */
02559   public double getXStep() {
    return m_X_Step;
  }
  
  /**
   * Set the value of the step size for X.
   *
   * @param value Value to use as the step size for X.
   */
02568   public void setXStep(double value) {
    m_X_Step = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02578   public String XBaseTipText() {
    return "The base of X.";
  }

  /**
   * Get the value of the base for X.
   *
   * @return Value of the base for X.
   */
02587   public double getXBase() {
    return m_X_Base;
  }
  
  /**
   * Set the value of the base for X.
   *
   * @param value Value to use as the base for X.
   */
02596   public void setXBase(double value) {
    m_X_Base = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02606   public String XExpressionTipText() {
    return "The expression for the X value (parameters: BASE, FROM, TO, STEP, I).";
  }

  /**
   * Get the expression for the X value.
   *
   * @return Expression for the X value.
   */
02615   public String getXExpression() {
    return m_X_Expression;
  }
  
  /**
   * Set the expression for the X value.
   *
   * @param value Expression for the X value.
   */
02624   public void setXExpression(String value) {
    m_X_Expression = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02634   public String gridIsExtendableTipText() {
    return "Whether the grid can be extended.";
  }

  /**
   * Get whether the grid can be extended dynamically.
   *
   * @return true if the grid can be extended.
   */
02643   public boolean getGridIsExtendable() {
    return m_GridIsExtendable;
  }
  
  /**
   * Set whether the grid can be extended dynamically.
   *
   * @param value whether the grid can be extended dynamically.
   */
02652   public void setGridIsExtendable(boolean value) {
    m_GridIsExtendable = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02662   public String maxGridExtensionsTipText() {
    return "The maximum number of grid extensions, -1 for unlimited.";
  }

  /**
   * Gets the maximum number of grid extensions, -1 for unlimited.
   *
   * @return the max number of grid extensions
   */
02671   public int getMaxGridExtensions() {
    return m_MaxGridExtensions;
  }
  
  /**
   * Sets the maximum number of grid extensions, -1 for unlimited.
   *
   * @param value the maximum of grid extensions.
   */
02680   public void setMaxGridExtensions(int value) {
    m_MaxGridExtensions = value;
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02690   public String sampleSizePercentTipText() {
    return "The sample size (in percent) to use in the initial grid search.";
  }

  /**
   * Gets the sample size for the initial grid search.
   *
   * @return the sample size.
   */
02699   public double getSampleSizePercent() {
    return m_SampleSize;
  }
  
  /**
   * Sets the sample size for the initial grid search.
   *
   * @param value the sample size for the initial grid search.
   */
02708   public void setSampleSizePercent(double value) {
    m_SampleSize = value;
  }

  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02718   public String traversalTipText() {
    return "Sets type of traversal of the grid, either by rows or columns.";
  }

  /**
   * Sets the type of traversal for the grid. 
   *
   * @param value       the traversal type
   */
02727   public void setTraversal(SelectedTag value) {
    if (value.getTags() == TAGS_TRAVERSAL) {
      m_Traversal = value.getSelectedTag().getID();
    }
  }

  /**
   * Gets the type of traversal for the grid. 
   *
   * @return            the current traversal type.
   */
02738   public SelectedTag getTraversal() {
    return new SelectedTag(m_Traversal, TAGS_TRAVERSAL);
  }
  
  /**
   * Returns the tip text for this property
   * 
   * @return            tip text for this property suitable for
   *              displaying in the explorer/experimenter gui
   */
02748   public String logFileTipText() {
    return "The log file to log the messages to.";
  }

  /**
   * Gets current log file.
   *
   * @return            the log file.
   */
02757   public File getLogFile() {
    return m_LogFile;
  }
  
  /**
   * Sets the log file to use.
   *
   * @param value       the log file.
   */
02766   public void setLogFile(File value) {
    m_LogFile = value;
  }

  /**
   * returns the best filter setup
   * 
   * @return            the best filter setup
   */
02775   public Filter getBestFilter() {
    return m_BestFilter;
  }

  /**
   * returns the best Classifier setup
   * 
   * @return            the best Classifier setup
   */
02784   public Classifier getBestClassifier() {
    return m_BestClassifier;
  }
  
  /**
   * Returns an enumeration of the measure names.
   * 
   * @return an enumeration of the measure names
   */
02793   public Enumeration enumerateMeasures() {
    Vector  result;
    
    result = new Vector();
    
    result.add("measureX");
    result.add("measureY");
    result.add("measureGridExtensionsPerformed");
    
    return result.elements();
  }

  /**
   * Returns the value of the named measure
   * 
   * @param measureName the name of the measure to query for its value
   * @return the value of the named measure
   */
02811   public double getMeasure(String measureName) {
    if (measureName.equalsIgnoreCase("measureX"))
      return evaluate(getValues().getX(), true);
    else if (measureName.equalsIgnoreCase("measureY"))
      return evaluate(getValues().getY(), false);
    else if (measureName.equalsIgnoreCase("measureGridExtensionsPerformed"))
      return getGridExtensionsPerformed();
    else
      throw new IllegalArgumentException("Measure '" + measureName + "' not supported!");
  }
  
  /**
   * returns the parameter pair that was found to work best
   * 
   * @return            the best parameter combination
   */
02827   public PointDouble getValues() {
    return m_Values;
  }

  /**
   * returns the number of grid extensions that took place during the search
   * (only applicable if the grid was extendable).
   * 
   * @return            the number of grid extensions that were performed
   * @see         #getGridIsExtendable()
   */
02838   public int getGridExtensionsPerformed() {
    return m_GridExtensionsPerformed;
  }
  
  /**
   * Returns default capabilities of the classifier.
   *
   * @return            the capabilities of this classifier
   */
02847   public Capabilities getCapabilities() {
    Capabilities  result;
    Capabilities  classes;
    Iterator            iter;
    Capability          capab;
    
    if (getFilter() == null)
      result = super.getCapabilities();
    else
      result = getFilter().getCapabilities();

    // only nominal and numeric classes allowed
    classes = result.getClassCapabilities();
    iter = classes.capabilities();
    while (iter.hasNext()) {
      capab = (Capability) iter.next();
      if (    (capab != Capability.BINARY_CLASS)
         && (capab != Capability.NOMINAL_CLASS)
         && (capab != Capability.NUMERIC_CLASS)
         && (capab != Capability.DATE_CLASS) )
      result.disable(capab);
    }
    
    // set dependencies
    for (Capability cap: Capability.values())
      result.enableDependency(cap);
    
    if (result.getMinimumNumberInstances() < 1)
      result.setMinimumNumberInstances(1);

    result.setOwner(this);
    
    return result;
  }

  /**
   * prints the specified message to stdout if debug is on and can also dump
   * the message to a log file
   * 
   * @param message     the message to print or store in a log file
   */
02888   protected void log(String message) {
    log(message, false);
  }

  /**
   * prints the specified message to stdout if debug is on and can also dump
   * the message to a log file
   * 
   * @param message     the message to print or store in a log file
   * @param onlyLog     if true the message will only be put into the log file
   *              but not to stdout
   */
02900   protected void log(String message, boolean onlyLog) {
    // print to stdout?
    if (getDebug() && (!onlyLog))
      System.out.println(message);
    
    // log file?
    if (!getLogFile().isDirectory())
      Debug.writeToFile(getLogFile().getAbsolutePath(), message, true);
  }
  
  /**
   * replaces the current option in the options array with a new value
   * 
   * @param options     the current options
   * @param option      the option to set a new value for
   * @param value the value to set
   * @return            the updated array
   * @throws Exception  if something goes wrong
   */
02919   protected String[] updateOption(String[] options, String option, String value) 
    throws Exception {
    
    String[]            result;
    Vector        tmpOptions;
    int                 i;

    // remove old option
    Utils.getOption(option, options);
    
    // add option with new value at the beginning (to avoid clashes with "--")
    tmpOptions = new Vector();
    tmpOptions.add("-" + option);
    tmpOptions.add("" + value);

    // move options into vector
    for (i = 0; i < options.length; i++) {
      if (options[i].length() != 0)
      tmpOptions.add(options[i]);
    }
    
    result = (String[]) tmpOptions.toArray(new String[tmpOptions.size()]);
    
    return result;
  }
  
  /**
   * evalutes the expression for the current iteration
   * 
   * @param value the current iteration value (from 'min' to 'max' with  
   *              stepsize 'step')
   * @param isX         true if X is to be evaluated otherwise Y
   * @return            the generated value, NaN if the evaluation fails
   */
02953   protected double evaluate(double value, boolean isX) {
    double  result;
    HashMap symbols;
    String  expr;
    double  base;
    double  min;
    double  max;
    double  step;

    if (isX) {
      expr = getXExpression();
      base = getXBase();
      min  = getXMin();
      max  = getXMax();
      step = getXStep();
    }
    else {
      expr = getYExpression();
      base = getYBase();
      min  = getYMin();
      max  = getYMax();
      step = getYStep();
    }

    try {
      symbols = new HashMap();
      symbols.put("BASE", new Double(base));
      symbols.put("FROM", new Double(min));
      symbols.put("TO",   new Double(max));
      symbols.put("STEP", new Double(step));
      symbols.put("I",    new Double(value));
      result = MathematicalExpression.evaluate(expr, symbols);
    }
    catch (Exception e) {
      result = Double.NaN;
    }
    
    return result;
  }

  /**
   * tries to set the value as double, integer (just casts it to int!) or
   * boolean (false if 0, otherwise true) in the object according to the 
   * specified path. float, char and long are also supported.
   * 
   * @param o           the object to modify
   * @param path  the property path
   * @param value the value to set
   * @return            the modified object
   * @throws Exception  if neither double nor int could be set
   */
03004   protected Object setValue(Object o, String path, double value) throws Exception {
    PropertyDescriptor  desc;
    Class         c;
    
    desc = PropertyPath.getPropertyDescriptor(o, path);
    c    = desc.getPropertyType();

    // float
    if ((c == Float.class) || (c == Float.TYPE))
      PropertyPath.setValue(o, path, new Float((float) value));
    // double
    else if ((c == Double.class) || (c == Double.TYPE))
      PropertyPath.setValue(o, path, new Double(value));
    // char
    else if ((c == Character.class) || (c == Character.TYPE))
      PropertyPath.setValue(o, path, new Integer((char) value));
    // int
    else if ((c == Integer.class) || (c == Integer.TYPE))
      PropertyPath.setValue(o, path, new Integer((int) value));
    // long
    else if ((c == Long.class) || (c == Long.TYPE))
      PropertyPath.setValue(o, path, new Long((long) value));
    // boolean
    else if ((c == Boolean.class) || (c == Boolean.TYPE))
      PropertyPath.setValue(o, path, (value == 0 ? new Boolean(false) : new Boolean(true)));
    else throw new Exception(
      "Could neither set double nor integer nor boolean value for '" + path + "'!");
    
    return o;
  }
  
  /**
   * returns a fully configures object (a copy of the provided one)
   * 
   * @param original    the object to create a copy from and set the parameters
   * @param valueX      the current iteration value for X 
   * @param valueY      the current iteration value for Y
   * @return            the configured classifier
   * @throws Exception  if setup fails
   */
03044   protected Object setup(Object original, double valueX, double valueY) throws Exception {
    Object  result;
    
    result = new SerializedObject(original).getObject();
    
    if (original instanceof Classifier) {
      if (getXProperty().startsWith(PREFIX_CLASSIFIER))
      setValue(
          result,
          getXProperty().substring(PREFIX_CLASSIFIER.length()),
          valueX);
      
      if (getYProperty().startsWith(PREFIX_CLASSIFIER))
      setValue(
          result,
          getYProperty().substring(PREFIX_CLASSIFIER.length()), 
          valueY);
    }
    else if (original instanceof Filter) {
      if (getXProperty().startsWith(PREFIX_FILTER))
      setValue(
          result,
          getXProperty().substring(PREFIX_FILTER.length()), 
          valueX);
      
      if (getYProperty().startsWith(PREFIX_FILTER))
      setValue(
          result,
          getYProperty().substring(PREFIX_FILTER.length()), 
          valueY);
    }
    else {
      throw new IllegalArgumentException("Object must be either classifier or filter!");
    }
    
    return result;
  }
  
  /**
   * generates a table string for all the performances in the grid and returns
   * that.
   * 
   * @param grid        the current grid to align the performances to
   * @param performances      the performances to align
   * @param type        the type of performance
   * @return                  the table string
   */
03091   protected String logPerformances(Grid grid, Vector<Performance> performances, Tag type) {
    StringBuffer  result;
    PerformanceTable    table;
    
    result = new StringBuffer(type.getReadable() + ":\n");
    table  = new PerformanceTable(grid, performances, type.getID());
    
    result.append(table.toString() + "\n");
    result.append("\n");
    result.append(table.toGnuplot() + "\n");
    result.append("\n");
    
    return result.toString();
  }
  
  /**
   * aligns all performances in the grid and prints those tables to the log
   * file.
   * 
   * @param grid        the current grid to align the performances to
   * @param performances      the performances to align
   */
03113   protected void logPerformances(Grid grid, Vector performances) {
    int           i;
    
    for (i = 0; i < TAGS_EVALUATION.length; i++)
      log("\n" + logPerformances(grid, performances, TAGS_EVALUATION[i]), true);
  }
  
  /**
   * determines the best values-pair for the given grid, using CV with 
   * specified number of folds.
   * 
   * @param grid  the grid to work on
   * @param inst  the data to work with
   * @param cv          the number of folds for the cross-validation
   * @return            the best values pair
   * @throws Exception  if setup or training fails
   */
03130   protected PointDouble determineBestInGrid(Grid grid, Instances inst, int cv) throws Exception {
    int                       i;
    Enumeration<PointDouble>  enm;
    Vector<Performance>       performances;
    PointDouble               values;
    Instances                 data;
    Evaluation                eval;
    PointDouble               result;
    Classifier                classifier;
    Filter              filter;
    int                       size;
    boolean             cached;
    boolean             allCached;
    Performance               p1;
    Performance               p2;
    double              x;
    double              y;
    
    performances = new Vector();
    
    log("Determining best pair with " + cv + "-fold CV in Grid:\n" + grid + "\n");
    
    if (m_Traversal == TRAVERSAL_BY_COLUMN)
      size = grid.width();
    else
      size = grid.height();
    
    allCached = true;

    for (i = 0; i < size; i++) {
      if (m_Traversal == TRAVERSAL_BY_COLUMN)
      enm = grid.column(i);
      else
      enm = grid.row(i);
      
      filter = null;
      data   = null;
      
      while (enm.hasMoreElements()) {
      values = enm.nextElement();
      
      // already calculated?
      cached = m_Cache.isCached(cv, values);
      if (cached) {
        performances.add(m_Cache.get(cv, values));
      }
      else {
        allCached = false;
        
        x = evaluate(values.getX(), true);
        y = evaluate(values.getY(), false);
        
        // data pass through filter
        if (filter == null) {
          filter = (Filter) setup(getFilter(), x, y);
          filter.setInputFormat(inst);
          data = Filter.useFilter(inst, filter);
          // make sure that the numbers don't get too small - otherwise NaNs!
          Filter cleaner = new NumericCleaner();
          cleaner.setInputFormat(data);
          data = Filter.useFilter(data, cleaner);
        }

        // setup classifier
        classifier = (Classifier) setup(getClassifier(), x, y);

        // evaluate
        eval = new Evaluation(data);
        eval.crossValidateModel(classifier, data, cv, new Random(getSeed()));
        performances.add(new Performance(values, eval));
        
        // add to cache
        m_Cache.add(cv, new Performance(values, eval));
      }

      log("" + performances.get(performances.size() - 1) + ": cached=" + cached);
      }
    }

    if (allCached) {
      log("All points were already cached - abnormal state!");
      throw new IllegalStateException("All points were already cached - abnormal state!");
    }
    
    // sort list
    Collections.sort(performances, new PerformanceComparator(m_Evaluation));

    result = performances.get(performances.size() - 1).getValues();

    // check whether all performances are the same
    m_UniformPerformance = true;
    p1 = performances.get(0);
    for (i = 1; i < performances.size(); i++) {
      p2 = performances.get(i);
      if (p2.getPerformance(m_Evaluation) != p1.getPerformance(m_Evaluation)) {
      m_UniformPerformance = false;
      break;
      }
    }
    if (m_UniformPerformance)
      log("All performances are the same!");
    
    logPerformances(grid, performances);
    log("\nBest performance:\n" + performances.get(performances.size() - 1));
    
    return result;
  }
  
  /**
   * returns the best values-pair in the grid
   * 
   * @return            the best values pair
   * @throws Exception  if something goes wrong
   */
03244   protected PointDouble findBest() throws Exception {
    PointInt            center;
    Grid          neighborGrid;
    boolean       finished;
    PointDouble         result;
    PointDouble         resultOld;
    int                 iteration;
    Instances           sample;
    Resample            resample;

    log("Step 1:\n");

    // generate sample?
    if (getSampleSizePercent() == 100) {
      sample = m_Data;
    }
    else {
      log("Generating sample (" + getSampleSizePercent() + "%)");
      resample = new Resample();
      resample.setRandomSeed(getSeed());
      resample.setSampleSizePercent(getSampleSizePercent());
      resample.setInputFormat(m_Data);
      sample = Filter.useFilter(m_Data, resample);
    }
    
    finished                  = false;
    iteration                 = 0;
    m_GridExtensionsPerformed = 0;
    m_UniformPerformance      = false;
    
    // find first center
    log("\n=== Initial grid - Start ===");
    result = determineBestInGrid(m_Grid, sample, 2);
    log("\nResult of Step 1: " + result + "\n");
    log("=== Initial grid - End ===\n");

    finished = m_UniformPerformance;
    
    if (!finished) {
      do {
      iteration++;
      resultOld = (PointDouble) result.clone();
      center    = m_Grid.getLocation(result);
      // on border? -> finished (if it cannot be extended)
      if (m_Grid.isOnBorder(center)) {
        log("Center is on border of grid.");

        // can we extend grid?
        if (getGridIsExtendable()) {
          // max number of extensions reached?
          if (m_GridExtensionsPerformed == getMaxGridExtensions()) {
            log("Maximum number of extensions reached!\n");
            finished = true;
          }
          else {
            m_GridExtensionsPerformed++;
            m_Grid = m_Grid.extend(result);
            center = m_Grid.getLocation(result);
            log("Extending grid (" + m_GridExtensionsPerformed + "/" 
              + getMaxGridExtensions() + "):\n" + m_Grid + "\n");
          }
        }
        else {
          finished = true;
        }
      }

      // new grid with current best one at center and immediate neighbors 
      // around it
      if (!finished) {
        neighborGrid = m_Grid.subgrid(
            (int) center.getY() + 1, (int) center.getX() - 1, 
            (int) center.getY() - 1, (int) center.getX() + 1);
        result = determineBestInGrid(neighborGrid, sample, 10);
        log("\nResult of Step 2/Iteration " + (iteration) + ":\n" + result);
        finished = m_UniformPerformance;

        // no improvement?
        if (result.equals(resultOld)) {
          finished = true;
          log("\nNo better point found.");
        }
      }
      }
      while (!finished);
    }
    
    log("\nFinal result: " + result);

    return result;
  }
  
  /**
   * builds the classifier
   * 
   * @param data        the training instances
   * @throws Exception  if something goes wrong
   */
03342   public void buildClassifier(Instances data) throws Exception {
    String  strX;
    String  strY;
    double  x;
    double  y;
    
    // can classifier handle the data?
    getCapabilities().testWithFail(data);

    // remove instances with missing class
    m_Data = new Instances(data);
    m_Data.deleteWithMissingClass();
    
    m_Cache = new PerformanceCache();
    
    if (getXProperty().startsWith(PREFIX_FILTER))
      strX = m_Filter.getClass().getName();
    else
      strX = m_Classifier.getClass().getName();
    
    if (getYProperty().startsWith(PREFIX_CLASSIFIER))
      strY = m_Classifier.getClass().getName();
    else
      strY = m_Filter.getClass().getName();
    
    m_Grid = new Grid(getXMin(), getXMax(), getXStep(), 
                  strX + ", property " + getXProperty() + ", expr. " + getXExpression() + ", base " + getXBase(),
                    getYMin(), getYMax(), getYStep(),
                    strY + ", property " + getYProperty() + ", expr. " + getYExpression() + ", base " + getYBase());

    log("\n"  
      + this.getClass().getName() + "\n" 
      + this.getClass().getName().replaceAll(".", "=") + "\n"
      + "Options: " + Utils.joinOptions(getOptions()) + "\n");
    
    // find best
    m_Values = findBest();

    // setup best configurations
    x                = evaluate(m_Values.getX(), true);
    y                = evaluate(m_Values.getY(), false);
    m_BestFilter     = (Filter) setup(getFilter(), x, y);
    m_BestClassifier = (Classifier) setup(getClassifier(), x, y);
    
    // process data
    m_Filter = (Filter) setup(getFilter(), x, y);
    m_Filter.setInputFormat(m_Data);
    Instances transformed = Filter.useFilter(m_Data, m_Filter);
    
    // train classifier
    m_Classifier = (Classifier) setup(getClassifier(), x, y);
    m_Classifier.buildClassifier(transformed);
  }

  /**
   * Classifies the given instance.
   *
   * @param instance    the test instance
   * @return            the classification
   * @throws Exception  if classification can't be done successfully
   */
03403   public double classifyInstance(Instance instance) throws Exception {
    // transform instance
    m_Filter.input(instance);
    m_Filter.batchFinished();
    Instance transformed = m_Filter.output();
    
    // classify instance
    return m_Classifier.classifyInstance(transformed);
  }

  /**
   * returns a string representation of the classifier
   * 
   * @return a string representation of the classifier
   */
03418   public String toString() {
    String  result;
    
    result = "";
    
    if (m_Values == null) {
      result = "No search performed yet.";
    }
    else {
      result = 
              this.getClass().getName() + ":\n"
            + "Filter: " + getFilter().getClass().getName() 
            + (getFilter() instanceof OptionHandler ? " " + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()) : "") + "\n"
            + "Classifier: " + getClassifier().getClass().getName() 
            + " " + Utils.joinOptions(getClassifier().getOptions()) + "\n\n"
            + "X property: " + getXProperty() + "\n"
            + "Y property: " + getYProperty() + "\n\n"
            + "Evaluation: " + getEvaluation().getSelectedTag().getReadable() + "\n"
            + "Coordinates: " + getValues() + "\n";
      
      if (getGridIsExtendable())
      result += "Grid-Extensions: " + getGridExtensionsPerformed() + "\n";
      
      result += 
      "Values: "
      + evaluate(getValues().getX(), true) + " (X coordinate)" 
      + ", "
      + evaluate(getValues().getY(), false) + " (Y coordinate)"
      + "\n\n"
      + m_Classifier.toString();
    }
    
    return result;
  }

  /**
   * Returns a string that summarizes the object.
   *
   * @return            the object summarized as a string
   */
03458   public String toSummaryString() {
    String  result;
    
    result = 
        "Best filter: " + getBestFilter().getClass().getName() 
      + (getBestFilter() instanceof OptionHandler ? " " + Utils.joinOptions(((OptionHandler) getBestFilter()).getOptions()) : "") + "\n"
      + "Best classifier: " + getBestClassifier().getClass().getName() 
      + " " + Utils.joinOptions(getBestClassifier().getOptions());
    
    return result;
  }
  
  /**
   * Returns the revision string.
   * 
   * @return            the revision
   */
03475   public String getRevision() {
    return RevisionUtils.extract("$Revision: 1.9 $");
  }
  
  /**
   * Main method for running this classifier from commandline.
   * 
   * @param args  the options
   */
03484   public static void main(String[] args) {
    runClassifier(new GridSearch(), args);
  }
}

Generated by  Doxygen 1.6.0   Back to index