ExhaustiveSearch.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 15k
Category:

Windows Develop

Development Platform:

Java

  1. /*
  2.  *    This program is free software; you can redistribute it and/or modify
  3.  *    it under the terms of the GNU General Public License as published by
  4.  *    the Free Software Foundation; either version 2 of the License, or
  5.  *    (at your option) any later version.
  6.  *
  7.  *    This program is distributed in the hope that it will be useful,
  8.  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
  9.  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  10.  *    GNU General Public License for more details.
  11.  *
  12.  *    You should have received a copy of the GNU General Public License
  13.  *    along with this program; if not, write to the Free Software
  14.  *    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15.  */
  16. /*
  17.  *    ExhaustiveSearch.java
  18.  *    Copyright (C) 1999 Mark Hall
  19.  *
  20.  */
  21. package  weka.attributeSelection;
  22. import  java.io.*;
  23. import  java.util.*;
  24. import  weka.core.*;
  25. /** 
  26.  * Class for performing an exhaustive search. <p>
  27.  *
  28.  * Valid options are: <p>
  29.  *
  30.  * -P <start set> <br>
  31.  * Specify a starting set of attributes. Eg 1,4,7-9. <p>
  32.  *
  33.  * -V <br>
  34.  * Verbose output. Output new best subsets as the search progresses. <p>
  35.  *
  36.  * @author Mark Hall (mhall@cs.waikato.ac.nz)
  37.  * @version $Revision: 1.6 $
  38.  */
  39. public class ExhaustiveSearch extends ASSearch 
  40.   implements StartSetHandler, OptionHandler {
  41.   /** 
  42.    * holds a starting set as an array of attributes.
  43.    */
  44.   private int[] m_starting;
  45.   /** the start set as a Range */
  46.   private Range m_startRange;
  47.   /** the best feature set found during the search */
  48.   private BitSet m_bestGroup;
  49.   /** the merit of the best subset found */
  50.   private double m_bestMerit;
  51.  /** does the data have a class */
  52.   private boolean m_hasClass;
  53.  
  54.   /** holds the class index */
  55.   private int m_classIndex;
  56.  
  57.   /** number of attributes in the data */
  58.   private int m_numAttribs;
  59.   /** if true, then ouput new best subsets as the search progresses */
  60.   private boolean m_verbose;
  61.   /** 
  62.    * stop after finding the first subset equal to or better than the
  63.    * supplied start set (set to true if start set is supplied).
  64.    */
  65.   private boolean m_stopAfterFirst;
  66.   
  67.   /** the number of subsets evaluated during the search */
  68.   private int m_evaluations;
  69.   /**
  70.    * Returns a string describing this search method
  71.    * @return a description of the search suitable for
  72.    * displaying in the explorer/experimenter gui
  73.    */
  74.   public String globalInfo() {
  75.     return "ExhaustiveSearch : nnPerforms an exhaustive search through "
  76.       +"the space of attribute subsets starting from the empty set of "
  77.       +"attrubutes. Reports the best subset found. If a start set is "
  78.       +"supplied, the algorithm searches backward from the start point "
  79.       +"and reports the smallest subset with as good or better evaluation "
  80.       +"as the start point.n";
  81.   }
  82.   /**
  83.    * Constructor
  84.    */
  85.   public ExhaustiveSearch () {
  86.     resetOptions();
  87.   }
  88.   /**
  89.    * Returns an enumeration describing the available options
  90.    * @return an enumeration of all the available options
  91.    **/
  92.   public Enumeration listOptions () {
  93.     Vector newVector = new Vector(2);
  94.     newVector.addElement(new Option("tSpecify a starting set of attributes." 
  95.     + "ntEg. 1,3,5-7."
  96.     +"ntIf a start point is supplied,"
  97.     +"ntExhaustive search stops after"
  98.     +"ntfinding the smallest possible subset"
  99.     +"ntwith merit as good as or better than"
  100.     +"ntthe start set."
  101.     ,"P",1
  102.     , "-P <start set>"));
  103.     newVector.addElement(new Option("tOutput subsets as the search progresses."
  104.     +"nt(default = false)."
  105.     , "V", 0
  106.     , "-V"));
  107.     return  newVector.elements();
  108.   }
  109.   /**
  110.    * Parses a given list of options.
  111.    *
  112.    * Valid options are: <p>
  113.    *
  114.    * -P <start set> <br>
  115.    * Specify a starting set of attributes. Eg 1,4,7-9. <p>
  116.    *
  117.    * -V <br>
  118.    * Verbose output. Output new best subsets as the search progresses. <p>
  119.    *
  120.    * @param options the list of options as an array of strings
  121.    * @exception Exception if an option is not supported
  122.    *
  123.    **/
  124.   public void setOptions (String[] options)
  125.     throws Exception
  126.   {
  127.     String optionString;
  128.     resetOptions();
  129.     optionString = Utils.getOption('P', options);
  130.     if (optionString.length() != 0) {
  131.       setStartSet(optionString);
  132.     }
  133.     setVerbose(Utils.getFlag('V',options));
  134.   }
  135.   /**
  136.    * Returns the tip text for this property
  137.    * @return tip text for this property suitable for
  138.    * displaying in the explorer/experimenter gui
  139.    */
  140.   public String startSetTipText() {
  141.     return "Set the start point for the search. This is specified as a comma "
  142.       +"seperated list off attribute indexes starting at 1. It can include "
  143.       +"ranges. Eg. 1,2,5-9,17.";
  144.   }
  145.   /**
  146.    * Sets a starting set of attributes for the search. It is the
  147.    * search method's responsibility to report this start set (if any)
  148.    * in its toString() method.
  149.    * @param startSet a string containing a list of attributes (and or ranges),
  150.    * eg. 1,2,6,10-15. "" indicates no start set.
  151.    * If a start point is supplied, Exhaustive search stops after finding
  152.    * the smallest possible subset with merit as good as or better than the
  153.    * start set. Otherwise, the search space is explored FULLY, and the
  154.    * best subset returned.
  155.    * @exception Exception if start set can't be set.
  156.    */
  157.   public void setStartSet (String startSet) throws Exception {
  158.     m_startRange.setRanges(startSet);
  159.   }
  160.   /**
  161.    * Returns a list of attributes (and or attribute ranges) as a String
  162.    * @return a list of attributes (and or attribute ranges)
  163.    */
  164.   public String getStartSet () {
  165.     return m_startRange.getRanges();
  166.   }
  167.   
  168.   /**
  169.    * Returns the tip text for this property
  170.    * @return tip text for this property suitable for
  171.    * displaying in the explorer/experimenter gui
  172.    */
  173.   public String verboseTipText() {
  174.     return "Print progress information. Sends progress info to the terminal "
  175.       +"as the search progresses.";
  176.   }
  177.   /**
  178.    * set whether or not to output new best subsets as the search proceeds
  179.    * @param v true if output is to be verbose
  180.    */
  181.   public void setVerbose(boolean v) {
  182.     m_verbose = v;
  183.   }
  184.   /**
  185.    * get whether or not output is verbose
  186.    * @return true if output is set to verbose
  187.    */
  188.   public boolean getVerbose() {
  189.     return m_verbose;
  190.   }
  191.   /**
  192.    * Gets the current settings of RandomSearch.
  193.    * @return an array of strings suitable for passing to setOptions()
  194.    */
  195.   public String[] getOptions () {
  196.     String[] options = new String[3];
  197.     int current = 0;
  198.     if (!(getStartSet().equals(""))) {
  199.       options[current++] = "-P";
  200.       options[current++] = ""+startSetToString();
  201.     }
  202.     if (m_verbose) {
  203.       options[current++] = "-V";
  204.     }
  205.     while (current < options.length) {
  206.       options[current++] = "";
  207.     }
  208.     return  options;
  209.   }
  210.   /**
  211.    * converts the array of starting attributes to a string. This is
  212.    * used by getOptions to return the actual attributes specified
  213.    * as the starting set. This is better than using m_startRanges.getRanges()
  214.    * as the same start set can be specified in different ways from the
  215.    * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
  216.    * is stored in a database is comparable.
  217.    * @return a comma seperated list of individual attribute numbers as a String
  218.    */
  219.   private String startSetToString() {
  220.     StringBuffer FString = new StringBuffer();
  221.     boolean didPrint;
  222.     
  223.     if (m_starting == null) {
  224.       return getStartSet();
  225.     }
  226.     for (int i = 0; i < m_starting.length; i++) {
  227.       didPrint = false;
  228.       
  229.       if ((m_hasClass == false) || 
  230.   (m_hasClass == true && i != m_classIndex)) {
  231. FString.append((m_starting[i] + 1));
  232. didPrint = true;
  233.       }
  234.       
  235.       if (i == (m_starting.length - 1)) {
  236. FString.append("");
  237.       }
  238.       else {
  239. if (didPrint) {
  240.   FString.append(",");
  241.   }
  242.       }
  243.     }
  244.     return FString.toString();
  245.   }
  246.   /**
  247.    * prints a description of the search
  248.    * @return a description of the search as a string
  249.    */
  250.   public String toString() {
  251.     StringBuffer text = new StringBuffer();
  252.     
  253.     text.append("tExhaustive Search.ntStart set: ");
  254.     if (m_starting == null) {
  255.       text.append("no attributesn");
  256.     }
  257.     else {
  258.       text.append(startSetToString()+"n");
  259.     }
  260.     text.append("tNumber of evaluations: "+m_evaluations+"n");
  261.     text.append("tMerit of best subset found: "
  262. +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"n");
  263.     return text.toString();
  264.   }
  265.   /**
  266.    * Searches the attribute subset space using an exhaustive search.
  267.    *
  268.    * @param ASEvaluator the attribute evaluator to guide the search
  269.    * @param data the training instances.
  270.    * @return an array (not necessarily ordered) of selected attribute indexes
  271.    * @exception Exception if the search can't be completed
  272.    */
  273.    public int[] search (ASEvaluation ASEval, Instances data)
  274.      throws Exception {
  275.      double best_merit;
  276.      double tempMerit;
  277.      int setSize;
  278.      boolean done = false;
  279.      int sizeOfBest;
  280.      int tempSize;
  281.      
  282.      m_numAttribs = data.numAttributes();
  283.      m_bestGroup = new BitSet(m_numAttribs);
  284.      
  285.      if (!(ASEval instanceof SubsetEvaluator)) {
  286.        throw  new Exception(ASEval.getClass().getName() 
  287.     + " is not a " 
  288.     + "Subset evaluator!");
  289.      }
  290.      
  291.      if (ASEval instanceof UnsupervisedSubsetEvaluator) {
  292.        m_hasClass = false;
  293.      }
  294.      else {
  295.        m_hasClass = true;
  296.        m_classIndex = data.classIndex();
  297.      }
  298.      
  299.      SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
  300.      m_numAttribs = data.numAttributes();
  301.      m_startRange.setUpper(m_numAttribs-1);
  302.      if (!(getStartSet().equals(""))) {
  303.        m_starting = m_startRange.getSelection();
  304.     }
  305.      
  306.      // If a starting subset has been supplied, then initialise the bitset
  307.      if (m_starting != null) {
  308.        m_stopAfterFirst = true;
  309.        for (int i = 0; i < m_starting.length; i++) {
  310.  if ((m_starting[i]) != m_classIndex) {
  311.    m_bestGroup.set(m_starting[i]);
  312.  }
  313.        }
  314.      }
  315.      best_merit = ASEvaluator.evaluateSubset(m_bestGroup);
  316.      m_evaluations++;
  317.      sizeOfBest = countFeatures(m_bestGroup);
  318.      if (m_verbose) {
  319.        if (m_stopAfterFirst) {
  320.  System.out.println("Initial subset ("
  321.     +Utils.doubleToString(Math.
  322.   abs(best_merit),8,5)
  323.     +"): "+printSubset(m_bestGroup));
  324.        }
  325.      }
  326.      BitSet tempGroup = new BitSet(m_numAttribs);
  327.      tempMerit = ASEvaluator.evaluateSubset(tempGroup);
  328.      if (m_verbose) {
  329.        System.out.println("Zero feature subset ("
  330.   +Utils.doubleToString(Math.
  331. abs(tempMerit),8,5)
  332.   +")");
  333.      }
  334.      if (tempMerit >= best_merit) {
  335.        tempSize = countFeatures(tempGroup);
  336.        if (tempMerit > best_merit || 
  337.    (tempSize < sizeOfBest)) {
  338.  best_merit = tempMerit;
  339.  m_bestGroup = (BitSet)(tempGroup.clone());
  340.  sizeOfBest = tempSize;
  341.        }
  342.        if (m_stopAfterFirst) {
  343.  done = true;
  344.        }
  345.      }
  346.      int i,j;
  347.      int subset;
  348.      if (!done) {
  349.        enumerateSizes: for (setSize = 1;setSize<=m_numAttribs;setSize++) {
  350.  // set up and evaluate initial subset of this size
  351.  subset = 0;
  352.  tempGroup = new BitSet(m_numAttribs);
  353.  for (i=0;i<setSize;i++) {
  354.    subset = (subset ^ (1<<i));
  355.    tempGroup.set(i);
  356.    if (m_hasClass && i == m_classIndex) {
  357.      tempGroup.clear(i);
  358.    }
  359.  }
  360.  tempMerit = ASEvaluator.evaluateSubset(tempGroup);
  361.  m_evaluations++;
  362.  if (tempMerit >= best_merit) {
  363.    tempSize = countFeatures(tempGroup);
  364.    if (tempMerit > best_merit || 
  365.        (tempSize < sizeOfBest)) {
  366.      best_merit = tempMerit;
  367.      m_bestGroup = (BitSet)(tempGroup.clone());
  368.      sizeOfBest = tempSize;
  369.      if (m_verbose) {
  370.        System.out.println("New best subset ("
  371. +Utils.doubleToString(Math.
  372.       abs(best_merit),8,5)
  373.   +"): "+printSubset(m_bestGroup));
  374.      }
  375.    }
  376.    if (m_stopAfterFirst) {
  377.      done = true;
  378.      break enumerateSizes;
  379.    }
  380.  }
  381.  // generate all the other subsets of this size
  382.  while (subset > 0) {
  383.    subset = generateNextSubset(subset, setSize, tempGroup);
  384.    if (subset > 0) {
  385.      tempMerit = ASEvaluator.evaluateSubset(tempGroup);
  386.      m_evaluations++;
  387.      if (tempMerit >= best_merit) {
  388.        tempSize = countFeatures(tempGroup);
  389.        if (tempMerit > best_merit || 
  390.    (tempSize < sizeOfBest)) {
  391.  best_merit = tempMerit;
  392.  m_bestGroup = (BitSet)(tempGroup.clone());
  393.  sizeOfBest = tempSize;
  394.  if (m_verbose) {
  395.    System.out.println("New best subset ("
  396.       +Utils.
  397.       doubleToString(Math.
  398.      abs(best_merit),8,5)
  399.       +"): "+printSubset(m_bestGroup));
  400.  }
  401.        }
  402.        if (m_stopAfterFirst) {
  403.  done = true;
  404.  break enumerateSizes;
  405.        }
  406.      }
  407.    }
  408.  }
  409.        }
  410.      }   
  411.      m_bestMerit = best_merit;
  412.      
  413.      return attributeList(m_bestGroup);
  414.    }
  415.   /**
  416.    * counts the number of features in a subset
  417.    * @param featureSet the feature set for which to count the features
  418.    * @return the number of features in the subset
  419.    */
  420.   private int countFeatures(BitSet featureSet) {
  421.     int count = 0;
  422.     for (int i=0;i<m_numAttribs;i++) {
  423.       if (featureSet.get(i)) {
  424. count++;
  425.       }
  426.     }
  427.     return count;
  428.   }   
  429.   /**
  430.    * prints a subset as a series of attribute numbers
  431.    * @param temp the subset to print
  432.    * @return a subset as a String of attribute numbers
  433.    */
  434.   private String printSubset(BitSet temp) {
  435.     StringBuffer text = new StringBuffer();
  436.     for (int j=0;j<m_numAttribs;j++) {
  437.       if (temp.get(j)) {
  438.         text.append((j+1)+" ");
  439.       }
  440.     }
  441.     return text.toString();
  442.   }
  443.   /**
  444.    * converts a BitSet into a list of attribute indexes 
  445.    * @param group the BitSet to convert
  446.    * @return an array of attribute indexes
  447.    **/
  448.   private int[] attributeList (BitSet group) {
  449.     int count = 0;
  450.     
  451.     // count how many were selected
  452.     for (int i = 0; i < m_numAttribs; i++) {
  453.       if (group.get(i)) {
  454. count++;
  455.       }
  456.     }
  457.     
  458.     int[] list = new int[count];
  459.     count = 0;
  460.     
  461.     for (int i = 0; i < m_numAttribs; i++) {
  462.       if (group.get(i)) {
  463. list[count++] = i;
  464.       }
  465.     }
  466.     
  467.     return  list;
  468.   }
  469.   /**
  470.    * generates the next subset of size "size" given the subset "set"
  471.    * coded as an integer. The next subset is returned (as an Integer) 
  472.    * and temp contains this subset as a BitSet.
  473.    * @param set the current subset coded as an integer
  474.    * @param size the size of the feature subset (eg. 2 means that the 
  475.    * current subset contains two features and the next generated subset
  476.    * should also contain 2 features).
  477.    * @param temp will hold the generated subset as a BitSet
  478.    */
  479.   private int generateNextSubset(int set, int size, BitSet temp) {
  480.     int i,j;
  481.     int counter = 0;
  482.     boolean done = false;
  483.     for (i=0;i<m_numAttribs;i++) {
  484.       temp.clear(i);
  485.     }
  486.     while ((!done) && (counter < size)) {
  487.       for (i=m_numAttribs-1-counter;i>=0;i--) {
  488. if ((set & (1<<i)) !=0) {
  489.   // erase and move
  490.   set = (set ^ (1<<i));
  491.   if (i != (m_numAttribs-1-counter)) {
  492.     set = (set ^ (1<<i+1));
  493.     for (j=0;j<counter;j++) {
  494.       set = (set ^ (1<<(i+2+j)));
  495.     }
  496.     done = true;
  497.     break;
  498.   } else {
  499.     counter++;
  500.     break;
  501.   }
  502. }
  503.       }
  504.     }
  505.     for (i=m_numAttribs-1;i>=0;i--) {
  506.       if ((set & (1<<i)) != 0) {
  507. if (i != m_classIndex) {
  508.   temp.set(i);
  509. }
  510.       }
  511.     }
  512.     return set;
  513.   }
  514.       
  515.   /**
  516.    * resets to defaults
  517.    */
  518.   private void resetOptions() {
  519.     m_starting = null;
  520.     m_startRange = new Range();
  521.     m_stopAfterFirst = false;
  522.     m_verbose = false;
  523.     m_evaluations = 0;
  524.   }
  525. }