GeneticSearch.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 31k
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.  *    GeneticSearch.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 genetic based search. <p>
  27.  *
  28.  * For more information see: <p>
  29.  * David E. Goldberg (1989). Genetic algorithms in search, optimization and
  30.  * machine learning. Addison-Wesley. <p>
  31.  *
  32.  * Valid options are: <p>
  33.  *
  34.  * -Z <size of the population> <br>
  35.  * Sets the size of the population. (default = 20). <p>
  36.  *
  37.  * -G <number of generations> <br>
  38.  * Sets the number of generations to perform.
  39.  * (default = 5). <p>
  40.  *
  41.  * -C <probability of crossover> <br>
  42.  * Sets the probability that crossover will occur.
  43.  * (default = .6). <p>
  44.  *
  45.  * -M <probability of mutation> <br>
  46.  * Sets the probability that a feature will be toggled on/off. <p>
  47.  *
  48.  * -R <report frequency> <br>
  49.  * Sets how frequently reports will be generated. Eg, setting the value
  50.  * to 5 will generate a report every 5th generation. <p>
  51.  * (default = number of generations). <p>
  52.  *
  53.  * -S <seed> <br>
  54.  * Sets the seed for random number generation. <p>
  55.  *
  56.  * @author Mark Hall (mhall@cs.waikato.ac.nz)
  57.  * @version $Revision: 1.9 $
  58.  */
  59. public class GeneticSearch extends ASSearch implements 
  60.   StartSetHandler, OptionHandler {
  61.   /** 
  62.    * holds a starting set as an array of attributes. Becomes one member of the
  63.    * initial random population
  64.    */
  65.   private int[] m_starting;
  66.   /** holds the start set for the search as a Range */
  67.   private Range m_startRange;
  68.   
  69.  /** does the data have a class */
  70.   private boolean m_hasClass;
  71.  
  72.   /** holds the class index */
  73.   private int m_classIndex;
  74.  
  75.   /** number of attributes in the data */
  76.   private int m_numAttribs;
  77.   /** the current population */
  78.   private GABitSet [] m_population;
  79.   /** the number of individual solutions */
  80.   private int m_popSize;
  81.   /** the best population member found during the search */
  82.   private GABitSet m_best;
  83.   /** the number of features in the best population member */
  84.   private int m_bestFeatureCount;
  85.   /** the number of entries to cache for lookup */
  86.   private int m_lookupTableSize;
  87.   /** the lookup table */
  88.   private Hashtable m_lookupTable;
  89.   /** random number generation */
  90.   private Random m_random;
  91.   /** seed for random number generation */
  92.   private int m_seed;
  93.   /** the probability of crossover occuring */
  94.   private double m_pCrossover;
  95.   /** the probability of mutation occuring */
  96.   private double m_pMutation;
  97.   /** sum of the current population fitness */
  98.   private double m_sumFitness;
  99.   private double m_maxFitness;
  100.   private double m_minFitness;
  101.   private double m_avgFitness;
  102.   /** the maximum number of generations to evaluate */
  103.   private int m_maxGenerations;
  104.   /** how often reports are generated */
  105.   private int m_reportFrequency;
  106.   /** holds the generation reports */
  107.   private StringBuffer m_generationReports;
  108.   // Inner class
  109.   protected class GABitSet implements Cloneable {
  110.     
  111.     private BitSet m_chromosome;
  112.     /** holds raw merit */
  113.     private double m_objective = -Double.MAX_VALUE;
  114.     private double m_fitness;
  115.     
  116.     /**
  117.      * Constructor
  118.      */
  119.     public GABitSet () {
  120.       m_chromosome = new BitSet();
  121.     }
  122.     /**
  123.      * makes a copy of this GABitSet
  124.      * @return a copy of the object
  125.      * @exception Exception if something goes wrong
  126.      */
  127.     public Object clone() throws CloneNotSupportedException {
  128.       GABitSet temp = new GABitSet();
  129.       
  130.       temp.setObjective(this.getObjective());
  131.       temp.setFitness(this.getFitness());
  132.       temp.setChromosome((BitSet)(this.m_chromosome.clone()));
  133.       return temp;
  134.       //return super.clone();
  135.     }
  136.     /**
  137.      * sets the objective merit value
  138.      * @param objective the objective value of this population member
  139.      */
  140.     public void setObjective(double objective) {
  141.       m_objective = objective;
  142.     }
  143.       
  144.     /**
  145.      * gets the objective merit
  146.      * @return the objective merit of this population member
  147.      */
  148.     public double getObjective() {
  149.       return m_objective;
  150.     }
  151.     /**
  152.      * sets the scaled fitness
  153.      * @param the scaled fitness of this population member
  154.      */
  155.     public void setFitness(double fitness) {
  156.       m_fitness = fitness;
  157.     }
  158.     /**
  159.      * gets the scaled fitness
  160.      * @return the scaled fitness of this population member
  161.      */
  162.     public double getFitness() {
  163.       return m_fitness;
  164.     }
  165.     /**
  166.      * get the chromosome
  167.      * @returns the chromosome of this population member
  168.      */
  169.     public BitSet getChromosome() {
  170.       return m_chromosome;
  171.     }
  172.     /**
  173.      * set the chromosome
  174.      * @param the chromosome to be set for this population member
  175.      */
  176.     public void setChromosome(BitSet c) {
  177.       m_chromosome = c;
  178.     }
  179.     /**
  180.      * unset a bit in the chromosome
  181.      * @param bit the bit to be cleared
  182.      */
  183.     public void clear(int bit) {
  184.       m_chromosome.clear(bit);
  185.     }
  186.     /**
  187.      * set a bit in the chromosome
  188.      * @param bit the bit to be set
  189.      */
  190.     public void set(int bit) {
  191.       m_chromosome.set(bit);
  192.     }
  193.     /**
  194.      * get the value of a bit in the chromosome
  195.      * @param bit the bit to query
  196.      * @return the value of the bit
  197.      */
  198.     public boolean get(int bit) {
  199.       return m_chromosome.get(bit);
  200.     }
  201.   }
  202.   /**
  203.    * Returns an enumeration describing the available options
  204.    * @return an enumeration of all the available options
  205.    **/
  206.   public Enumeration listOptions () {
  207.     Vector newVector = new Vector(6);
  208.     newVector.addElement(new Option("tSpecify a starting set of attributes." 
  209.     + "ntEg. 1,3,5-7."
  210.     +"If supplied, the starting set becomes"
  211.     +"ntone member of the initial random"
  212.     +"ntpopulation."
  213.     ,"P",1
  214.     , "-P <start set>"));
  215.     newVector.addElement(new Option("tSet the size of the population."
  216.     +"nt(default = 10)."
  217.     , "Z", 1
  218.     , "-Z <population size>"));
  219.     newVector.addElement(new Option("tSet the number of generations."
  220.     +"nt(default = 20)" 
  221.     , "G", 1, "-G <number of generations>"));
  222.     newVector.addElement(new Option("tSet the probability of crossover."
  223.     +"nt(default = 0.6)" 
  224.     , "C", 1, "-C <probability of"
  225.     +" crossover>"));    
  226.     newVector.addElement(new Option("tSet the probability of mutation."
  227.     +"nt(default = 0.033)" 
  228.     , "M", 1, "-M <probability of mutation>"));
  229.     newVector.addElement(new Option("tSet frequency of generation reports."
  230.     +"nte.g, setting the value to 5 will "
  231.     +"nt report every 5th generation"
  232.     +"nt(default = number of generations)" 
  233.     , "R", 1, "-R <report frequency>"));
  234.     newVector.addElement(new Option("tSet the random number seed."
  235.     +"nt(default = 1)" 
  236.     , "S", 1, "-S <seed>"));
  237.     return  newVector.elements();
  238.   }
  239.   /**
  240.    * Parses a given list of options.
  241.    *
  242.    * Valid options are: <p>
  243.    *
  244.    * -Z <size of the population> <br>
  245.    * Sets the size of the population. (default = 20). <p>
  246.    *
  247.    * -G <number of generations> <br>
  248.    * Sets the number of generations to perform.
  249.    * (default = 5). <p>
  250.    *
  251.    * -C <probability of crossover> <br>
  252.    * Sets the probability that crossover will occur.
  253.    * (default = .6). <p>
  254.    *
  255.    * -M <probability of mutation> <br>
  256.    * Sets the probability that a feature will be toggled on/off. <p>
  257.    *
  258.    * -R <report frequency> <br>
  259.    * Sets how frequently reports will be generated. Eg, setting the value
  260.    * to 5 will generate a report every 5th generation. <p>
  261.    * (default = number of generations). <p>
  262.    *
  263.    * -S <seed> <br>
  264.    * Sets the seed for random number generation. <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.     optionString = Utils.getOption('Z', options);
  280.     if (optionString.length() != 0) {
  281.       setPopulationSize(Integer.parseInt(optionString));
  282.     }
  283.     optionString = Utils.getOption('G', options);
  284.     if (optionString.length() != 0) {
  285.       setMaxGenerations(Integer.parseInt(optionString));
  286.       setReportFrequency(Integer.parseInt(optionString));
  287.     }
  288.     optionString = Utils.getOption('C', options);
  289.     if (optionString.length() != 0) {
  290.       setCrossoverProb((new Double(optionString)).doubleValue());
  291.     }
  292.     optionString = Utils.getOption('M', options);
  293.     if (optionString.length() != 0) {
  294.       setMutationProb((new Double(optionString)).doubleValue());
  295.     }
  296.     optionString = Utils.getOption('R', options);
  297.     if (optionString.length() != 0) {
  298.       setReportFrequency(Integer.parseInt(optionString));
  299.     }
  300.     optionString = Utils.getOption('S', options);
  301.     if (optionString.length() != 0) {
  302.       setSeed(Integer.parseInt(optionString));
  303.     }
  304.   }
  305.   /**
  306.    * Gets the current settings of ReliefFAttributeEval.
  307.    *
  308.    * @return an array of strings suitable for passing to setOptions()
  309.    */
  310.   public String[] getOptions () {
  311.     String[] options = new String[14];
  312.     int current = 0;
  313.     if (!(getStartSet().equals(""))) {
  314.       options[current++] = "-P";
  315.       options[current++] = ""+startSetToString();
  316.     }
  317.     options[current++] = "-Z";
  318.     options[current++] = "" + getPopulationSize();
  319.     options[current++] = "-G";
  320.     options[current++] = "" + getMaxGenerations();
  321.     options[current++] = "-C";
  322.     options[current++] = "" + getCrossoverProb();
  323.     options[current++] = "-M";
  324.     options[current++] = "" + getMutationProb();
  325.     options[current++] = "-R";
  326.     options[current++] = "" + getReportFrequency();
  327.     options[current++] = "-S";
  328.     options[current++] = "" + getSeed();
  329.     while (current < options.length) {
  330.       options[current++] = "";
  331.     }
  332.     return  options;
  333.   }
  334.   /**
  335.    * Returns the tip text for this property
  336.    * @return tip text for this property suitable for
  337.    * displaying in the explorer/experimenter gui
  338.    */
  339.   public String startSetTipText() {
  340.     return "Set a start point for the search. This is specified as a comma "
  341.       +"seperated list off attribute indexes starting at 1. It can include "
  342.       +"ranges. Eg. 1,2,5-9,17. The start set becomes one of the population "
  343.       +"members of the initial population.";
  344.   }
  345.   /**
  346.    * Sets a starting set of attributes for the search. It is the
  347.    * search method's responsibility to report this start set (if any)
  348.    * in its toString() method.
  349.    * @param startSet a string containing a list of attributes (and or ranges),
  350.    * eg. 1,2,6,10-15.
  351.    * @exception Exception if start set can't be set.
  352.    */
  353.   public void setStartSet (String startSet) throws Exception {
  354.     m_startRange.setRanges(startSet);
  355.   }
  356.   /**
  357.    * Returns a list of attributes (and or attribute ranges) as a String
  358.    * @return a list of attributes (and or attribute ranges)
  359.    */
  360.   public String getStartSet () {
  361.     return m_startRange.getRanges();
  362.   }
  363.   /**
  364.    * Returns the tip text for this property
  365.    * @return tip text for this property suitable for
  366.    * displaying in the explorer/experimenter gui
  367.    */
  368.   public String seedTipText() {
  369.     return "Set the random seed.";
  370.   }
  371.   /**
  372.    * set the seed for random number generation
  373.    * @param s seed value
  374.    */
  375.   public void setSeed(int s) {
  376.     m_seed = s;
  377.   }
  378.   /**
  379.    * get the value of the random number generator's seed
  380.    * @return the seed for random number generation
  381.    */
  382.   public int getSeed() {
  383.     return m_seed;
  384.   }
  385.   
  386.   /**
  387.    * Returns the tip text for this property
  388.    * @return tip text for this property suitable for
  389.    * displaying in the explorer/experimenter gui
  390.    */
  391.   public String reportFrequencyTipText() {
  392.     return "Set how frequently reports are generated. Default is equal to "
  393.       +"the number of generations meaning that a report will be printed for "
  394.       +"initial and final generations. Setting the value to 5 will result in "
  395.       +"a report being printed every 5 generations.";
  396.   }
  397.   /**
  398.    * set how often reports are generated
  399.    * @param f generate reports every f generations
  400.    */
  401.   public void setReportFrequency(int f) {
  402.     m_reportFrequency = f;
  403.   }
  404.   /**
  405.    * get how often repports are generated
  406.    * @return how often reports are generated
  407.    */
  408.   public int getReportFrequency() {
  409.     return m_reportFrequency;
  410.   }
  411.   /**
  412.    * Returns the tip text for this property
  413.    * @return tip text for this property suitable for
  414.    * displaying in the explorer/experimenter gui
  415.    */
  416.   public String mutationProbTipText() {
  417.     return "Set the probability of mutation occuring.";
  418.   }
  419.   /**
  420.    * set the probability of mutation
  421.    * @param m the probability for mutation occuring
  422.    */
  423.   public void setMutationProb(double m) {
  424.     m_pMutation = m;
  425.   }
  426.   /**
  427.    * get the probability of mutation
  428.    * @return the probability of mutation occuring
  429.    */
  430.   public double getMutationProb() {
  431.     return m_pMutation;
  432.   }
  433.   /**
  434.    * Returns the tip text for this property
  435.    * @return tip text for this property suitable for
  436.    * displaying in the explorer/experimenter gui
  437.    */
  438.   public String crossoverProbTipText() {
  439.     return "Set the probability of crossover. This is the probability that "
  440.       +"two population members will exchange genetic material."; 
  441.   }
  442.   /**
  443.    * set the probability of crossover
  444.    * @param c the probability that two population members will exchange
  445.    * genetic material
  446.    */
  447.   public void setCrossoverProb(double c) {
  448.     m_pCrossover = c;
  449.   }
  450.   /**
  451.    * get the probability of crossover
  452.    * @return the probability of crossover
  453.    */
  454.   public double getCrossoverProb() {
  455.     return m_pCrossover;
  456.   }
  457.   /**
  458.    * Returns the tip text for this property
  459.    * @return tip text for this property suitable for
  460.    * displaying in the explorer/experimenter gui
  461.    */
  462.   public String maxGenerationsTipText() {
  463.     return "Set the number of generations to evaluate.";
  464.   }
  465.   /**
  466.    * set the number of generations to evaluate
  467.    * @param m the number of generations
  468.    */
  469.   public void setMaxGenerations(int m) {
  470.     m_maxGenerations = m;
  471.   }
  472.   /**
  473.    * get the number of generations
  474.    * @return the maximum number of generations
  475.    */
  476.   public int getMaxGenerations() {
  477.     return m_maxGenerations;
  478.   }
  479.   /**
  480.    * Returns the tip text for this property
  481.    * @return tip text for this property suitable for
  482.    * displaying in the explorer/experimenter gui
  483.    */
  484.   public String populationSizeTipText() {
  485.     return "Set the population size. This is the number of individuals "
  486.       +"(attribute sets) in the population.";
  487.   }
  488.   /**
  489.    * set the population size
  490.    * @param p the size of the population
  491.    */
  492.   public void setPopulationSize(int p) {
  493.     m_popSize = p;
  494.   }
  495.   /**
  496.    * get the size of the population
  497.    * @return the population size
  498.    */
  499.   public int getPopulationSize() {
  500.     return m_popSize;
  501.   }
  502.   /**
  503.    * Returns a string describing this search method
  504.    * @return a description of the search suitable for
  505.    * displaying in the explorer/experimenter gui
  506.    */
  507.   public String globalInfo() {
  508.     return "GeneticSearch :nnPerforms a search using the simple genetic "
  509.       +"algorithm described in Goldberg (1989).n";
  510.   }
  511.   /**
  512.    * Constructor. Make a new GeneticSearch object
  513.    */
  514.   public GeneticSearch() {
  515.     resetOptions();
  516.   }
  517.   /**
  518.    * converts the array of starting attributes to a string. This is
  519.    * used by getOptions to return the actual attributes specified
  520.    * as the starting set. This is better than using m_startRanges.getRanges()
  521.    * as the same start set can be specified in different ways from the
  522.    * command line---eg 1,2,3 == 1-3. This is to ensure that stuff that
  523.    * is stored in a database is comparable.
  524.    * @return a comma seperated list of individual attribute numbers as a String
  525.    */
  526.   private String startSetToString() {
  527.     StringBuffer FString = new StringBuffer();
  528.     boolean didPrint;
  529.     
  530.     if (m_starting == null) {
  531.       return getStartSet();
  532.     }
  533.     for (int i = 0; i < m_starting.length; i++) {
  534.       didPrint = false;
  535.       
  536.       if ((m_hasClass == false) || 
  537.   (m_hasClass == true && i != m_classIndex)) {
  538. FString.append((m_starting[i] + 1));
  539. didPrint = true;
  540.       }
  541.       
  542.       if (i == (m_starting.length - 1)) {
  543. FString.append("");
  544.       }
  545.       else {
  546. if (didPrint) {
  547.   FString.append(",");
  548.   }
  549.       }
  550.     }
  551.     return FString.toString();
  552.   }
  553.   /**
  554.    * returns a description of the search
  555.    * @return a description of the search as a String
  556.    */
  557.   public String toString() {
  558.     StringBuffer GAString = new StringBuffer();
  559.     GAString.append("tGenetic search.ntStart set: ");
  560.     if (m_starting == null) {
  561.       GAString.append("no attributesn");
  562.     }
  563.     else {
  564.       GAString.append(startSetToString()+"n");
  565.     }
  566.     GAString.append("tPopulation size: "+m_popSize);
  567.     GAString.append("ntNumber of generations: "+m_maxGenerations);
  568.     GAString.append("ntProbability of crossover: "
  569. +Utils.doubleToString(m_pCrossover,6,3));
  570.     GAString.append("ntProbability of mutation: "
  571. +Utils.doubleToString(m_pMutation,6,3));
  572.     GAString.append("ntReport frequency: "+m_reportFrequency);
  573.     GAString.append("ntRandom number seed: "+m_seed+"n");
  574.     GAString.append(m_generationReports.toString());
  575.     return GAString.toString();
  576.   }
  577.   /**
  578.    * Searches the attribute subset space using a genetic algorithm.
  579.    *
  580.    * @param ASEvaluator the attribute evaluator to guide the search
  581.    * @param data the training instances.
  582.    * @return an array (not necessarily ordered) of selected attribute indexes
  583.    * @exception Exception if the search can't be completed
  584.    */
  585.    public int[] search (ASEvaluation ASEval, Instances data)
  586.     throws Exception {
  587.      m_best = null;
  588.      m_generationReports = new StringBuffer();
  589.      if (!(ASEval instanceof SubsetEvaluator)) {
  590.        throw  new Exception(ASEval.getClass().getName() 
  591.     + " is not a " 
  592.     + "Subset evaluator!");
  593.      }
  594.      
  595.     if (ASEval instanceof UnsupervisedSubsetEvaluator) {
  596.       m_hasClass = false;
  597.     }
  598.     else {
  599.       m_hasClass = true;
  600.       m_classIndex = data.classIndex();
  601.     }
  602.     SubsetEvaluator ASEvaluator = (SubsetEvaluator)ASEval;
  603.     m_numAttribs = data.numAttributes();
  604.     m_startRange.setUpper(m_numAttribs-1);
  605.     if (!(getStartSet().equals(""))) {
  606.       m_starting = m_startRange.getSelection();
  607.     }
  608.     // initial random population
  609.     m_lookupTable = new Hashtable(m_lookupTableSize);
  610.     m_random = new Random(m_seed);
  611.     m_population = new GABitSet [m_popSize];
  612.     // set up random initial population
  613.     initPopulation();
  614.     evaluatePopulation(ASEvaluator);
  615.     populationStatistics();
  616.     scalePopulation();
  617.     checkBest();
  618.     m_generationReports.append(populationReport(0));
  619.     boolean converged;
  620.     for (int i=1;i<=m_maxGenerations;i++) {
  621.       generation();
  622.       evaluatePopulation(ASEvaluator);
  623.       populationStatistics();
  624.       scalePopulation();
  625.       // find the best pop member and check for convergence
  626.       converged = checkBest();
  627.       if ((i == m_maxGenerations) || 
  628.   ((i % m_reportFrequency) == 0) ||
  629.   (converged == true)) {
  630. m_generationReports.append(populationReport(i));
  631. if (converged == true) {
  632.   break;
  633. }
  634.       }
  635.     }
  636.     return attributeList(m_best.getChromosome());
  637.    }
  638.   /**
  639.    * converts a BitSet into a list of attribute indexes 
  640.    * @param group the BitSet to convert
  641.    * @return an array of attribute indexes
  642.    **/
  643.   private int[] attributeList (BitSet group) {
  644.     int count = 0;
  645.     // count how many were selected
  646.     for (int i = 0; i < m_numAttribs; i++) {
  647.       if (group.get(i)) {
  648. count++;
  649.       }
  650.     }
  651.     int[] list = new int[count];
  652.     count = 0;
  653.     for (int i = 0; i < m_numAttribs; i++) {
  654.       if (group.get(i)) {
  655. list[count++] = i;
  656.       }
  657.     }
  658.     return  list;
  659.   }
  660.   /**
  661.    * checks to see if any population members in the current
  662.    * population are better than the best found so far. Also checks
  663.    * to see if the search has converged---that is there is no difference
  664.    * in fitness between the best and worse population member
  665.    * @return true is the search has converged
  666.    * @exception Exception if something goes wrong
  667.    */
  668.   private boolean checkBest() throws Exception {
  669.     int i,j,count,lowestCount = m_numAttribs;
  670.     double b = -Double.MAX_VALUE;
  671.     GABitSet localbest = null;
  672.     BitSet temp;
  673.     boolean converged = false;
  674.     int oldcount = Integer.MAX_VALUE;
  675.     if (m_maxFitness - m_minFitness > 0) {
  676.       // find the best in this population
  677.       for (i=0;i<m_popSize;i++) {
  678. if (m_population[i].getObjective() > b) {
  679.   b = m_population[i].getObjective();
  680.   localbest = m_population[i];
  681.   oldcount = countFeatures(localbest.getChromosome());
  682. } else if (Utils.eq(m_population[i].getObjective(), b)) {
  683.   // see if it contains fewer features
  684.   count = countFeatures(m_population[i].getChromosome());
  685.   if (count < oldcount) {
  686.     b = m_population[i].getObjective();
  687.     localbest = m_population[i];
  688.     oldcount = count;
  689.   }
  690. }
  691.       }
  692.     } else {
  693.       // look for the smallest subset
  694.       for (i=0;i<m_popSize;i++) {
  695. temp = m_population[i].getChromosome();
  696. count = countFeatures(temp);;
  697. if (count < lowestCount) {
  698.   lowestCount = count;
  699.   localbest = m_population[i];
  700.   b = localbest.getObjective();
  701. }
  702.       }
  703.       converged = true;
  704.     }
  705.     // count the number of features in localbest
  706.     count = 0;
  707.     temp = localbest.getChromosome();
  708.     count = countFeatures(temp);
  709.     // compare to the best found so far
  710.     if (m_best == null) {
  711.       m_best = (GABitSet)localbest.clone();
  712.       m_bestFeatureCount = count;
  713.     } else if (b > m_best.getObjective()) {
  714.       m_best = (GABitSet)localbest.clone();
  715.       m_bestFeatureCount = count;
  716.     } else if (Utils.eq(m_best.getObjective(), b)) {
  717.       // see if the localbest has fewer features than the best so far
  718.       if (count < m_bestFeatureCount) {
  719. m_best = (GABitSet)localbest.clone();
  720. m_bestFeatureCount = count;
  721.       }
  722.     }
  723.     return converged;
  724.   }
  725.   /**
  726.    * counts the number of features in a subset
  727.    * @param featureSet the feature set for which to count the features
  728.    * @return the number of features in the subset
  729.    */
  730.   private int countFeatures(BitSet featureSet) {
  731.     int count = 0;
  732.     for (int i=0;i<m_numAttribs;i++) {
  733.       if (featureSet.get(i)) {
  734. count++;
  735.       }
  736.     }
  737.     return count;
  738.   }
  739.   /**
  740.    * performs a single generation---selection, crossover, and mutation
  741.    * @exception Exception if an error occurs
  742.    */
  743.   private void generation () throws Exception {
  744.     int i,j=0;
  745.     double best_fit = -Double.MAX_VALUE;
  746.     int old_count = 0;
  747.     int count;
  748.     GABitSet [] newPop = new GABitSet [m_popSize];
  749.     int parent1,parent2;
  750.     /** first ensure that the population best is propogated into the new
  751. generation */
  752.     for (i=0;i<m_popSize;i++) {
  753.       if (m_population[i].getFitness() > best_fit) {
  754. j = i;
  755. best_fit = m_population[i].getFitness();
  756. old_count = countFeatures(m_population[i].getChromosome());
  757.       } else if (Utils.eq(m_population[i].getFitness(), best_fit)) {
  758. count = countFeatures(m_population[i].getChromosome());
  759. if (count < old_count) {
  760.   j = i;
  761.   best_fit = m_population[i].getFitness();
  762.   old_count = count;
  763. }
  764.       }
  765.     }
  766.     newPop[0] = (GABitSet)(m_population[j].clone());
  767.     newPop[1] = newPop[0];
  768.     for (j=2;j<m_popSize;j+=2) {
  769.       parent1 = select();
  770.       parent2 = select();
  771.       newPop[j] = (GABitSet)(m_population[parent1].clone());
  772.       newPop[j+1] = (GABitSet)(m_population[parent2].clone());
  773.       // if parents are equal mutate one bit
  774.       if (parent1 == parent2) {
  775. int r;
  776. if (m_hasClass) {
  777.   while ((r = (Math.abs(m_random.nextInt()) % m_numAttribs)) == m_classIndex);
  778. }
  779. else {
  780.   r = m_random.nextInt() % m_numAttribs;
  781. }
  782. if (newPop[j].get(r)) {
  783.   newPop[j].clear(r);
  784. }
  785. else {
  786.   newPop[j].set(r);
  787. }
  788.       }
  789.       else {
  790. // crossover
  791. double r = m_random.nextDouble();
  792. if (r < m_pCrossover) {
  793.   // cross point
  794.   int cp = Math.abs(m_random.nextInt());
  795.   
  796.   cp %= (m_numAttribs-2);
  797.   cp ++;
  798.   
  799.   for (i=0;i<cp;i++) {
  800.     if (m_population[parent1].get(i)) {
  801.       newPop[j+1].set(i);
  802.     }
  803.     else {
  804.       newPop[j+1].clear(i);
  805.     }
  806.     if (m_population[parent2].get(i)) {
  807.       newPop[j].set(i);
  808.     }
  809.     else {
  810.       newPop[j].clear(i);
  811.     }
  812.   }
  813. }
  814. // mutate
  815. for (int k=0;k<2;k++) {
  816.   for (i=0;i<m_numAttribs;i++) {
  817.     r = m_random.nextDouble();
  818.     if (r < m_pMutation) {
  819.       if (m_hasClass && (i == m_classIndex)) {
  820. // ignore class attribute
  821.       }
  822.       else {
  823. if (newPop[j+k].get(i)) {
  824.   newPop[j+k].clear(i);
  825. }
  826. else {
  827.   newPop[j+k].set(i);
  828. }
  829.       }
  830.     }
  831.   }
  832. }
  833.   
  834.       }
  835.     }
  836.     m_population = newPop;
  837.   }
  838.   /**
  839.    * selects a population member to be considered for crossover
  840.    * @return the index of the selected population member
  841.    */
  842.   private int select() {
  843.     int i;
  844.     double r,partsum;
  845.     partsum = 0;
  846.     r = m_random.nextDouble() * m_sumFitness;
  847.     for (i=0;i<m_popSize;i++) {
  848.       partsum += m_population[i].getFitness();
  849.       if (partsum >= r) {
  850. break;
  851.       }
  852.     }
  853.     return i;
  854.   }
  855.   /**
  856.    * evaluates an entire population. Population members are looked up in
  857.    * a hash table and if they are not found then they are evaluated using
  858.    * ASEvaluator.
  859.    * @param ASEvaluator the subset evaluator to use for evaluating population
  860.    * members
  861.    * @exception Exception if something goes wrong during evaluation
  862.    */
  863.   private void evaluatePopulation (SubsetEvaluator ASEvaluator)
  864.     throws Exception {
  865.     int i;
  866.     double merit;
  867.     for (i=0;i<m_popSize;i++) {
  868.       // if its not in the lookup table then evaluate and insert
  869.       if (m_lookupTable.containsKey(m_population[i]
  870.     .getChromosome()) == false) {
  871. merit = ASEvaluator.evaluateSubset(m_population[i].getChromosome());
  872. m_population[i].setObjective(merit);
  873. m_lookupTable.put(m_population[i].getChromosome(),m_population[i]);
  874.       } else {
  875. GABitSet temp = (GABitSet)m_lookupTable.
  876.   get(m_population[i].getChromosome());
  877. m_population[i].setObjective(temp.getObjective());
  878.       }
  879.     }
  880.   }
  881.   /**
  882.    * creates random population members for the initial population. Also
  883.    * sets the first population member to be a start set (if any) 
  884.    * provided by the user
  885.    * @exception Exception if the population can't be created
  886.    */
  887.   private void initPopulation () throws Exception {
  888.     int i,j,bit;
  889.     int num_bits;
  890.     boolean ok;
  891.     int start = 0;
  892.     // add the start set as the first population member (if specified)
  893.     if (m_starting != null) {
  894.       m_population[0] = new GABitSet();
  895.       for (i=0;i<m_starting.length;i++) {
  896. if ((m_starting[i]) != m_classIndex) {
  897.   m_population[0].set(m_starting[i]);
  898. }
  899.       }
  900.       start = 1;
  901.     }
  902.     for (i=start;i<m_popSize;i++) {
  903.       m_population[i] = new GABitSet();
  904.       
  905.       num_bits = m_random.nextInt();
  906.       num_bits = num_bits % m_numAttribs-1;
  907.       if (num_bits < 0) {
  908. num_bits *= -1;
  909.       }
  910.       if (num_bits == 0) {
  911. num_bits = 1;
  912.       }
  913.       for (j=0;j<num_bits;j++) {
  914. ok = false;
  915. do {
  916.   bit = m_random.nextInt();
  917.   if (bit < 0) {
  918.     bit *= -1;
  919.   }
  920.   bit = bit % m_numAttribs;
  921.   if (m_hasClass) {
  922.     if (bit != m_classIndex) {
  923.       ok = true;
  924.     }
  925.   }
  926.   else {
  927.     ok = true;
  928.   }
  929. } while (!ok);
  930. if (bit > m_numAttribs) {
  931.   throw new Exception("Problem in population init");
  932. }
  933. m_population[i].set(bit);
  934.       }
  935.     }
  936.   }
  937.   /**
  938.    * calculates summary statistics for the current population
  939.    */
  940.   private void populationStatistics() {
  941.     int i;
  942.     
  943.     m_sumFitness = m_minFitness = m_maxFitness = 
  944.       m_population[0].getObjective();
  945.     for (i=1;i<m_popSize;i++) {
  946.       m_sumFitness += m_population[i].getObjective();
  947.       if (m_population[i].getObjective() > m_maxFitness) {
  948. m_maxFitness = m_population[i].getObjective();
  949.       }
  950.       else if (m_population[i].getObjective() < m_minFitness) {
  951. m_minFitness = m_population[i].getObjective();
  952.       }
  953.     }
  954.     m_avgFitness = (m_sumFitness / m_popSize);
  955.   }
  956.   /**
  957.    * scales the raw (objective) merit of the population members
  958.    */
  959.   private void scalePopulation() {
  960.     int j;
  961.     double a = 0;
  962.     double b = 0;
  963.     double fmultiple = 2.0;
  964.     double delta;
  965.     
  966.     // prescale
  967.     if (m_minFitness > ((fmultiple * m_avgFitness - m_maxFitness) / 
  968. (fmultiple - 1.0))) {
  969.       delta = m_maxFitness - m_avgFitness;
  970.       a = ((fmultiple - 1.0) * m_avgFitness / delta);
  971.       b = m_avgFitness * (m_maxFitness - fmultiple * m_avgFitness) / delta;
  972.     }
  973.     else {
  974.       delta = m_avgFitness - m_minFitness;
  975.       a = m_avgFitness / delta;
  976.       b = -m_minFitness * m_avgFitness / delta;
  977.     }
  978.       
  979.     // scalepop
  980.     m_sumFitness = 0;
  981.     for (j=0;j<m_popSize;j++) {
  982.       m_population[j].
  983. setFitness(Math.abs((a * m_population[j].getObjective() + b)));
  984.       m_sumFitness += m_population[j].getFitness();
  985.     }
  986.   }
  987.   
  988.   /**
  989.    * generates a report on the current population
  990.    * @return a report as a String
  991.    */
  992.   private String populationReport (int genNum) {
  993.     int i;
  994.     StringBuffer temp = new StringBuffer();
  995.     if (genNum == 0) {
  996.       temp.append("nInitial populationn");
  997.     }
  998.     else {
  999.       temp.append("nGeneration: "+genNum+"n");
  1000.     }
  1001.     temp.append("merit   tscaled  tsubsetn");
  1002.     
  1003.     for (i=0;i<m_popSize;i++) {
  1004.       temp.append(Utils.doubleToString(Math.
  1005.        abs(m_population[i].getObjective()),
  1006.        8,5)
  1007.   +"t"
  1008.   +Utils.doubleToString(m_population[i].getFitness(),
  1009. 8,5)
  1010.   +"t");
  1011.       temp.append(printPopMember(m_population[i].getChromosome())+"n");
  1012.     }
  1013.     return temp.toString();
  1014.   }
  1015.   /**
  1016.    * prints a population member as a series of attribute numbers
  1017.    * @param temp the chromosome of a population member
  1018.    * @return a population member as a String of attribute numbers
  1019.    */
  1020.   private String printPopMember(BitSet temp) {
  1021.     StringBuffer text = new StringBuffer();
  1022.     for (int j=0;j<m_numAttribs;j++) {
  1023.       if (temp.get(j)) {
  1024.         text.append((j+1)+" ");
  1025.       }
  1026.     }
  1027.     return text.toString();
  1028.   }
  1029.   /**
  1030.    * prints a population member's chromosome
  1031.    * @param temp the chromosome of a population member
  1032.    * @return a population member's chromosome as a String
  1033.    */
  1034.   private String printPopChrom(BitSet temp) {
  1035.     StringBuffer text = new StringBuffer();
  1036.     for (int j=0;j<m_numAttribs;j++) {
  1037.       if (temp.get(j)) {
  1038. text.append("1");
  1039.       } else {
  1040. text.append("0");
  1041.       }
  1042.     }
  1043.     return text.toString();
  1044.   }
  1045.   /**
  1046.    * reset to default values for options
  1047.    */
  1048.   private void resetOptions () {
  1049.     m_population = null;
  1050.     m_popSize = 20;
  1051.     m_lookupTableSize = 1001;
  1052.     m_pCrossover = 0.6;
  1053.     m_pMutation = 0.033;
  1054.     m_maxGenerations = 20;
  1055.     m_reportFrequency = m_maxGenerations;
  1056.     m_starting = null;
  1057.     m_startRange = new Range();
  1058.     m_seed = 1;
  1059.   }
  1060. }