ForwardSelection.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 17k
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.  *    ForwardSelection.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 forward selection hill climbing 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.  * -R <br>
  34.  * Produce a ranked list of attributes. <p>
  35.  * 
  36.  * -T <threshold> <br>
  37.  * Specify a threshold by which the AttributeSelection module can. <br>
  38.  * discard attributes. Use in conjunction with -R <p>
  39.  *
  40.  * @author Mark Hall (mhall@cs.waikato.ac.nz)
  41.  * @version $Revision: 1.14 $
  42.  */
  43. public class ForwardSelection extends ASSearch 
  44.   implements RankedOutputSearch, StartSetHandler, OptionHandler {
  45.  /** does the data have a class */
  46.   private boolean m_hasClass;
  47.  
  48.   /** holds the class index */
  49.   private int m_classIndex;
  50.  
  51.   /** number of attributes in the data */
  52.   private int m_numAttribs;
  53.   /** true if the user has requested a ranked list of attributes */
  54.   private boolean m_rankingRequested;
  55.   /** 
  56.    * go from one side of the search space to the other in order to generate
  57.    * a ranking
  58.    */
  59.   private boolean m_doRank;
  60.   /** used to indicate whether or not ranking has been performed */
  61.   private boolean m_doneRanking;
  62.   /**
  63.    * A threshold by which to discard attributes---used by the
  64.    * AttributeSelection module
  65.    */
  66.   private double m_threshold;
  67.   /** The number of attributes to select. -1 indicates that all attributes
  68.       are to be retained. Has precedence over m_threshold */
  69.   private int m_numToSelect = -1;
  70.   private int m_calculatedNumToSelect;
  71.   /** the merit of the best subset found */
  72.   private double m_bestMerit;
  73.   /** a ranked list of attribute indexes */
  74.   private double [][] m_rankedAtts;
  75.   private int m_rankedSoFar;
  76.   /** the best subset found */
  77.   private BitSet m_best_group;
  78.   private ASEvaluation m_ASEval;
  79.   private Instances m_Instances;
  80.   /** holds the start set for the search as a Range */
  81.   private Range m_startRange;
  82.   /** holds an array of starting attributes */
  83.   private int [] m_starting;
  84.   /**
  85.    * Returns a string describing this search method
  86.    * @return a description of the search suitable for
  87.    * displaying in the explorer/experimenter gui
  88.    */
  89.   public String globalInfo() {
  90.     return "ForwardSelection :nnPerforms a greedy forward search through "
  91.       +"the space of attribute subsets. May start with no attributes or from "
  92.       +"an arbitrary point in the space. Stops when the addition of any "
  93.       +"remaining attributes results in a decrease in evaluation. "
  94.       +"Can also produce a ranked list of "
  95.       +"attributes by traversing the space from one side to the other and "
  96.       +"recording the order that attributes are selected.n";
  97.   }
  98.   public ForwardSelection () {
  99.     m_threshold = -Double.MAX_VALUE;
  100.     m_doneRanking = false;
  101.     m_startRange = new Range();
  102.     m_starting = null;
  103.     resetOptions();
  104.   }
  105.   /**
  106.    * Returns the tip text for this property
  107.    * @return tip text for this property suitable for
  108.    * displaying in the explorer/experimenter gui
  109.    */
  110.   public String thresholdTipText() {
  111.     return "Set threshold by which attributes can be discarded. Default value "
  112.       + "results in no attributes being discarded. Use in conjunction with "
  113.       + "generateRanking";
  114.   }
  115.   /**
  116.    * Set the threshold by which the AttributeSelection module can discard
  117.    * attributes.
  118.    * @param threshold the threshold.
  119.    */
  120.   public void setThreshold(double threshold) {
  121.     m_threshold = threshold;
  122.   }
  123.   /**
  124.    * Returns the threshold so that the AttributeSelection module can
  125.    * discard attributes from the ranking.
  126.    */
  127.   public double getThreshold() {
  128.     return m_threshold;
  129.   }
  130.   /**
  131.    * Returns the tip text for this property
  132.    * @return tip text for this property suitable for
  133.    * displaying in the explorer/experimenter gui
  134.    */
  135.   public String numToSelectTipText() {
  136.     return "Specify the number of attributes to retain. The default value "
  137.       +"(-1) indicates that all attributes are to be retained. Use either "
  138.       +"this option or a threshold to reduce the attribute set.";
  139.   }
  140.   /**
  141.    * Specify the number of attributes to select from the ranked list
  142.    * (if generating a ranking). -1
  143.    * indicates that all attributes are to be retained.
  144.    * @param n the number of attributes to retain
  145.    */
  146.   public void setNumToSelect(int n) {
  147.     m_numToSelect = n;
  148.   }
  149.   /**
  150.    * Gets the number of attributes to be retained.
  151.    * @return the number of attributes to retain
  152.    */
  153.   public int getNumToSelect() {
  154.     return m_numToSelect;
  155.   }
  156.   /**
  157.    * Gets the calculated number of attributes to retain. This is the
  158.    * actual number of attributes to retain. This is the same as
  159.    * getNumToSelect if the user specifies a number which is not less
  160.    * than zero. Otherwise it should be the number of attributes in the
  161.    * (potentially transformed) data.
  162.    */
  163.   public int getCalculatedNumToSelect() {
  164.     if (m_numToSelect >= 0) {
  165.       m_calculatedNumToSelect = m_numToSelect;
  166.     }
  167.     return m_calculatedNumToSelect;
  168.   }
  169.   /**
  170.    * Returns the tip text for this property
  171.    * @return tip text for this property suitable for
  172.    * displaying in the explorer/experimenter gui
  173.    */
  174.   public String generateRankingTipText() {
  175.     return "Set to true if a ranked list is required.";
  176.   }
  177.   
  178.   /**
  179.    * Records whether the user has requested a ranked list of attributes.
  180.    * @param doRank true if ranking is requested
  181.    */
  182.   public void setGenerateRanking(boolean doRank) {
  183.     m_rankingRequested = doRank;
  184.   }
  185.   /**
  186.    * Gets whether ranking has been requested. This is used by the
  187.    * AttributeSelection module to determine if rankedAttributes()
  188.    * should be called.
  189.    * @return true if ranking has been requested.
  190.    */
  191.   public boolean getGenerateRanking() {
  192.     return m_rankingRequested;
  193.   }
  194.   /**
  195.    * Returns the tip text for this property
  196.    * @return tip text for this property suitable for
  197.    * displaying in the explorer/experimenter gui
  198.    */
  199.   public String startSetTipText() {
  200.     return "Set the start point for the search. This is specified as a comma "
  201.       +"seperated list off attribute indexes starting at 1. It can include "
  202.       +"ranges. Eg. 1,2,5-9,17.";
  203.   }
  204.   /**
  205.    * Sets a starting set of attributes for the search. It is the
  206.    * search method's responsibility to report this start set (if any)
  207.    * in its toString() method.
  208.    * @param startSet a string containing a list of attributes (and or ranges),
  209.    * eg. 1,2,6,10-15.
  210.    * @exception Exception if start set can't be set.
  211.    */
  212.   public void setStartSet (String startSet) throws Exception {
  213.     m_startRange.setRanges(startSet);
  214.   }
  215.   /**
  216.    * Returns a list of attributes (and or attribute ranges) as a String
  217.    * @return a list of attributes (and or attribute ranges)
  218.    */
  219.   public String getStartSet () {
  220.     return m_startRange.getRanges();
  221.   }
  222.   /**
  223.    * Returns an enumeration describing the available options.
  224.    * @return an enumeration of all the available options.
  225.    **/
  226.   public Enumeration listOptions () {
  227.     Vector newVector = new Vector(3);
  228.     newVector
  229.       .addElement(new Option("tSpecify a starting set of attributes." 
  230.      + "ntEg. 1,3,5-7."
  231.      ,"P",1
  232.      , "-P <start set>"));
  233.     newVector.addElement(new Option("tProduce a ranked list of attributes."
  234.     ,"R",0,"-R"));
  235.     newVector
  236.       .addElement(new Option("tSpecify a theshold by which attributes" 
  237.      + "ntmay be discarded from the ranking."
  238.      +"ntUse in conjuction with -R","T",1
  239.      , "-T <threshold>"));
  240.     newVector
  241.       .addElement(new Option("tSpecify number of attributes to select" 
  242.      ,"N",1
  243.      , "-N <num to select>"));
  244.     return newVector.elements();
  245.   }
  246.   
  247.   /**
  248.    * Parses a given list of options.
  249.    *
  250.    * Valid options are: <p>
  251.    * 
  252.    * -P <start set> <br>
  253.    * Specify a starting set of attributes. Eg 1,4,7-9. <p>
  254.    *
  255.    * -R <br>
  256.    * Produce a ranked list of attributes. <p>
  257.    * 
  258.    * -T <threshold> <br>
  259.    * Specify a threshold by which the AttributeSelection module can <br>
  260.    * discard attributes. Use in conjunction with -R <p>
  261.    *
  262.    * -N <number to retain> <br>
  263.    * Specify the number of attributes to retain. Overides any threshold. <br>
  264.    * <p>
  265.    *
  266.    * @param options the list of options as an array of strings
  267.    * @exception Exception if an option is not supported
  268.    *
  269.    **/
  270.   public void setOptions (String[] options)
  271.     throws Exception
  272.   {
  273.     String optionString;
  274.     resetOptions();
  275.     optionString = Utils.getOption('P', options);
  276.     if (optionString.length() != 0) {
  277.       setStartSet(optionString);
  278.     }
  279.     setGenerateRanking(Utils.getFlag('R', options));
  280.     optionString = Utils.getOption('T', options);
  281.     if (optionString.length() != 0) {
  282.       Double temp;
  283.       temp = Double.valueOf(optionString);
  284.       setThreshold(temp.doubleValue());
  285.     }
  286.     optionString = Utils.getOption('N', options);
  287.     if (optionString.length() != 0) {
  288.       setNumToSelect(Integer.parseInt(optionString));
  289.     }
  290.   }
  291.   /**
  292.    * Gets the current settings of ReliefFAttributeEval.
  293.    *
  294.    * @return an array of strings suitable for passing to setOptions()
  295.    */
  296.   public String[] getOptions () {
  297.     String[] options = new String[7];
  298.     int current = 0;
  299.     
  300.     if (!(getStartSet().equals(""))) {
  301.       options[current++] = "-P";
  302.       options[current++] = ""+startSetToString();
  303.     }
  304.     if (getGenerateRanking()) {
  305.       options[current++] = "-R";
  306.     }
  307.     options[current++] = "-T";
  308.     options[current++] = "" + getThreshold();
  309.     options[current++] = "-N";
  310.     options[current++] = ""+getNumToSelect();
  311.     while (current < options.length) {
  312.       options[current++] = "";
  313.     }
  314.     return  options;
  315.   }
  316.   /**
  317.    * converts the array of starting attributes to a string. This is
  318.    * used by getOptions to return the actual attributes specified
  319.    * as the starting set. This is better than using m_startRanges.getRanges()
  320.    * as the same start set can be specified in different ways from the
  321.    * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
  322.    * is stored in a database is comparable.
  323.    * @return a comma seperated list of individual attribute numbers as a String
  324.    */
  325.   private String startSetToString() {
  326.     StringBuffer FString = new StringBuffer();
  327.     boolean didPrint;
  328.     
  329.     if (m_starting == null) {
  330.       return getStartSet();
  331.     }
  332.     for (int i = 0; i < m_starting.length; i++) {
  333.       didPrint = false;
  334.       
  335.       if ((m_hasClass == false) || 
  336.   (m_hasClass == true && i != m_classIndex)) {
  337. FString.append((m_starting[i] + 1));
  338. didPrint = true;
  339.       }
  340.       
  341.       if (i == (m_starting.length - 1)) {
  342. FString.append("");
  343.       }
  344.       else {
  345. if (didPrint) {
  346.   FString.append(",");
  347.   }
  348.       }
  349.     }
  350.     return FString.toString();
  351.   }
  352.   /**
  353.    * returns a description of the search.
  354.    * @return a description of the search as a String.
  355.    */
  356.   public String toString() {
  357.     StringBuffer FString = new StringBuffer();
  358.     FString.append("tForward Selection.ntStart set: ");
  359.     if (m_starting == null) {
  360.       FString.append("no attributesn");
  361.     }
  362.     else {
  363.       FString.append(startSetToString()+"n");
  364.     }
  365.     if (!m_doneRanking) {
  366.       FString.append("tMerit of best subset found: "
  367.      +Utils.doubleToString(Math.abs(m_bestMerit),8,3)+"n");
  368.     }
  369.     
  370.     if ((m_threshold != -Double.MAX_VALUE) && (m_doneRanking)) {
  371.       FString.append("tThreshold for discarding attributes: "
  372.      + Utils.doubleToString(m_threshold,8,4)+"n");
  373.     }
  374.     return FString.toString();
  375.   }
  376.   /**
  377.    * Searches the attribute subset space by forward selection.
  378.    *
  379.    * @param ASEvaluator the attribute evaluator to guide the search
  380.    * @param data the training instances.
  381.    * @return an array (not necessarily ordered) of selected attribute indexes
  382.    * @exception Exception if the search can't be completed
  383.    */
  384.   public int[] search (ASEvaluation ASEval, Instances data)
  385.     throws Exception {
  386.     int i;
  387.     double best_merit = -Double.MAX_VALUE;
  388.     double temp_best,temp_merit;
  389.     int temp_index=0;
  390.     BitSet temp_group;
  391.     if (data != null) { // this is a fresh run so reset
  392.       resetOptions();
  393.       m_Instances = data;
  394.     }
  395.     m_ASEval = ASEval;
  396.     m_numAttribs = m_Instances.numAttributes();
  397.     if (m_best_group == null) {
  398.       m_best_group = new BitSet(m_numAttribs);
  399.     }
  400.     if (!(m_ASEval instanceof SubsetEvaluator)) {
  401.       throw  new Exception(m_ASEval.getClass().getName() 
  402.    + " is not a " 
  403.    + "Subset evaluator!");
  404.     }
  405.     m_startRange.setUpper(m_numAttribs-1);
  406.     if (!(getStartSet().equals(""))) {
  407.       m_starting = m_startRange.getSelection();
  408.     }
  409.     if (m_ASEval instanceof UnsupervisedSubsetEvaluator) {
  410.       m_hasClass = false;
  411.     }
  412.     else {
  413.       m_hasClass = true;
  414.       m_classIndex = m_Instances.classIndex();
  415.     }
  416.     SubsetEvaluator ASEvaluator = (SubsetEvaluator)m_ASEval;
  417.     if (m_rankedAtts == null) {
  418.       m_rankedAtts = new double[m_numAttribs][2];
  419.       m_rankedSoFar = 0;
  420.     }
  421.     // If a starting subset has been supplied, then initialise the bitset
  422.     if (m_starting != null) {
  423.       for (i = 0; i < m_starting.length; i++) {
  424. if ((m_starting[i]) != m_classIndex) {
  425.   m_best_group.set(m_starting[i]);
  426. }
  427.       }
  428.     }
  429.     // Evaluate the initial subset
  430.     best_merit = ASEvaluator.evaluateSubset(m_best_group);
  431.     // main search loop
  432.     boolean done = false;
  433.     boolean addone = false;
  434.     while (!done) {
  435.       temp_group = (BitSet)m_best_group.clone();
  436.       temp_best = best_merit;
  437.       if (m_doRank) {
  438. temp_best = -Double.MAX_VALUE;
  439.       }
  440.       done = true;
  441.       addone = false;
  442.       for (i=0;i<m_numAttribs;i++) {
  443. if ((i != m_classIndex) && (!temp_group.get(i))) {
  444.   // set the bit
  445.   temp_group.set(i);
  446.   temp_merit = ASEvaluator.evaluateSubset(temp_group);
  447.   if (temp_merit > temp_best) {
  448.     temp_best = temp_merit;
  449.     temp_index = i;
  450.     addone = true;
  451.     done = false;
  452.   }
  453.   // unset the bit
  454.   temp_group.clear(i);
  455.   if (m_doRank) {
  456.     done = false;
  457.   }
  458. }
  459.       }
  460.       if (addone) {
  461. m_best_group.set(temp_index);
  462. best_merit = temp_best;
  463. m_rankedAtts[m_rankedSoFar][0] = temp_index;
  464. m_rankedAtts[m_rankedSoFar][1] = best_merit;
  465. m_rankedSoFar++;
  466.       }
  467.     }
  468.     m_bestMerit = best_merit;
  469.     return attributeList(m_best_group);
  470.   }
  471.   /**
  472.    * Produces a ranked list of attributes. Search must have been performed
  473.    * prior to calling this function. Search is called by this function to
  474.    * complete the traversal of the the search space. A list of
  475.    * attributes and merits are returned. The attributes a ranked by the
  476.    * order they are added to the subset during a forward selection search.
  477.    * Individual merit values reflect the merit associated with adding the
  478.    * corresponding attribute to the subset; because of this, merit values
  479.    * may initially increase but then decrease as the best subset is
  480.    * "passed by" on the way to the far side of the search space.
  481.    *
  482.    * @return an array of attribute indexes and associated merit values
  483.    * @exception Exception if something goes wrong.
  484.    */
  485.   public double [][] rankedAttributes() throws Exception {
  486.     
  487.     if (m_rankedAtts == null || m_rankedSoFar == -1) {
  488.       throw new Exception("Search must be performed before attributes "
  489.   +"can be ranked.");
  490.     }
  491.     m_doRank = true;
  492.     search (m_ASEval, null);
  493.     double [][] final_rank = new double [m_rankedSoFar][2];
  494.     for (int i=0;i<m_rankedSoFar;i++) {
  495.       final_rank[i][0] = m_rankedAtts[i][0];
  496.       final_rank[i][1] = m_rankedAtts[i][1];
  497.     }
  498.     
  499.     resetOptions();
  500.     m_doneRanking = true;
  501.     if (m_numToSelect > final_rank.length) {
  502.       throw new Exception("More attributes requested than exist in the data");
  503.     }
  504.     if (m_numToSelect <= 0) {
  505.       if (m_threshold == -Double.MAX_VALUE) {
  506. m_calculatedNumToSelect = final_rank.length;
  507.       } else {
  508. determineNumToSelectFromThreshold(final_rank);
  509.       }
  510.     }
  511.     return final_rank;
  512.   }
  513.   private void determineNumToSelectFromThreshold(double [][] ranking) {
  514.     int count = 0;
  515.     for (int i = 0; i < ranking.length; i++) {
  516.       if (ranking[i][1] > m_threshold) {
  517. count++;
  518.       }
  519.     }
  520.     m_calculatedNumToSelect = count;
  521.   }
  522.   /**
  523.    * converts a BitSet into a list of attribute indexes 
  524.    * @param group the BitSet to convert
  525.    * @return an array of attribute indexes
  526.    **/
  527.   private int[] attributeList (BitSet group) {
  528.     int count = 0;
  529.     // count how many were selected
  530.     for (int i = 0; i < m_numAttribs; i++) {
  531.       if (group.get(i)) {
  532. count++;
  533.       }
  534.     }
  535.     int[] list = new int[count];
  536.     count = 0;
  537.     for (int i = 0; i < m_numAttribs; i++) {
  538.       if (group.get(i)) {
  539. list[count++] = i;
  540.       }
  541.     }
  542.     return  list;
  543.   }
  544.   /**
  545.    * Resets options
  546.    */
  547.   private void resetOptions() {
  548.     m_doRank = false;
  549.     m_best_group = null;
  550.     m_ASEval = null;
  551.     m_Instances = null;
  552.     m_rankedSoFar = -1;
  553.     m_rankedAtts = null;
  554.   }
  555. }