BestFirst.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 19k
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.  *    BestFirst.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 a best first 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.  * -D <-1 = backward | 0 = bidirectional | 1 = forward> <br>
  34.  * Direction of the search. (default = 1). <p>
  35.  *
  36.  * -N <num> <br>
  37.  * Number of non improving nodes to consider before terminating search.
  38.  * (default = 5). <p>
  39.  *
  40.  * @author Mark Hall (mhall@cs.waikato.ac.nz)
  41.  * @version $Revision: 1.20 $
  42.  */
  43. public class BestFirst extends ASSearch 
  44.   implements OptionHandler, StartSetHandler
  45. {
  46.   // Inner classes
  47.   /**
  48.    * Class for a node in a linked list. Used in best first search.
  49.    * @author Mark Hall (mhall@cs.waikato.ac.nz)
  50.    **/
  51.   public class Link2 {
  52.     BitSet group;
  53.     double merit;
  54.     // Constructor
  55.     public Link2 (BitSet gr, double mer) {
  56.       group = (BitSet)gr.clone();
  57.       merit = mer;
  58.     }
  59.     /** Get a group */
  60.     public BitSet getGroup () {
  61.       return  group;
  62.     }
  63.     public String toString () {
  64.       return  ("Node: " + group.toString() + "  " + merit);
  65.     }
  66.   }
  67.   /**
  68.    * Class for handling a linked list. Used in best first search.
  69.    * Extends the Vector class.
  70.    * @author Mark Hall (mhall@cs.waikato.ac.nz)
  71.    **/
  72.   public class LinkedList2
  73.     extends FastVector
  74.   {
  75.     // Max number of elements in the list
  76.     int m_MaxSize;
  77.     // ================
  78.     // Public methods
  79.     // ================
  80.     public LinkedList2 (int sz) {
  81.       super();
  82.       m_MaxSize = sz;
  83.     }
  84.     /**
  85.      * removes an element (Link) at a specific index from the list.
  86.      * @param index the index of the element to be removed.
  87.      **/
  88.     public void removeLinkAt (int index)
  89.       throws Exception
  90.     {
  91.       if ((index >= 0) && (index < size())) {
  92.         removeElementAt(index);
  93.       }
  94.       else {
  95.         throw  new Exception("index out of range (removeLinkAt)");
  96.       }
  97.     }
  98.     /**
  99.      * returns the element (Link) at a specific index from the list.
  100.      * @param index the index of the element to be returned.
  101.      **/
  102.     public Link2 getLinkAt (int index)
  103.       throws Exception
  104.     {
  105.       if (size() == 0) {
  106.         throw  new Exception("List is empty (getLinkAt)");
  107.       }
  108.       else {if ((index >= 0) && (index < size())) {
  109.         return  ((Link2)(elementAt(index)));
  110.       }
  111.       else {
  112.         throw  new Exception("index out of range (getLinkAt)");
  113.       }
  114.       }
  115.     }
  116.     /**
  117.      * adds an element (Link) to the list.
  118.      * @param gr the attribute set specification
  119.      * @param mer the "merit" of this attribute set
  120.      **/
  121.     public void addToList (BitSet gr, double mer)
  122.       throws Exception
  123.     {
  124.       Link2 newL = new Link2(gr, mer);
  125.       if (size() == 0) {
  126. addElement(newL);
  127.       }
  128.       else {if (mer > ((Link2)(firstElement())).merit) {
  129. if (size() == m_MaxSize) {
  130.   removeLinkAt(m_MaxSize - 1);
  131. }
  132. //----------
  133. insertElementAt(newL, 0);
  134.       }
  135.       else {
  136. int i = 0;
  137. int size = size();
  138. boolean done = false;
  139. //------------
  140. // don't insert if list contains max elements an this
  141. // is worst than the last
  142. if ((size == m_MaxSize) && (mer <= ((Link2)(lastElement())).merit)) {
  143. }
  144. //---------------
  145. else {
  146.   while ((!done) && (i < size)) {
  147.     if (mer > ((Link2)(elementAt(i))).merit) {
  148.       if (size == m_MaxSize) {
  149. removeLinkAt(m_MaxSize - 1);
  150.       }
  151.       // ---------------------
  152.       insertElementAt(newL, i);
  153.       done = true;
  154.     }
  155.     else {if (i == size - 1) {
  156.       addElement(newL);
  157.       done = true;
  158.     }
  159.     else {
  160.       i++;
  161.     }
  162.     }
  163.   }
  164. }
  165.       }
  166.       }
  167.     }
  168.   }
  169.   // member variables
  170.   /** maximum number of stale nodes before terminating search */
  171.   private int m_maxStale;
  172.   /** 0 == backward search, 1 == forward search, 2 == bidirectional */
  173.   private int m_searchDirection;
  174.   /** search directions */
  175.   private static final int SELECTION_BACKWARD = 0;
  176.   private static final int SELECTION_FORWARD = 1;
  177.   private static final int SELECTION_BIDIRECTIONAL = 2;
  178.   public static final Tag [] TAGS_SELECTION = {
  179.     new Tag(SELECTION_BACKWARD, "Backward"),
  180.     new Tag(SELECTION_FORWARD, "Forward"),
  181.     new Tag(SELECTION_BIDIRECTIONAL, "Bi-directional"),
  182.   };
  183.   /** holds an array of starting attributes */
  184.   private int[] m_starting;
  185.   /** holds the start set for the search as a Range */
  186.   private Range m_startRange;
  187.   /** does the data have a class */
  188.   private boolean m_hasClass;
  189.   /** holds the class index */
  190.   private int m_classIndex;
  191.   /** number of attributes in the data */
  192.   private int m_numAttribs;
  193.   /** total number of subsets evaluated during a search */
  194.   private int m_totalEvals;
  195.   /** for debugging */
  196.   private boolean m_debug;
  197.   /** holds the merit of the best subset found */
  198.   private double m_bestMerit;
  199.   
  200.   /**
  201.    * Returns a string describing this search method
  202.    * @return a description of the search method suitable for
  203.    * displaying in the explorer/experimenter gui
  204.    */
  205.   public String globalInfo() {
  206.     return "BestFirst:nn"
  207.       +"Searches the space of attribute subsets by greedy hillclimbing "
  208.       +"augmented with a backtracking facility. Setting the number of "
  209.       +"consecutive non-improving nodes allowed controls the level of "
  210.       +"backtracking done. Best first may start with the empty set of "
  211.       +"attributes and search forward, or start with the full set of "
  212.       +"attributes and search backward, or start at any point and search "
  213.       +"in both directions (by considering all possible single attribute "
  214.       +"additions and deletions at a given point).n";
  215.   }
  216.   /** 
  217.    *Constructor 
  218.    */
  219.   public BestFirst () {
  220.     resetOptions();
  221.   }
  222.   /**
  223.    * Returns an enumeration describing the available options.
  224.    * @return an enumeration of all the available options.
  225.    *
  226.    **/
  227.   public Enumeration listOptions () {
  228.     Vector newVector = new Vector(3);
  229.     
  230.     newVector.addElement(new Option("tSpecify a starting set of attributes." 
  231.     + "ntEg. 1,3,5-7."
  232.     ,"P",1
  233.     , "-P <start set>"));
  234.     newVector.addElement(new Option("tDirection of search. (default = 1)."
  235.     , "D", 1
  236.     , "-D <0 = backward | 1 = forward " 
  237.     + "| 2 = bi-directional>"));
  238.     newVector.addElement(new Option("tNumber of non-improving nodes to" 
  239.     + "ntconsider before terminating search."
  240.     , "N", 1, "-N <num>"));
  241.     return  newVector.elements();
  242.   }
  243.   /**
  244.    * Parses a given list of options.
  245.    *
  246.    * Valid options are: <p>
  247.    *
  248.    * -P <start set> <br>
  249.    * Specify a starting set of attributes. Eg 1,4,7-9. <p>
  250.    *
  251.    * -D <-1 = backward | 0 = bidirectional | 1 = forward> <br>
  252.    * Direction of the search. (default = 1). <p>
  253.    *
  254.    * -N <num> <br>
  255.    * Number of non improving nodes to consider before terminating search.
  256.    * (default = 5). <p>
  257.    * @param options the list of options as an array of strings
  258.    * @exception Exception if an option is not supported
  259.    *
  260.    **/
  261.   public void setOptions (String[] options)
  262.     throws Exception
  263.   {
  264.     String optionString;
  265.     resetOptions();
  266.     optionString = Utils.getOption('P', options);
  267.     if (optionString.length() != 0) {
  268.       setStartSet(optionString);
  269.     }
  270.     optionString = Utils.getOption('D', options);
  271.     if (optionString.length() != 0) {
  272.       setDirection(new SelectedTag(Integer.parseInt(optionString),
  273.    TAGS_SELECTION));
  274.     } else {
  275.       setDirection(new SelectedTag(SELECTION_FORWARD, TAGS_SELECTION));
  276.     }
  277.     optionString = Utils.getOption('N', options);
  278.     if (optionString.length() != 0) {
  279.       setSearchTermination(Integer.parseInt(optionString));
  280.     }
  281.     m_debug = Utils.getFlag('Z', options);
  282.   }
  283.   /**
  284.    * Returns the tip text for this property
  285.    * @return tip text for this property suitable for
  286.    * displaying in the explorer/experimenter gui
  287.    */
  288.   public String startSetTipText() {
  289.     return "Set the start point for the search. This is specified as a comma "
  290.       +"seperated list off attribute indexes starting at 1. It can include "
  291.       +"ranges. Eg. 1,2,5-9,17.";
  292.   }
  293.   /**
  294.    * Sets a starting set of attributes for the search. It is the
  295.    * search method's responsibility to report this start set (if any)
  296.    * in its toString() method.
  297.    * @param startSet a string containing a list of attributes (and or ranges),
  298.    * eg. 1,2,6,10-15.
  299.    * @exception Exception if start set can't be set.
  300.    */
  301.   public void setStartSet (String startSet) throws Exception {
  302.     m_startRange.setRanges(startSet);
  303.   }
  304.   /**
  305.    * Returns a list of attributes (and or attribute ranges) as a String
  306.    * @return a list of attributes (and or attribute ranges)
  307.    */
  308.   public String getStartSet () {
  309.     return m_startRange.getRanges();
  310.   }
  311.   /**
  312.    * Returns the tip text for this property
  313.    * @return tip text for this property suitable for
  314.    * displaying in the explorer/experimenter gui
  315.    */
  316.   public String searchTerminationTipText() {
  317.     return "Set the amount of backtracking. Specify the number of ";
  318.   }
  319.   /**
  320.    * Set the numnber of non-improving nodes to consider before terminating
  321.    * search.
  322.    *
  323.    * @param t the number of non-improving nodes
  324.    * @exception Exception if t is less than 1
  325.    */
  326.   public void setSearchTermination (int t)
  327.     throws Exception
  328.   {
  329.     if (t < 1) {
  330.       throw  new Exception("Value of -N must be > 0.");
  331.     }
  332.     m_maxStale = t;
  333.   }
  334.   /**
  335.    * Get the termination criterion (number of non-improving nodes).
  336.    *
  337.    * @return the number of non-improving nodes
  338.    */
  339.   public int getSearchTermination () {
  340.     return  m_maxStale;
  341.   }
  342.   /**
  343.    * Returns the tip text for this property
  344.    * @return tip text for this property suitable for
  345.    * displaying in the explorer/experimenter gui
  346.    */
  347.   public String directionTipText() {
  348.     return "Set the direction of the search.";
  349.   }
  350.   /**
  351.    * Set the search direction
  352.    *
  353.    * @param d the direction of the search
  354.    */
  355.   public void setDirection (SelectedTag d) {
  356.     
  357.     if (d.getTags() == TAGS_SELECTION) {
  358.       m_searchDirection = d.getSelectedTag().getID();
  359.     }
  360.   }
  361.   /**
  362.    * Get the search direction
  363.    *
  364.    * @return the direction of the search
  365.    */
  366.   public SelectedTag getDirection () {
  367.     return new SelectedTag(m_searchDirection, TAGS_SELECTION);
  368.   }
  369.   /**
  370.    * Gets the current settings of BestFirst.
  371.    * @return an array of strings suitable for passing to setOptions()
  372.    */
  373.   public String[] getOptions () {
  374.     String[] options = new String[6];
  375.     int current = 0;
  376.     if (!(getStartSet().equals(""))) {
  377.       options[current++] = "-P";
  378.       options[current++] = ""+startSetToString();
  379.     }
  380.     options[current++] = "-D";
  381.     options[current++] = "" + m_searchDirection;
  382.     options[current++] = "-N";
  383.     options[current++] = "" + m_maxStale;
  384.     while (current < options.length) {
  385.       options[current++] = "";
  386.     }
  387.     return  options;
  388.   }
  389.   /**
  390.    * converts the array of starting attributes to a string. This is
  391.    * used by getOptions to return the actual attributes specified
  392.    * as the starting set. This is better than using m_startRanges.getRanges()
  393.    * as the same start set can be specified in different ways from the
  394.    * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
  395.    * is stored in a database is comparable.
  396.    * @return a comma seperated list of individual attribute numbers as a String
  397.    */
  398.   private String startSetToString() {
  399.     StringBuffer FString = new StringBuffer();
  400.     boolean didPrint;
  401.     if (m_starting == null) {
  402.       return getStartSet();
  403.     }
  404.     for (int i = 0; i < m_starting.length; i++) {
  405.       didPrint = false;
  406.       
  407.       if ((m_hasClass == false) || 
  408.   (m_hasClass == true && i != m_classIndex)) {
  409. FString.append((m_starting[i] + 1));
  410. didPrint = true;
  411.       }
  412.       
  413.       if (i == (m_starting.length - 1)) {
  414. FString.append("");
  415.       }
  416.       else {
  417. if (didPrint) {
  418.   FString.append(",");
  419.   }
  420.       }
  421.     }
  422.     return FString.toString();
  423.   }
  424.   /**
  425.    * returns a description of the search as a String
  426.    * @return a description of the search
  427.    */
  428.   public String toString () {
  429.     StringBuffer BfString = new StringBuffer();
  430.     BfString.append("tBest first.ntStart set: ");
  431.     if (m_starting == null) {
  432.       BfString.append("no attributesn");
  433.     }
  434.     else {
  435.       BfString.append(startSetToString()+"n");
  436.     }
  437.     BfString.append("tSearch direction: ");
  438.     if (m_searchDirection == SELECTION_BACKWARD) {
  439.       BfString.append("backwardn");
  440.     }
  441.     else {if (m_searchDirection == SELECTION_FORWARD) {
  442.       BfString.append("forwardn");
  443.     }
  444.     else {
  445.       BfString.append("bi-directionaln");
  446.     }
  447.     }
  448.     BfString.append("tStale search after " 
  449.     + m_maxStale + " node expansionsn");
  450.     BfString.append("tTotal number of subsets evaluated: " 
  451.     + m_totalEvals + "n");
  452.     BfString.append("tMerit of best subset found: "
  453.     +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"n");
  454.     return  BfString.toString();
  455.   }
  456.   private void printGroup (BitSet tt, int numAttribs) {
  457.     int i;
  458.     for (i = 0; i < numAttribs; i++) {
  459.       if (tt.get(i) == true) {
  460. System.out.print((i + 1) + " ");
  461.       }
  462.     }
  463.     System.out.println();
  464.   }
  465.   /**
  466.    * Searches the attribute subset space by best first search
  467.    *
  468.    * @param ASEvaluator the attribute evaluator to guide the search
  469.    * @param data the training instances.
  470.    * @return an array (not necessarily ordered) of selected attribute indexes
  471.    * @exception Exception if the search can't be completed
  472.    */
  473.   public int[] search (ASEvaluation ASEval, Instances data)
  474.     throws Exception
  475.   {
  476.     m_totalEvals = 0;
  477.     if (!(ASEval instanceof SubsetEvaluator)) {
  478.       throw  new Exception(ASEval.getClass().getName() 
  479.    + " is not a " 
  480.    + "Subset evaluator!");
  481.     }
  482.     if (ASEval instanceof UnsupervisedSubsetEvaluator) {
  483.       m_hasClass = false;
  484.     }
  485.     else {
  486.       m_hasClass = true;
  487.       m_classIndex = data.classIndex();
  488.     }
  489.     SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
  490.     m_numAttribs = data.numAttributes();
  491.     int i, j;
  492.     int best_size = 0;
  493.     int size = 0;
  494.     int done;
  495.     int sd = m_searchDirection;
  496.     int evals = 0;
  497.     BitSet best_group, temp_group;
  498.     int stale;
  499.     double best_merit;
  500.     boolean ok = true;
  501.     double merit;
  502.     boolean z;
  503.     boolean added;
  504.     Link2 tl;
  505.     Hashtable lookup = new Hashtable((int)(200.0*m_numAttribs*1.5));
  506.     LinkedList2 bfList = new LinkedList2(m_maxStale);
  507.     best_merit = -Double.MAX_VALUE;
  508.     stale = 0;
  509.     best_group = new BitSet(m_numAttribs);
  510.     m_startRange.setUpper(m_numAttribs-1);
  511.     if (!(getStartSet().equals(""))) {
  512.       m_starting = m_startRange.getSelection();
  513.     }
  514.     // If a starting subset has been supplied, then initialise the bitset
  515.     if (m_starting != null) {
  516.       for (i = 0; i < m_starting.length; i++) {
  517. if ((m_starting[i]) != m_classIndex) {
  518.   best_group.set(m_starting[i]);
  519. }
  520.       }
  521.       best_size = m_starting.length;
  522.       m_totalEvals++;
  523.     }
  524.     else {
  525.       if (m_searchDirection == SELECTION_BACKWARD) {
  526. setStartSet("1-last");
  527. m_starting = new int[m_numAttribs];
  528. // init initial subset to all attributes
  529. for (i = 0, j = 0; i < m_numAttribs; i++) {
  530.   if (i != m_classIndex) {
  531.     best_group.set(i);
  532.     m_starting[j++] = i;
  533.   }
  534. }
  535. best_size = m_numAttribs - 1;
  536. m_totalEvals++;
  537.       }
  538.     }
  539.     // evaluate the initial subset
  540.     best_merit = ASEvaluator.evaluateSubset(best_group);
  541.     // add the initial group to the list and the hash table
  542.     bfList.addToList(best_group, best_merit);
  543.     BitSet tt = (BitSet)best_group.clone();
  544.     lookup.put(tt, "");
  545.     while (stale < m_maxStale) {
  546.       added = false;
  547.       if (m_searchDirection == SELECTION_BIDIRECTIONAL) {
  548. // bi-directional search
  549.   done = 2;
  550.   sd = SELECTION_FORWARD;
  551. }
  552.       else {
  553. done = 1;
  554.       }
  555.       // finished search?
  556.       if (bfList.size() == 0) {
  557. stale = m_maxStale;
  558. break;
  559.       }
  560.       // copy the attribute set at the head of the list
  561.       tl = bfList.getLinkAt(0);
  562.       temp_group = (BitSet)(tl.getGroup().clone());
  563.       // remove the head of the list
  564.       bfList.removeLinkAt(0);
  565.       // count the number of bits set (attributes)
  566.       int kk;
  567.       for (kk = 0, size = 0; kk < m_numAttribs; kk++) {
  568. if (temp_group.get(kk)) {
  569.   size++;
  570. }
  571.       }
  572.       do {
  573. for (i = 0; i < m_numAttribs; i++) {
  574.   if (sd == SELECTION_FORWARD) {
  575.     z = ((i != m_classIndex) && (!temp_group.get(i)));
  576.   }
  577.   else {
  578.     z = ((i != m_classIndex) && (temp_group.get(i)));
  579.   }
  580.   if (z) {
  581.     // set the bit (attribute to add/delete)
  582.     if (sd == SELECTION_FORWARD) {
  583.       temp_group.set(i);
  584.     }
  585.     else {
  586.       temp_group.clear(i);
  587.     }
  588.     /* if this subset has been seen before, then it is already 
  589.        in the list (or has been fully expanded) */
  590.     tt = (BitSet)temp_group.clone();
  591.     if (lookup.containsKey(tt) == false) {
  592.       merit = ASEvaluator.evaluateSubset(temp_group);
  593.       m_totalEvals++;
  594.       if (m_debug) {
  595. System.out.print("Group: ");
  596. printGroup(tt, m_numAttribs);
  597. System.out.println("Merit: " + merit);
  598.       }
  599.       // is this better than the best?
  600.       if (sd == SELECTION_FORWARD) {
  601. z = ((merit - best_merit) > 0.00001);
  602.       }
  603.       else {
  604. z = ((merit >= best_merit) && ((size) < best_size));
  605.       }
  606.       if (z) {
  607. added = true;
  608. stale = 0;
  609. best_merit = merit;
  610. best_size = (size + best_size);
  611. best_group = (BitSet)(temp_group.clone());
  612.       }
  613.       // insert this one in the list and in the hash table
  614.       bfList.addToList(tt, merit);
  615.       lookup.put(tt, "");
  616.     }
  617.     // unset this addition(deletion)
  618.     if (sd == SELECTION_FORWARD) {
  619.       temp_group.clear(i);
  620.     }
  621.     else {
  622.       temp_group.set(i);
  623.     }
  624.   }
  625. }
  626. if (done == 2) {
  627.   sd = SELECTION_BACKWARD;
  628. }
  629. done--;
  630.       } while (done > 0);
  631.       /* if we haven't added a new attribute subset then full expansion 
  632.  of this node hasen't resulted in anything better */
  633.       if (!added) {
  634. stale++;
  635.       }
  636.     }
  637.     m_bestMerit = best_merit;
  638.     return  attributeList(best_group);
  639.   }
  640.   /**
  641.    * Reset options to default values
  642.    */
  643.   protected void resetOptions () {
  644.     m_maxStale = 5;
  645.     m_searchDirection = SELECTION_FORWARD;
  646.     m_starting = null;
  647.     m_startRange = new Range();
  648.     m_classIndex = -1;
  649.     m_totalEvals = 0;
  650.     m_debug = false;
  651.   }
  652.   /**
  653.    * converts a BitSet into a list of attribute indexes 
  654.    * @param group the BitSet to convert
  655.    * @return an array of attribute indexes
  656.    **/
  657.   private int[] attributeList (BitSet group) {
  658.     int count = 0;
  659.     // count how many were selected
  660.     for (int i = 0; i < m_numAttribs; i++) {
  661.       if (group.get(i)) {
  662. count++;
  663.       }
  664.     }
  665.     int[] list = new int[count];
  666.     count = 0;
  667.     for (int i = 0; i < m_numAttribs; i++) {
  668.       if (group.get(i)) {
  669. list[count++] = i;
  670.       }
  671.     }
  672.     return  list;
  673.   }
  674. }