Coverage Report - weka.attributeSelection.GreedyStepwise
 
Classes in this File Line Coverage Branch Coverage Complexity
GreedyStepwise
0%
0/225
0%
0/140
3.156
 
 1  
 /*
 2  
  *   This program is free software: you can redistribute it and/or modify
 3  
  *   it under the terms of the GNU General Public License as published by
 4  
  *   the Free Software Foundation, either version 3 of the License, or
 5  
  *   (at your option) any later version.
 6  
  *
 7  
  *   This program is distributed in the hope that it will be useful,
 8  
  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 9  
  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 10  
  *   GNU General Public License for more details.
 11  
  *
 12  
  *   You should have received a copy of the GNU General Public License
 13  
  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 14  
  */
 15  
 
 16  
 /*
 17  
  *    GreedyStepwise.java
 18  
  *    Copyright (C) 2004-2012 University of Waikato, Hamilton, New Zealand
 19  
  *
 20  
  */
 21  
 
 22  
 package weka.attributeSelection;
 23  
 
 24  
 import java.util.BitSet;
 25  
 import java.util.Enumeration;
 26  
 import java.util.Vector;
 27  
 
 28  
 import weka.core.Instances;
 29  
 import weka.core.Option;
 30  
 import weka.core.OptionHandler;
 31  
 import weka.core.Range;
 32  
 import weka.core.RevisionUtils;
 33  
 import weka.core.Utils;
 34  
 
 35  
 /** 
 36  
  <!-- globalinfo-start -->
 37  
  * GreedyStepwise :<br/>
 38  
  * <br/>
 39  
  * Performs a greedy forward or backward search through the space of attribute subsets. May start with no/all attributes or from an arbitrary point in the space. Stops when the addition/deletion of any remaining attributes results in a decrease in evaluation. Can also produce a ranked list of attributes by traversing the space from one side to the other and recording the order that attributes are selected.<br/>
 40  
  * <p/>
 41  
  <!-- globalinfo-end -->
 42  
  *
 43  
  <!-- options-start -->
 44  
  * Valid options are: <p/>
 45  
  * 
 46  
  * <pre> -C
 47  
  *  Use conservative forward search</pre>
 48  
  * 
 49  
  * <pre> -B
 50  
  *  Use a backward search instead of a
 51  
  *  forward one.</pre>
 52  
  * 
 53  
  * <pre> -P &lt;start set&gt;
 54  
  *  Specify a starting set of attributes.
 55  
  *  Eg. 1,3,5-7.</pre>
 56  
  * 
 57  
  * <pre> -R
 58  
  *  Produce a ranked list of attributes.</pre>
 59  
  * 
 60  
  * <pre> -T &lt;threshold&gt;
 61  
  *  Specify a theshold by which attributes
 62  
  *  may be discarded from the ranking.
 63  
  *  Use in conjuction with -R</pre>
 64  
  * 
 65  
  * <pre> -N &lt;num to select&gt;
 66  
  *  Specify number of attributes to select</pre>
 67  
  * 
 68  
  <!-- options-end -->
 69  
  *
 70  
  * @author Mark Hall
 71  
  * @version $Revision: 8034 $
 72  
  */
 73  
 public class GreedyStepwise 
 74  
   extends ASSearch 
 75  
   implements RankedOutputSearch, StartSetHandler, OptionHandler {
 76  
   
 77  
   /** for serialization */
 78  
   static final long serialVersionUID = -6312951970168325471L;
 79  
 
 80  
   /** does the data have a class */
 81  
   protected boolean m_hasClass;
 82  
  
 83  
   /** holds the class index */
 84  
   protected int m_classIndex;
 85  
  
 86  
   /** number of attributes in the data */
 87  
   protected int m_numAttribs;
 88  
 
 89  
   /** true if the user has requested a ranked list of attributes */
 90  
   protected boolean m_rankingRequested;
 91  
 
 92  
   /** 
 93  
    * go from one side of the search space to the other in order to generate
 94  
    * a ranking
 95  
    */
 96  
   protected boolean m_doRank;
 97  
 
 98  
   /** used to indicate whether or not ranking has been performed */
 99  
   protected boolean m_doneRanking;
 100  
 
 101  
   /**
 102  
    * A threshold by which to discard attributes---used by the
 103  
    * AttributeSelection module
 104  
    */
 105  
   protected double m_threshold;
 106  
 
 107  
   /** The number of attributes to select. -1 indicates that all attributes
 108  
       are to be retained. Has precedence over m_threshold */
 109  0
   protected int m_numToSelect = -1;
 110  
 
 111  
   protected int m_calculatedNumToSelect;
 112  
 
 113  
   /** the merit of the best subset found */
 114  
   protected double m_bestMerit;
 115  
 
 116  
   /** a ranked list of attribute indexes */
 117  
   protected double [][] m_rankedAtts;
 118  
   protected int m_rankedSoFar;
 119  
 
 120  
   /** the best subset found */
 121  
   protected BitSet m_best_group;
 122  
   protected ASEvaluation m_ASEval;
 123  
 
 124  
   protected Instances m_Instances;
 125  
 
 126  
   /** holds the start set for the search as a Range */
 127  
   protected Range m_startRange;
 128  
 
 129  
   /** holds an array of starting attributes */
 130  
   protected int [] m_starting;
 131  
 
 132  
   /** Use a backwards search instead of a forwards one */
 133  0
   protected boolean m_backward = false;
 134  
 
 135  
   /** If set then attributes will continue to be added during a forward
 136  
       search as long as the merit does not degrade */
 137  0
   protected boolean m_conservativeSelection = false;
 138  
 
 139  
   /**
 140  
    * Constructor
 141  
    */
 142  0
   public GreedyStepwise () {
 143  0
     m_threshold = -Double.MAX_VALUE;
 144  0
     m_doneRanking = false;
 145  0
     m_startRange = new Range();
 146  0
     m_starting = null;
 147  0
     resetOptions();
 148  0
   }
 149  
 
 150  
   /**
 151  
    * Returns a string describing this search method
 152  
    * @return a description of the search suitable for
 153  
    * displaying in the explorer/experimenter gui
 154  
    */
 155  
   public String globalInfo() {
 156  0
     return "GreedyStepwise :\n\nPerforms a greedy forward or backward search "
 157  
       +"through "
 158  
       +"the space of attribute subsets. May start with no/all attributes or from "
 159  
       +"an arbitrary point in the space. Stops when the addition/deletion of any "
 160  
       +"remaining attributes results in a decrease in evaluation. "
 161  
       +"Can also produce a ranked list of "
 162  
       +"attributes by traversing the space from one side to the other and "
 163  
       +"recording the order that attributes are selected.\n";
 164  
   }
 165  
 
 166  
   /**
 167  
    * Returns the tip text for this property
 168  
    * @return tip text for this property suitable for
 169  
    * displaying in the explorer/experimenter gui
 170  
    */
 171  
   public String searchBackwardsTipText() {
 172  0
     return "Search backwards rather than forwards.";
 173  
   }
 174  
 
 175  
   /**
 176  
    * Set whether to search backwards instead of forwards
 177  
    *
 178  
    * @param back true to search backwards
 179  
    */
 180  
   public void setSearchBackwards(boolean back) {
 181  0
     m_backward = back;
 182  0
     if (m_backward) {
 183  0
       setGenerateRanking(false);
 184  
     }
 185  0
   }
 186  
 
 187  
   /**
 188  
    * Get whether to search backwards
 189  
    *
 190  
    * @return true if the search will proceed backwards
 191  
    */
 192  
   public boolean getSearchBackwards() {
 193  0
     return m_backward;
 194  
   }
 195  
 
 196  
   /**
 197  
    * Returns the tip text for this property
 198  
    * @return tip text for this property suitable for
 199  
    * displaying in the explorer/experimenter gui
 200  
    */
 201  
   public String thresholdTipText() {
 202  0
     return "Set threshold by which attributes can be discarded. Default value "
 203  
       + "results in no attributes being discarded. Use in conjunction with "
 204  
       + "generateRanking";
 205  
   }
 206  
 
 207  
   /**
 208  
    * Set the threshold by which the AttributeSelection module can discard
 209  
    * attributes.
 210  
    * @param threshold the threshold.
 211  
    */
 212  
   public void setThreshold(double threshold) {
 213  0
     m_threshold = threshold;
 214  0
   }
 215  
 
 216  
   /**
 217  
    * Returns the threshold so that the AttributeSelection module can
 218  
    * discard attributes from the ranking.
 219  
    */
 220  
   public double getThreshold() {
 221  0
     return m_threshold;
 222  
   }
 223  
 
 224  
   /**
 225  
    * Returns the tip text for this property
 226  
    * @return tip text for this property suitable for
 227  
    * displaying in the explorer/experimenter gui
 228  
    */
 229  
   public String numToSelectTipText() {
 230  0
     return "Specify the number of attributes to retain. The default value "
 231  
       +"(-1) indicates that all attributes are to be retained. Use either "
 232  
       +"this option or a threshold to reduce the attribute set.";
 233  
   }
 234  
 
 235  
   /**
 236  
    * Specify the number of attributes to select from the ranked list
 237  
    * (if generating a ranking). -1
 238  
    * indicates that all attributes are to be retained.
 239  
    * @param n the number of attributes to retain
 240  
    */
 241  
   public void setNumToSelect(int n) {
 242  0
     m_numToSelect = n;
 243  0
   }
 244  
 
 245  
   /**
 246  
    * Gets the number of attributes to be retained.
 247  
    * @return the number of attributes to retain
 248  
    */
 249  
   public int getNumToSelect() {
 250  0
     return m_numToSelect;
 251  
   }
 252  
 
 253  
   /**
 254  
    * Gets the calculated number of attributes to retain. This is the
 255  
    * actual number of attributes to retain. This is the same as
 256  
    * getNumToSelect if the user specifies a number which is not less
 257  
    * than zero. Otherwise it should be the number of attributes in the
 258  
    * (potentially transformed) data.
 259  
    */
 260  
   public int getCalculatedNumToSelect() {
 261  0
     if (m_numToSelect >= 0) {
 262  0
       m_calculatedNumToSelect = m_numToSelect;
 263  
     }
 264  0
     return m_calculatedNumToSelect;
 265  
   }
 266  
 
 267  
   /**
 268  
    * Returns the tip text for this property
 269  
    * @return tip text for this property suitable for
 270  
    * displaying in the explorer/experimenter gui
 271  
    */
 272  
   public String generateRankingTipText() {
 273  0
     return "Set to true if a ranked list is required.";
 274  
   }
 275  
   
 276  
   /**
 277  
    * Records whether the user has requested a ranked list of attributes.
 278  
    * @param doRank true if ranking is requested
 279  
    */
 280  
   public void setGenerateRanking(boolean doRank) {
 281  0
     m_rankingRequested = doRank;
 282  0
   }
 283  
 
 284  
   /**
 285  
    * Gets whether ranking has been requested. This is used by the
 286  
    * AttributeSelection module to determine if rankedAttributes()
 287  
    * should be called.
 288  
    * @return true if ranking has been requested.
 289  
    */
 290  
   public boolean getGenerateRanking() {
 291  0
     return m_rankingRequested;
 292  
   }
 293  
 
 294  
   /**
 295  
    * Returns the tip text for this property
 296  
    * @return tip text for this property suitable for
 297  
    * displaying in the explorer/experimenter gui
 298  
    */
 299  
   public String startSetTipText() {
 300  0
     return "Set the start point for the search. This is specified as a comma "
 301  
       +"seperated list off attribute indexes starting at 1. It can include "
 302  
       +"ranges. Eg. 1,2,5-9,17.";
 303  
   }
 304  
 
 305  
   /**
 306  
    * Sets a starting set of attributes for the search. It is the
 307  
    * search method's responsibility to report this start set (if any)
 308  
    * in its toString() method.
 309  
    * @param startSet a string containing a list of attributes (and or ranges),
 310  
    * eg. 1,2,6,10-15.
 311  
    * @throws Exception if start set can't be set.
 312  
    */
 313  
   public void setStartSet (String startSet) throws Exception {
 314  0
     m_startRange.setRanges(startSet);
 315  0
   }
 316  
 
 317  
   /**
 318  
    * Returns a list of attributes (and or attribute ranges) as a String
 319  
    * @return a list of attributes (and or attribute ranges)
 320  
    */
 321  
   public String getStartSet () {
 322  0
     return m_startRange.getRanges();
 323  
   }
 324  
 
 325  
   /**
 326  
    * Returns the tip text for this property
 327  
    * @return tip text for this property suitable for
 328  
    * displaying in the explorer/experimenter gui
 329  
    */
 330  
   public String conservativeForwardSelectionTipText() {
 331  0
     return "If true (and forward search is selected) then attributes "
 332  
       +"will continue to be added to the best subset as long as merit does "
 333  
       +"not degrade.";
 334  
   }
 335  
 
 336  
   /**
 337  
    * Set whether attributes should continue to be added during
 338  
    * a forward search as long as merit does not decrease
 339  
    * @param c true if atts should continue to be atted
 340  
    */
 341  
   public void setConservativeForwardSelection(boolean c) {
 342  0
     m_conservativeSelection = c;
 343  0
   }
 344  
 
 345  
   /**
 346  
    * Gets whether conservative selection has been enabled
 347  
    * @return true if conservative forward selection is enabled
 348  
    */
 349  
   public boolean getConservativeForwardSelection() {
 350  0
     return m_conservativeSelection;
 351  
   }
 352  
 
 353  
   /**
 354  
    * Returns an enumeration describing the available options.
 355  
    * @return an enumeration of all the available options.
 356  
    **/
 357  
   public Enumeration listOptions () {
 358  0
     Vector newVector = new Vector(5);
 359  
 
 360  0
     newVector.addElement(new Option("\tUse conservative forward search"
 361  
                                     ,"-C", 0, "-C"));
 362  
 
 363  0
     newVector.addElement(new Option("\tUse a backward search instead of a"
 364  
                                     +"\n\tforward one."
 365  
                                     ,"-B", 0, "-B"));
 366  0
     newVector
 367  
       .addElement(new Option("\tSpecify a starting set of attributes." 
 368  
                              + "\n\tEg. 1,3,5-7."
 369  
                              ,"P",1
 370  
                              , "-P <start set>"));
 371  
 
 372  0
     newVector.addElement(new Option("\tProduce a ranked list of attributes."
 373  
                                     ,"R",0,"-R"));
 374  0
     newVector
 375  
       .addElement(new Option("\tSpecify a theshold by which attributes" 
 376  
                              + "\n\tmay be discarded from the ranking."
 377  
                              +"\n\tUse in conjuction with -R","T",1
 378  
                              , "-T <threshold>"));
 379  
 
 380  0
     newVector
 381  
       .addElement(new Option("\tSpecify number of attributes to select" 
 382  
                              ,"N",1
 383  
                              , "-N <num to select>"));
 384  
 
 385  0
     return newVector.elements();
 386  
 
 387  
   }
 388  
   
 389  
   /**
 390  
    * Parses a given list of options. <p/>
 391  
    *
 392  
    <!-- options-start -->
 393  
    * Valid options are: <p/>
 394  
    * 
 395  
    * <pre> -C
 396  
    *  Use conservative forward search</pre>
 397  
    * 
 398  
    * <pre> -B
 399  
    *  Use a backward search instead of a
 400  
    *  forward one.</pre>
 401  
    * 
 402  
    * <pre> -P &lt;start set&gt;
 403  
    *  Specify a starting set of attributes.
 404  
    *  Eg. 1,3,5-7.</pre>
 405  
    * 
 406  
    * <pre> -R
 407  
    *  Produce a ranked list of attributes.</pre>
 408  
    * 
 409  
    * <pre> -T &lt;threshold&gt;
 410  
    *  Specify a theshold by which attributes
 411  
    *  may be discarded from the ranking.
 412  
    *  Use in conjuction with -R</pre>
 413  
    * 
 414  
    * <pre> -N &lt;num to select&gt;
 415  
    *  Specify number of attributes to select</pre>
 416  
    * 
 417  
    <!-- options-end -->
 418  
    *
 419  
    * @param options the list of options as an array of strings
 420  
    * @throws Exception if an option is not supported
 421  
    */
 422  
   public void setOptions (String[] options)
 423  
     throws Exception {
 424  
     String optionString;
 425  0
     resetOptions();
 426  
 
 427  0
     setSearchBackwards(Utils.getFlag('B', options));
 428  
 
 429  0
     setConservativeForwardSelection(Utils.getFlag('C', options));
 430  
 
 431  0
     optionString = Utils.getOption('P', options);
 432  0
     if (optionString.length() != 0) {
 433  0
       setStartSet(optionString);
 434  
     }
 435  
 
 436  0
     setGenerateRanking(Utils.getFlag('R', options));
 437  
 
 438  0
     optionString = Utils.getOption('T', options);
 439  0
     if (optionString.length() != 0) {
 440  
       Double temp;
 441  0
       temp = Double.valueOf(optionString);
 442  0
       setThreshold(temp.doubleValue());
 443  
     }
 444  
 
 445  0
     optionString = Utils.getOption('N', options);
 446  0
     if (optionString.length() != 0) {
 447  0
       setNumToSelect(Integer.parseInt(optionString));
 448  
     }
 449  0
   }
 450  
 
 451  
   /**
 452  
    * Gets the current settings of ReliefFAttributeEval.
 453  
    *
 454  
    * @return an array of strings suitable for passing to setOptions()
 455  
    */
 456  
   public String[] getOptions () {
 457  0
     String[] options = new String[9];
 458  0
     int current = 0;
 459  
     
 460  0
     if (getSearchBackwards()) {
 461  0
       options[current++] = "-B";
 462  
     }
 463  
 
 464  0
     if (getConservativeForwardSelection()) {
 465  0
       options[current++] = "-C";
 466  
     }
 467  
 
 468  0
     if (!(getStartSet().equals(""))) {
 469  0
       options[current++] = "-P";
 470  0
       options[current++] = ""+startSetToString();
 471  
     }
 472  
 
 473  0
     if (getGenerateRanking()) {
 474  0
       options[current++] = "-R";
 475  
     }
 476  0
     options[current++] = "-T";
 477  0
     options[current++] = "" + getThreshold();
 478  
 
 479  0
     options[current++] = "-N";
 480  0
     options[current++] = ""+getNumToSelect();
 481  
 
 482  0
     while (current < options.length) {
 483  0
       options[current++] = "";
 484  
     }
 485  0
     return  options;
 486  
   }
 487  
 
 488  
   /**
 489  
    * converts the array of starting attributes to a string. This is
 490  
    * used by getOptions to return the actual attributes specified
 491  
    * as the starting set. This is better than using m_startRanges.getRanges()
 492  
    * as the same start set can be specified in different ways from the
 493  
    * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
 494  
    * is stored in a database is comparable.
 495  
    * @return a comma seperated list of individual attribute numbers as a String
 496  
    */
 497  
   protected String startSetToString() {
 498  0
     StringBuffer FString = new StringBuffer();
 499  
     boolean didPrint;
 500  
     
 501  0
     if (m_starting == null) {
 502  0
       return getStartSet();
 503  
     }
 504  0
     for (int i = 0; i < m_starting.length; i++) {
 505  0
       didPrint = false;
 506  
       
 507  0
       if ((m_hasClass == false) || 
 508  
           (m_hasClass == true && i != m_classIndex)) {
 509  0
         FString.append((m_starting[i] + 1));
 510  0
         didPrint = true;
 511  
       }
 512  
       
 513  0
       if (i == (m_starting.length - 1)) {
 514  0
         FString.append("");
 515  
       }
 516  
       else {
 517  0
         if (didPrint) {
 518  0
           FString.append(",");
 519  
           }
 520  
       }
 521  
     }
 522  
 
 523  0
     return FString.toString();
 524  
   }
 525  
 
 526  
   /**
 527  
    * returns a description of the search.
 528  
    * @return a description of the search as a String.
 529  
    */
 530  
   public String toString() {
 531  0
     StringBuffer FString = new StringBuffer();
 532  0
     FString.append("\tGreedy Stepwise ("
 533  
                    + ((m_backward)
 534  
                       ? "backwards)"
 535  
                       : "forwards)")+".\n\tStart set: ");
 536  
 
 537  0
     if (m_starting == null) {
 538  0
       if (m_backward) {
 539  0
         FString.append("all attributes\n");
 540  
       } else {
 541  0
         FString.append("no attributes\n");
 542  
       }
 543  
     }
 544  
     else {
 545  0
       FString.append(startSetToString()+"\n");
 546  
     }
 547  0
     if (!m_doneRanking) {
 548  0
       FString.append("\tMerit of best subset found: "
 549  
                      +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"\n");
 550  
     } else {
 551  0
       if (m_backward) {
 552  0
         FString.append("\n\tRanking is the order that attributes were removed, " +
 553  
                         "starting \n\twith all attributes. The merit scores in the left" +
 554  
                         "\n\tcolumn are the goodness of the remaining attributes in the" +
 555  
                         "\n\tsubset after removing the corresponding in the right column" +
 556  
                         "\n\tattribute from the subset.\n");
 557  
       } else {
 558  0
         FString.append("\n\tRanking is the order that attributes were added, starting " +
 559  
                         "\n\twith no attributes. The merit scores in the left column" +
 560  
                         "\n\tare the goodness of the subset after the adding the" +
 561  
                         "\n\tcorresponding attribute in the right column to the subset.\n");
 562  
       }
 563  
     }
 564  
     
 565  0
     if ((m_threshold != -Double.MAX_VALUE) && (m_doneRanking)) {
 566  0
       FString.append("\tThreshold for discarding attributes: "
 567  
                      + Utils.doubleToString(m_threshold,8,4)+"\n");
 568  
     }
 569  
 
 570  0
     return FString.toString();
 571  
   }
 572  
 
 573  
 
 574  
   /**
 575  
    * Searches the attribute subset space by forward selection.
 576  
    *
 577  
    * @param ASEval the attribute evaluator to guide the search
 578  
    * @param data the training instances.
 579  
    * @return an array (not necessarily ordered) of selected attribute indexes
 580  
    * @throws Exception if the search can't be completed
 581  
    */
 582  
   public int[] search (ASEvaluation ASEval, Instances data)
 583  
     throws Exception {
 584  
 
 585  
     int i;
 586  0
     double best_merit = -Double.MAX_VALUE;
 587  
     double temp_best,temp_merit;
 588  0
     int temp_index=0;
 589  
     BitSet temp_group;
 590  
 
 591  0
     if (data != null) { // this is a fresh run so reset
 592  0
       resetOptions();
 593  0
       m_Instances = data;
 594  
     }
 595  0
     m_ASEval = ASEval;
 596  
 
 597  0
     m_numAttribs = m_Instances.numAttributes();
 598  
 
 599  0
     if (m_best_group == null) {
 600  0
       m_best_group = new BitSet(m_numAttribs);
 601  
     }
 602  
 
 603  0
     if (!(m_ASEval instanceof SubsetEvaluator)) {
 604  0
       throw  new Exception(m_ASEval.getClass().getName() 
 605  
                            + " is not a " 
 606  
                            + "Subset evaluator!");
 607  
     }
 608  
 
 609  0
     m_startRange.setUpper(m_numAttribs-1);
 610  0
     if (!(getStartSet().equals(""))) {
 611  0
       m_starting = m_startRange.getSelection();
 612  
     }
 613  
 
 614  0
     if (m_ASEval instanceof UnsupervisedSubsetEvaluator) {
 615  0
       m_hasClass = false;
 616  0
       m_classIndex = -1;
 617  
     }
 618  
     else {
 619  0
       m_hasClass = true;
 620  0
       m_classIndex = m_Instances.classIndex();
 621  
     }
 622  
 
 623  0
     SubsetEvaluator ASEvaluator = (SubsetEvaluator)m_ASEval;
 624  
 
 625  0
     if (m_rankedAtts == null) {
 626  0
       m_rankedAtts = new double[m_numAttribs][2];
 627  0
       m_rankedSoFar = 0;
 628  
     }
 629  
 
 630  
     // If a starting subset has been supplied, then initialise the bitset
 631  0
     if (m_starting != null && m_rankedSoFar <= 0) {
 632  0
       for (i = 0; i < m_starting.length; i++) {
 633  0
         if ((m_starting[i]) != m_classIndex) {
 634  0
           m_best_group.set(m_starting[i]);
 635  
         }
 636  
       }
 637  
     } else {
 638  0
       if (m_backward && m_rankedSoFar <= 0) {
 639  0
         for (i = 0; i < m_numAttribs; i++) {
 640  0
           if (i != m_classIndex) {
 641  0
             m_best_group.set(i);
 642  
           }
 643  
         }
 644  
       }
 645  
     }
 646  
 
 647  
     // Evaluate the initial subset
 648  0
     best_merit = ASEvaluator.evaluateSubset(m_best_group);
 649  
 
 650  
     // main search loop
 651  0
     boolean done = false;
 652  0
     boolean addone = false;
 653  
     boolean z;
 654  0
     while (!done) {
 655  0
       temp_group = (BitSet)m_best_group.clone();
 656  0
       temp_best = best_merit;
 657  0
       if (m_doRank) {
 658  0
         temp_best = -Double.MAX_VALUE;
 659  
       }
 660  0
       done = true;
 661  0
       addone = false;
 662  0
       for (i=0;i<m_numAttribs;i++) {
 663  0
         if (m_backward) {
 664  0
           z = ((i != m_classIndex) && (temp_group.get(i)));
 665  
         } else {
 666  0
           z = ((i != m_classIndex) && (!temp_group.get(i)));
 667  
         }
 668  0
         if (z) {
 669  
           // set/unset the bit
 670  0
           if (m_backward) {
 671  0
             temp_group.clear(i);
 672  
           } else {
 673  0
             temp_group.set(i);
 674  
           }
 675  0
           temp_merit = ASEvaluator.evaluateSubset(temp_group);
 676  0
           if (m_backward) {
 677  0
             z = (temp_merit >= temp_best);
 678  
           } else {
 679  0
             if (m_conservativeSelection) {
 680  0
               z = (temp_merit >= temp_best);
 681  
             } else {
 682  0
               z = (temp_merit > temp_best);
 683  
             }
 684  
           }
 685  
 
 686  0
           if (z) {
 687  0
             temp_best = temp_merit;
 688  0
             temp_index = i;
 689  0
             addone = true;
 690  0
             done = false;
 691  
           }
 692  
 
 693  
           // unset this addition/deletion
 694  0
           if (m_backward) {
 695  0
             temp_group.set(i);
 696  
           } else {
 697  0
             temp_group.clear(i);
 698  
           }
 699  0
           if (m_doRank) {
 700  0
             done = false;
 701  
           }
 702  
         }
 703  
       }
 704  0
       if (addone) {
 705  0
         if (m_backward) {
 706  0
           m_best_group.clear(temp_index);
 707  
         } else {
 708  0
           m_best_group.set(temp_index);
 709  
         }
 710  0
         best_merit = temp_best;
 711  0
         m_rankedAtts[m_rankedSoFar][0] = temp_index;
 712  0
         m_rankedAtts[m_rankedSoFar][1] = best_merit;
 713  0
         m_rankedSoFar++;
 714  
       }
 715  
     }
 716  0
     m_bestMerit = best_merit;
 717  0
     return attributeList(m_best_group);
 718  
   }
 719  
 
 720  
   /**
 721  
    * Produces a ranked list of attributes. Search must have been performed
 722  
    * prior to calling this function. Search is called by this function to
 723  
    * complete the traversal of the the search space. A list of
 724  
    * attributes and merits are returned. The attributes a ranked by the
 725  
    * order they are added to the subset during a forward selection search.
 726  
    * Individual merit values reflect the merit associated with adding the
 727  
    * corresponding attribute to the subset; because of this, merit values
 728  
    * may initially increase but then decrease as the best subset is
 729  
    * "passed by" on the way to the far side of the search space.
 730  
    *
 731  
    * @return an array of attribute indexes and associated merit values
 732  
    * @throws Exception if something goes wrong.
 733  
    */
 734  
   public double [][] rankedAttributes() throws Exception {
 735  
     
 736  0
     if (m_rankedAtts == null || m_rankedSoFar == -1) {
 737  0
       throw new Exception("Search must be performed before attributes "
 738  
                           +"can be ranked.");
 739  
     }
 740  
     
 741  0
     m_doRank = true;
 742  0
     search (m_ASEval, null);
 743  
 
 744  0
     double [][] final_rank = new double [m_rankedSoFar][2];
 745  0
     for (int i=0;i<m_rankedSoFar;i++) {
 746  0
       final_rank[i][0] = m_rankedAtts[i][0];
 747  0
       final_rank[i][1] = m_rankedAtts[i][1];
 748  
     }
 749  
     
 750  0
     resetOptions();
 751  0
     m_doneRanking = true;
 752  
 
 753  0
     if (m_numToSelect > final_rank.length) {
 754  0
       throw new Exception("More attributes requested than exist in the data");
 755  
     }
 756  
 
 757  0
     if (m_numToSelect <= 0) {
 758  0
       if (m_threshold == -Double.MAX_VALUE) {
 759  0
         m_calculatedNumToSelect = final_rank.length;
 760  
       } else {
 761  0
         determineNumToSelectFromThreshold(final_rank);
 762  
       }
 763  
     }
 764  
 
 765  0
     return final_rank;
 766  
   }
 767  
 
 768  
   private void determineNumToSelectFromThreshold(double [][] ranking) {
 769  0
     int count = 0;
 770  0
     for (int i = 0; i < ranking.length; i++) {
 771  0
       if (ranking[i][1] > m_threshold) {
 772  0
         count++;
 773  
       }
 774  
     }
 775  0
     m_calculatedNumToSelect = count;
 776  0
   }
 777  
 
 778  
   /**
 779  
    * converts a BitSet into a list of attribute indexes 
 780  
    * @param group the BitSet to convert
 781  
    * @return an array of attribute indexes
 782  
    **/
 783  
   protected int[] attributeList (BitSet group) {
 784  0
     int count = 0;
 785  
 
 786  
     // count how many were selected
 787  0
     for (int i = 0; i < m_numAttribs; i++) {
 788  0
       if (group.get(i)) {
 789  0
         count++;
 790  
       }
 791  
     }
 792  
 
 793  0
     int[] list = new int[count];
 794  0
     count = 0;
 795  
 
 796  0
     for (int i = 0; i < m_numAttribs; i++) {
 797  0
       if (group.get(i)) {
 798  0
         list[count++] = i;
 799  
       }
 800  
     }
 801  
 
 802  0
     return  list;
 803  
   }
 804  
 
 805  
   /**
 806  
    * Resets options
 807  
    */
 808  
   protected void resetOptions() {
 809  0
     m_doRank = false;
 810  0
     m_best_group = null;
 811  0
     m_ASEval = null;
 812  0
     m_Instances = null;
 813  0
     m_rankedSoFar = -1;
 814  0
     m_rankedAtts = null;
 815  0
   }
 816  
   
 817  
   /**
 818  
    * Returns the revision string.
 819  
    * 
 820  
    * @return                the revision
 821  
    */
 822  
   public String getRevision() {
 823  0
     return RevisionUtils.extract("$Revision: 8034 $");
 824  
   }
 825  
 }