source: src/main/java/weka/attributeSelection/AttributeSelection.java @ 24

Last change on this file since 24 was 4, checked in by gnappo, 14 years ago

Import di weka.

File size: 33.7 KB
Line 
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/*
18 *    AttributeSelection.java
19 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.attributeSelection;
24
25import weka.core.Instance;
26import weka.core.Instances;
27import weka.core.Option;
28import weka.core.OptionHandler;
29import weka.core.RevisionHandler;
30import weka.core.RevisionUtils;
31import weka.core.Utils;
32import weka.core.converters.ConverterUtils.DataSource;
33import weka.filters.Filter;
34import weka.filters.unsupervised.attribute.Remove;
35
36import java.beans.BeanInfo;
37import java.beans.IntrospectionException;
38import java.beans.Introspector;
39import java.beans.MethodDescriptor;
40import java.beans.PropertyDescriptor;
41import java.io.Serializable;
42import java.lang.reflect.Method;
43import java.util.Enumeration;
44import java.util.Random;
45
46/**
47 * Attribute selection class. Takes the name of a search class and
48 * an evaluation class on the command line. <p/>
49 *
50 * Valid options are: <p/>
51 *
52 * -h <br/>
53 * Display help. <p/>
54 *
55 * -i &lt;name of input file&gt; <br/>
56 * Specify the training data file. <p/>
57 *
58 * -c &lt;class index&gt; <br/>
59 * The index of the attribute to use as the class. <p/>
60 *
61 * -s &lt;search method&gt; <br/>
62 * The full class name of the search method followed by search method options
63 * (if any).<br/>
64 * Eg. -s "weka.attributeSelection.BestFirst -N 10" <p/>
65 *
66 * -x &lt;number of folds&gt; <br/>
67 * Perform a cross validation. <p/>
68 *
69 * -n &lt;random number seed&gt; <br/>
70 * Specify a random number seed. Use in conjuction with -x. (Default = 1). <p/>
71 *
72 * ------------------------------------------------------------------------ <p/>
73 *
74 * Example usage as the main of an attribute evaluator (called FunkyEvaluator):
75 * <pre>
76 * public static void main(String [] args) {
77 *   runEvaluator(new FunkyEvaluator(), args);
78 * }
79 * </pre>
80 * <p/>
81 *
82 * ------------------------------------------------------------------------ <p/>
83 *
84 * @author   Mark Hall (mhall@cs.waikato.ac.nz)
85 * @version  $Revision: 1.47 $
86 */
87public class AttributeSelection 
88  implements Serializable, RevisionHandler {
89 
90  /** for serialization */
91  static final long serialVersionUID = 4170171824147584330L;
92
93  /** the instances to select attributes from */
94  private Instances m_trainInstances;
95
96  /** the attribute/subset evaluator */
97  private ASEvaluation m_ASEvaluator;
98
99  /** the search method */
100  private ASSearch m_searchMethod;
101
102  /** the number of folds to use for cross validation */
103  private int m_numFolds;
104
105  /** holds a string describing the results of the attribute selection */
106  private StringBuffer m_selectionResults;
107
108  /** rank features (if allowed by the search method) */
109  private boolean m_doRank;
110 
111  /** do cross validation */
112  private boolean m_doXval;
113
114  /** seed used to randomly shuffle instances for cross validation */
115  private int m_seed;
116
117  /** number of attributes requested from ranked results */
118  private int m_numToSelect;
119 
120  /** the selected attributes */
121  private int [] m_selectedAttributeSet;
122
123  /** the attribute indexes and associated merits if a ranking is produced */
124  private double [][] m_attributeRanking;
125
126  /** if a feature selection run involves an attribute transformer */
127  private AttributeTransformer m_transformer = null;
128 
129  /** the attribute filter for processing instances with respect to
130      the most recent feature selection run */
131  private Remove m_attributeFilter = null;
132
133  /** hold statistics for repeated feature selection, such as
134      under cross validation */
135  private double [][] m_rankResults = null;
136  private double [] m_subsetResults = null;
137  private int m_trials = 0;
138
139  /**
140   * Return the number of attributes selected from the most recent
141   * run of attribute selection
142   * @return the number of attributes selected
143   */
144  public int numberAttributesSelected() throws Exception {
145    int [] att = selectedAttributes();
146    return att.length-1;
147  }
148
149  /**
150   * get the final selected set of attributes.
151   * @return an array of attribute indexes
152   * @exception Exception if attribute selection has not been performed yet
153   */
154  public int [] selectedAttributes () throws Exception {
155    if (m_selectedAttributeSet == null) {
156      throw new Exception("Attribute selection has not been performed yet!");
157    }
158    return m_selectedAttributeSet;
159  }
160
161  /**
162   * get the final ranking of the attributes.
163   * @return a two dimensional array of ranked attribute indexes and their
164   * associated merit scores as doubles.
165   * @exception Exception if a ranking has not been produced
166   */
167  public double [][] rankedAttributes () throws Exception {
168    if (m_attributeRanking == null) {
169      throw new Exception("Ranking has not been performed");
170    }
171    return m_attributeRanking;
172  }
173
174  /**
175   * set the attribute/subset evaluator
176   * @param evaluator the evaluator to use
177   */
178  public void setEvaluator (ASEvaluation evaluator) {
179    m_ASEvaluator = evaluator;
180  }
181
182  /**
183   * set the search method
184   * @param search the search method to use
185   */
186  public void setSearch (ASSearch search) {
187    m_searchMethod = search;
188
189    if (m_searchMethod instanceof RankedOutputSearch) {
190      setRanking(((RankedOutputSearch)m_searchMethod).getGenerateRanking());
191    }
192  }
193
194  /**
195   * set the number of folds for cross validation
196   * @param folds the number of folds
197   */
198  public void setFolds (int folds) {
199    m_numFolds = folds;
200  }
201
202  /**
203   * produce a ranking (if possible with the set search and evaluator)
204   * @param r true if a ranking is to be produced
205   */
206  public void setRanking (boolean r) {
207    m_doRank = r;
208  }
209
210  /**
211   * do a cross validation
212   * @param x true if a cross validation is to be performed
213   */
214  public void setXval (boolean x) {
215    m_doXval = x;
216  }
217
218  /**
219   * set the seed for use in cross validation
220   * @param s the seed
221   */
222  public void setSeed (int s) {
223    m_seed = s;
224  }
225
226  /**
227   * get a description of the attribute selection
228   * @return a String describing the results of attribute selection
229   */
230  public String toResultsString() {
231    return m_selectionResults.toString();
232  }
233
234  /**
235   * reduce the dimensionality of a set of instances to include only those
236   * attributes chosen by the last run of attribute selection.
237   * @param in the instances to be reduced
238   * @return a dimensionality reduced set of instances
239   * @exception Exception if the instances can't be reduced
240   */
241  public Instances reduceDimensionality(Instances in) throws Exception {
242    if (m_attributeFilter == null) {
243      throw new Exception("No feature selection has been performed yet!");
244    }
245
246    if (m_transformer != null) {
247      Instances transformed = new Instances(m_transformer.transformedHeader(),
248                                            in.numInstances());
249      for (int i=0;i<in.numInstances();i++) {
250        transformed.add(m_transformer.convertInstance(in.instance(i)));
251      }
252      return Filter.useFilter(transformed, m_attributeFilter);
253    }
254
255    return Filter.useFilter(in, m_attributeFilter);
256  }
257
258  /**
259   * reduce the dimensionality of a single instance to include only those
260   * attributes chosen by the last run of attribute selection.
261   * @param in the instance to be reduced
262   * @return a dimensionality reduced instance
263   * @exception Exception if the instance can't be reduced
264   */
265  public Instance reduceDimensionality(Instance in) throws Exception {
266    if (m_attributeFilter == null) {
267      throw new Exception("No feature selection has been performed yet!");
268    }
269    if (m_transformer != null) {
270      in = m_transformer.convertInstance(in);
271    }
272    m_attributeFilter.input(in);
273    m_attributeFilter.batchFinished();
274    Instance result = m_attributeFilter.output();
275    return result;
276  }
277
278  /**
279   * constructor. Sets defaults for each member varaible. Default
280   * attribute evaluator is CfsSubsetEval; default search method is
281   * BestFirst.
282   */
283  public AttributeSelection () {
284    setFolds(10);
285    setRanking(false);
286    setXval(false);
287    setSeed(1);
288    setEvaluator(new CfsSubsetEval());
289    setSearch(new GreedyStepwise());
290    m_selectionResults = new StringBuffer();
291    m_selectedAttributeSet = null;
292    m_attributeRanking = null;
293  }
294
295  /**
296   * Perform attribute selection with a particular evaluator and
297   * a set of options specifying search method and input file etc.
298   *
299   * @param ASEvaluator an evaluator object
300   * @param options an array of options, not only for the evaluator
301   * but also the search method (if any) and an input data file
302   * @return the results of attribute selection as a String
303   * @exception Exception if no training file is set
304   */
305  public static String SelectAttributes (ASEvaluation ASEvaluator, 
306                                         String[] options)
307    throws Exception {
308    String trainFileName, searchName;
309    Instances train = null;
310    ASSearch searchMethod = null;
311    String[] optionsTmp = (String[]) options.clone();
312    boolean helpRequested = false;
313
314    try {
315      // get basic options (options the same for all attribute selectors
316      trainFileName = Utils.getOption('i', options);
317      helpRequested = Utils.getFlag('h', optionsTmp);
318
319      if (helpRequested || (trainFileName.length() == 0)) {
320        searchName = Utils.getOption('s', optionsTmp);
321        if (searchName.length() != 0) {
322          String[] searchOptions = Utils.splitOptions(searchName);
323          searchMethod = (ASSearch)Class.forName(searchOptions[0]).newInstance();
324        }
325
326        if (helpRequested)
327          throw new Exception("Help requested.");
328        else
329          throw new Exception("No training file given.");
330      }
331    }
332    catch (Exception e) {
333      throw  new Exception('\n' + e.getMessage() 
334                           + makeOptionString(ASEvaluator, searchMethod));
335    }
336
337    DataSource source = new DataSource(trainFileName);
338    train = source.getDataSet();
339    return SelectAttributes(ASEvaluator, options, train);
340  }
341
342  /**
343   * returns a string summarizing the results of repeated attribute
344   * selection runs on splits of a dataset.
345   * @return a summary of attribute selection results
346   * @exception Exception if no attribute selection has been performed.
347   */
348  public String CVResultsString () throws Exception {
349    StringBuffer CvString = new StringBuffer();
350   
351    if ((m_subsetResults == null && m_rankResults == null) ||
352        ( m_trainInstances == null)) {
353      throw new Exception("Attribute selection has not been performed yet!");
354    }
355
356    int fieldWidth = (int)(Math.log(m_trainInstances.numAttributes()) +1.0);
357
358    CvString.append("\n\n=== Attribute selection " + m_numFolds
359                    + " fold cross-validation ");
360
361    if (!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) && 
362        !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator) &&
363        (m_trainInstances.classAttribute().isNominal())) {
364        CvString.append("(stratified), seed: ");
365        CvString.append(m_seed+" ===\n\n");
366    }
367    else {
368      CvString.append("seed: "+m_seed+" ===\n\n");
369    }
370
371    if ((m_searchMethod instanceof RankedOutputSearch) && (m_doRank == true)) {
372      CvString.append("average merit      average rank  attribute\n");
373
374      // calcualte means and std devs
375      for (int i = 0; i < m_rankResults[0].length; i++) {
376        m_rankResults[0][i] /= m_numFolds; // mean merit
377        double var = m_rankResults[0][i]*m_rankResults[0][i]*m_numFolds;
378        var = (m_rankResults[2][i] - var);
379        var /= m_numFolds;
380
381        if (var <= 0.0) {
382          var = 0.0;
383          m_rankResults[2][i] = 0;
384        }
385        else {
386          m_rankResults[2][i] = Math.sqrt(var);
387        }
388
389        m_rankResults[1][i] /= m_numFolds; // mean rank
390        var = m_rankResults[1][i]*m_rankResults[1][i]*m_numFolds;
391        var = (m_rankResults[3][i] - var);
392        var /= m_numFolds;
393
394        if (var <= 0.0) {
395          var = 0.0;
396          m_rankResults[3][i] = 0;
397        }
398        else {
399          m_rankResults[3][i] = Math.sqrt(var);
400        }
401      }
402
403      // now sort them by mean rank
404      int[] s = Utils.sort(m_rankResults[1]);
405      for (int i=0; i<s.length; i++) {
406        if (m_rankResults[1][s[i]] > 0) {
407          CvString.append(Utils.doubleToString(Math.
408                                               abs(m_rankResults[0][s[i]]),
409                                               6, 3) 
410                          + " +-" 
411                          + Utils.doubleToString(m_rankResults[2][s[i]], 6, 3) 
412                          + "   " 
413                          + Utils.doubleToString(m_rankResults[1][s[i]],
414                                                 fieldWidth+2, 1) 
415                          + " +-" 
416                          + Utils.doubleToString(m_rankResults[3][s[i]], 5, 2) 
417                        +"  "
418                          + Utils.doubleToString(((double)(s[i] + 1)), 
419                                                 fieldWidth, 0)
420                          + " " 
421                          + m_trainInstances.attribute(s[i]).name() 
422                          + "\n");
423        }
424      }
425    }
426    else {
427      CvString.append("number of folds (%)  attribute\n");
428
429      for (int i = 0; i < m_subsetResults.length; i++) {
430        if ((m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) ||
431            (i != m_trainInstances.classIndex())) {
432          CvString.append(Utils.doubleToString(m_subsetResults[i], 12, 0) 
433                          + "(" 
434                          + Utils.doubleToString((m_subsetResults[i] / 
435                                                  m_numFolds * 100.0)
436                                                 , 3, 0) 
437                          + " %)  " 
438                          + Utils.doubleToString(((double)(i + 1)),
439                                                 fieldWidth, 0)
440                          + " " 
441                          + m_trainInstances.attribute(i).name() 
442                          + "\n");
443        }
444      }
445    }
446
447    return CvString.toString();
448  }
449
450  /**
451   * Select attributes for a split of the data. Calling this function
452   * updates the statistics on attribute selection. CVResultsString()
453   * returns a string summarizing the results of repeated calls to
454   * this function. Assumes that splits are from the same dataset---
455   * ie. have the same number and types of attributes as previous
456   * splits.
457   *
458   * @param split the instances to select attributes from
459   * @exception Exception if an error occurs
460   */
461  public void selectAttributesCVSplit(Instances split) throws Exception {
462    double[][] attributeRanking = null;
463
464    // if the train instances are null then set equal to this split.
465    // If this is the case then this function is more than likely being
466    // called from outside this class in order to obtain CV statistics
467    // and all we need m_trainIstances for is to get at attribute names
468    // and types etc.
469    if (m_trainInstances == null) {
470      m_trainInstances = split;
471    }
472
473    // create space to hold statistics
474    if (m_rankResults == null && m_subsetResults == null) {
475      m_subsetResults = new double[split.numAttributes()];
476      m_rankResults = new double[4][split.numAttributes()];
477    }
478
479    m_ASEvaluator.buildEvaluator(split);
480    // Do the search
481    int[] attributeSet = m_searchMethod.search(m_ASEvaluator, 
482                                               split);
483    // Do any postprocessing that a attribute selection method might
484    // require
485    attributeSet = m_ASEvaluator.postProcess(attributeSet);
486   
487    if ((m_searchMethod instanceof RankedOutputSearch) && 
488        (m_doRank == true)) {
489      attributeRanking = ((RankedOutputSearch)m_searchMethod).
490        rankedAttributes();
491     
492      // System.out.println(attributeRanking[0][1]);
493      for (int j = 0; j < attributeRanking.length; j++) {
494        // merit
495        m_rankResults[0][(int)attributeRanking[j][0]] += 
496          attributeRanking[j][1];
497        // squared merit
498        m_rankResults[2][(int)attributeRanking[j][0]] += 
499          (attributeRanking[j][1]*attributeRanking[j][1]);
500        // rank
501        m_rankResults[1][(int)attributeRanking[j][0]] += (j + 1);
502        // squared rank
503        m_rankResults[3][(int)attributeRanking[j][0]] += (j + 1)*(j + 1);
504        // += (attributeRanking[j][0] * attributeRanking[j][0]);
505      }
506    } else {
507      for (int j = 0; j < attributeSet.length; j++) {
508        m_subsetResults[attributeSet[j]]++;
509      }
510    }
511
512    m_trials++;
513  }
514
515  /**
516   * Perform a cross validation for attribute selection. With subset
517   * evaluators the number of times each attribute is selected over
518   * the cross validation is reported. For attribute evaluators, the
519   * average merit and average ranking + std deviation is reported for
520   * each attribute.
521   *
522   * @return the results of cross validation as a String
523   * @exception Exception if an error occurs during cross validation
524   */
525  public String CrossValidateAttributes () throws Exception {
526    Instances cvData = new Instances(m_trainInstances);
527    Instances train;
528
529    Random random = new Random(m_seed);
530    cvData.randomize(random);
531
532    if (!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) && 
533        !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) {
534      if (cvData.classAttribute().isNominal()) {
535        cvData.stratify(m_numFolds);
536      }
537
538    }
539
540    for (int i = 0; i < m_numFolds; i++) {
541      // Perform attribute selection
542      train = cvData.trainCV(m_numFolds, i, random);
543      selectAttributesCVSplit(train);
544    }
545
546    return  CVResultsString();
547  }
548
549  /**
550   * Perform attribute selection on the supplied training instances.
551   *
552   * @param data the instances to select attributes from
553   * @exception Exception if there is a problem during selection
554   */
555  public void SelectAttributes (Instances data) throws Exception {
556    int [] attributeSet;
557   
558    m_transformer = null;
559    m_attributeFilter = null;
560    m_trainInstances = data;
561   
562    if (m_doXval == true && (m_ASEvaluator instanceof AttributeTransformer)) {
563      throw new Exception("Can't cross validate an attribute transformer.");
564    }
565
566    if (m_ASEvaluator instanceof SubsetEvaluator &&
567        m_searchMethod instanceof Ranker) {
568      throw new Exception(m_ASEvaluator.getClass().getName()
569                          +" must use a search method other than Ranker");
570    }
571
572    if (m_ASEvaluator instanceof AttributeEvaluator &&
573        !(m_searchMethod instanceof Ranker)) {
574      //      System.err.println("AttributeEvaluators must use a Ranker search "
575      //                         +"method. Switching to Ranker...");
576      //      m_searchMethod = new Ranker();
577      throw new Exception("AttributeEvaluators must use the Ranker search "
578                          + "method");
579    }
580
581    if (m_searchMethod instanceof RankedOutputSearch) {
582      m_doRank = ((RankedOutputSearch)m_searchMethod).getGenerateRanking();
583    }
584
585    if (m_ASEvaluator instanceof UnsupervisedAttributeEvaluator ||
586        m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) {
587      // unset the class index
588      //      m_trainInstances.setClassIndex(-1);
589    } else {
590      // check that a class index has been set
591      if (m_trainInstances.classIndex() < 0) {
592        m_trainInstances.setClassIndex(m_trainInstances.numAttributes()-1);
593      }
594    }
595   
596    // Initialize the attribute evaluator
597    m_ASEvaluator.buildEvaluator(m_trainInstances);
598    if (m_ASEvaluator instanceof AttributeTransformer) {
599      m_trainInstances = 
600        ((AttributeTransformer)m_ASEvaluator).transformedHeader();
601      m_transformer = (AttributeTransformer)m_ASEvaluator;
602    }
603    int fieldWidth = (int)(Math.log(m_trainInstances.numAttributes()) +1.0);
604
605    // Do the search
606    attributeSet = m_searchMethod.search(m_ASEvaluator, 
607                                         m_trainInstances);
608
609    // try and determine if the search method uses an attribute transformer---
610    // this is a bit of a hack to make things work properly with RankSearch
611    // using PrincipalComponents as its attribute ranker
612     try {
613       BeanInfo bi = Introspector.getBeanInfo(m_searchMethod.getClass());
614       PropertyDescriptor properties[];
615       MethodDescriptor methods[];
616       //       methods = bi.getMethodDescriptors();
617       properties = bi.getPropertyDescriptors();
618       for (int i=0;i<properties.length;i++) {
619         String name = properties[i].getDisplayName();
620         Method meth = properties[i].getReadMethod();
621         Object retType = meth.getReturnType();
622         if (retType.equals(ASEvaluation.class)) {
623           Class args [] = { };
624           ASEvaluation tempEval = (ASEvaluation)(meth.invoke(m_searchMethod,
625                                                              (Object[])args));
626           if (tempEval instanceof AttributeTransformer) {
627             // grab the transformed data header
628             m_trainInstances = 
629               ((AttributeTransformer)tempEval).transformedHeader();
630             m_transformer = (AttributeTransformer)tempEval;
631           }
632         }
633       }
634     } catch (IntrospectionException ex) {
635       System.err.println("AttributeSelection: Couldn't "
636                          +"introspect");
637     }
638     
639     
640     // Do any postprocessing that a attribute selection method might require
641     attributeSet = m_ASEvaluator.postProcess(attributeSet);
642     if (!m_doRank) {
643       m_selectionResults.append(printSelectionResults());
644     }
645     
646    if ((m_searchMethod instanceof RankedOutputSearch) && m_doRank == true) {
647      m_attributeRanking = 
648        ((RankedOutputSearch)m_searchMethod).rankedAttributes();
649      m_selectionResults.append(printSelectionResults());
650      m_selectionResults.append("Ranked attributes:\n");
651
652      // retrieve the number of attributes to retain
653      m_numToSelect = 
654        ((RankedOutputSearch)m_searchMethod).getCalculatedNumToSelect();
655
656      // determine fieldwidth for merit
657      int f_p=0;
658      int w_p=0;
659     
660      for (int i = 0; i < m_numToSelect; i++) {
661        double precision = (Math.abs(m_attributeRanking[i][1]) - 
662                            (int)(Math.abs(m_attributeRanking[i][1])));
663        double intPart = (int)(Math.abs(m_attributeRanking[i][1]));
664
665        if (precision > 0) {
666          precision = Math.abs((Math.log(Math.abs(precision)) / 
667                                Math.log(10)))+3;
668        }
669        if (precision > f_p) {
670          f_p = (int)precision;
671        }
672
673        if (intPart == 0) {
674          if (w_p < 2) {
675            w_p = 2;
676          }
677        } else if ((Math.abs((Math.log(Math.abs(m_attributeRanking[i][1])) 
678                       / Math.log(10)))+1) > w_p) {
679          if (m_attributeRanking[i][1] > 0) {
680            w_p = (int)Math.abs((Math.log(Math.abs(m_attributeRanking[i][1]))
681                                 / Math.log(10)))+1;
682          }
683        }
684      }
685
686      for (int i = 0; i < m_numToSelect; i++) {
687        m_selectionResults.
688          append(Utils.doubleToString(m_attributeRanking[i][1],
689                                      f_p+w_p+1,f_p) 
690                 + Utils.doubleToString((m_attributeRanking[i][0] + 1),
691                                        fieldWidth+1,0) 
692                 + " " 
693                 + m_trainInstances.
694                 attribute((int)m_attributeRanking[i][0]).name() 
695                 + "\n");
696      }
697       
698      // set up the selected attributes array - usable by a filter or
699      // whatever
700      if (m_trainInstances.classIndex() >= 0) {
701        if ((!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator)       
702             && !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) ||
703            m_ASEvaluator instanceof AttributeTransformer) {
704          // one more for the class
705          m_selectedAttributeSet = new int[m_numToSelect + 1];
706          m_selectedAttributeSet[m_numToSelect] = 
707              m_trainInstances.classIndex();
708        } else {
709          m_selectedAttributeSet = new int[m_numToSelect];
710        }
711      } else {
712        m_selectedAttributeSet = new int[m_numToSelect];
713      }
714
715      m_selectionResults.append("\nSelected attributes: ");
716
717      for (int i = 0; i < m_numToSelect; i++) {
718        m_selectedAttributeSet[i] = (int)m_attributeRanking[i][0];
719         
720        if (i == m_numToSelect - 1) {
721          m_selectionResults.append(((int)m_attributeRanking[i][0] + 1) 
722                                    + " : " 
723                                    + (i + 1) 
724                                    + "\n");
725        }
726        else {
727          m_selectionResults.append(((int)m_attributeRanking[i][0] + 1));
728          m_selectionResults.append(",");
729        }
730      }
731    } else {
732      // set up the selected attributes array - usable by a filter or
733      // whatever
734      if ((!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) 
735           && !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) || 
736          m_trainInstances.classIndex() >= 0) 
737        // one more for the class
738        {
739          m_selectedAttributeSet = new int[attributeSet.length + 1];
740          m_selectedAttributeSet[attributeSet.length] = 
741            m_trainInstances.classIndex();
742        }
743      else {
744        m_selectedAttributeSet = new int[attributeSet.length];
745      }
746     
747      for (int i = 0; i < attributeSet.length; i++) {
748        m_selectedAttributeSet[i] = attributeSet[i];
749      }
750
751      m_selectionResults.append("Selected attributes: ");
752
753      for (int i = 0; i < attributeSet.length; i++) {
754        if (i == (attributeSet.length - 1)) {
755          m_selectionResults.append((attributeSet[i] + 1) 
756                                    + " : " 
757                                    + attributeSet.length 
758                                    + "\n");
759        }
760        else {
761          m_selectionResults.append((attributeSet[i] + 1) + ",");
762        }
763      }
764
765      for (int i=0;i<attributeSet.length;i++) {
766        m_selectionResults.append("                     "
767                                  +m_trainInstances
768                                  .attribute(attributeSet[i]).name()
769                                  +"\n");
770      }
771    }
772
773    // Cross validation should be called from here
774    if (m_doXval == true) {
775      m_selectionResults.append(CrossValidateAttributes()); 
776    }
777
778    // set up the attribute filter with the selected attributes
779    if (m_selectedAttributeSet != null && !m_doXval) {
780      m_attributeFilter = new Remove();
781      m_attributeFilter.setAttributeIndicesArray(m_selectedAttributeSet);
782      m_attributeFilter.setInvertSelection(true);
783      m_attributeFilter.setInputFormat(m_trainInstances); 
784    }
785
786    // Save space
787    m_trainInstances = new Instances(m_trainInstances, 0);
788  }
789
790  /**
791   * Perform attribute selection with a particular evaluator and
792   * a set of options specifying search method and options for the
793   * search method and evaluator.
794   *
795   * @param ASEvaluator an evaluator object
796   * @param options an array of options, not only for the evaluator
797   * but also the search method (if any) and an input data file
798   * @param train the input instances
799   * @return the results of attribute selection as a String
800   * @exception Exception if incorrect options are supplied
801   */
802  public static String SelectAttributes (ASEvaluation ASEvaluator, 
803                                         String[] options, 
804                                         Instances train)
805    throws Exception {
806    int seed = 1, folds = 10;
807    String foldsString, seedString, searchName;
808    String classString;
809    String searchClassName;
810    String[] searchOptions = null; //new String [1];
811    ASSearch searchMethod = null;
812    boolean doCrossVal = false;
813    int classIndex = -1;
814    boolean helpRequested = false;
815    AttributeSelection trainSelector = new AttributeSelection();
816
817    try {
818      if (Utils.getFlag('h', options)) {
819        helpRequested = true;
820      }
821
822      // does data already have a class attribute set?
823      if (train.classIndex() != -1)
824        classIndex = train.classIndex() + 1;
825
826      // get basic options (options the same for all attribute selectors
827      classString = Utils.getOption('c', options);
828
829      if (classString.length() != 0) {
830        if (classString.equals("first")) {
831          classIndex = 1;
832        } else if (classString.equals("last")) {
833          classIndex = train.numAttributes();
834        } else {
835          classIndex = Integer.parseInt(classString);
836        }
837      }
838
839      if ((classIndex != -1) && 
840          ((classIndex == 0) || (classIndex > train.numAttributes()))) {
841        throw  new Exception("Class index out of range.");
842      }
843
844      if (classIndex != -1) {
845        train.setClassIndex(classIndex - 1);
846      }
847      else {
848        //      classIndex = train.numAttributes();
849        //      train.setClassIndex(classIndex - 1);
850      }
851     
852      foldsString = Utils.getOption('x', options);
853
854      if (foldsString.length() != 0) {
855        folds = Integer.parseInt(foldsString);
856        doCrossVal = true;
857      }
858     
859      trainSelector.setFolds(folds);
860      trainSelector.setXval(doCrossVal);
861
862      seedString = Utils.getOption('n', options);
863
864      if (seedString.length() != 0) {
865        seed = Integer.parseInt(seedString);
866      }
867
868      trainSelector.setSeed(seed);
869
870      searchName = Utils.getOption('s', options);
871
872      if ((searchName.length() == 0) && 
873          (!(ASEvaluator instanceof AttributeEvaluator))) {
874        throw  new Exception("No search method given.");
875      }
876
877      if (searchName.length() != 0) {
878        searchName = searchName.trim();
879        // split off any search options
880        int breakLoc = searchName.indexOf(' ');
881        searchClassName = searchName;
882        String searchOptionsString = "";
883
884        if (breakLoc != -1) {
885          searchClassName = searchName.substring(0, breakLoc);
886          searchOptionsString = searchName.substring(breakLoc).trim();
887          searchOptions = Utils.splitOptions(searchOptionsString);
888        }
889      }
890      else {
891        try {
892          searchClassName = new String("weka.attributeSelection.Ranker");
893          searchMethod = (ASSearch)Class.
894            forName(searchClassName).newInstance();
895        }
896        catch (Exception e) {
897          throw  new Exception("Can't create Ranker object");
898        }
899      }
900
901      // if evaluator is a subset evaluator
902      // create search method and set its options (if any)
903      if (searchMethod == null) {
904        searchMethod = ASSearch.forName(searchClassName, searchOptions);
905      }
906
907      // set the search method
908      trainSelector.setSearch(searchMethod);
909    }
910    catch (Exception e) {
911      throw  new Exception('\n' + e.getMessage() 
912                           + makeOptionString(ASEvaluator, searchMethod));
913    }
914
915    try {
916      // Set options for ASEvaluator
917      if (ASEvaluator instanceof OptionHandler) {
918        ((OptionHandler)ASEvaluator).setOptions(options);
919      }
920
921      /* // Set options for Search method
922         if (searchMethod instanceof OptionHandler)
923         {
924         if (searchOptions != null)
925         {
926         ((OptionHandler)searchMethod).setOptions(searchOptions);
927         }
928         }
929         Utils.checkForRemainingOptions(searchOptions); */
930    }
931    catch (Exception e) {
932      throw  new Exception("\n" + e.getMessage() 
933                           + makeOptionString(ASEvaluator, searchMethod));
934    }
935
936    try {
937      Utils.checkForRemainingOptions(options);
938    }
939    catch (Exception e) {
940      throw  new Exception('\n' + e.getMessage() 
941                           + makeOptionString(ASEvaluator, searchMethod));
942    }
943
944    if (helpRequested) {
945      System.out.println(makeOptionString(ASEvaluator, searchMethod));
946      System.exit(0);
947    }
948
949    // set the attribute evaluator
950    trainSelector.setEvaluator(ASEvaluator);
951
952    // do the attribute selection
953    trainSelector.SelectAttributes(train);
954
955    // return the results string
956    return trainSelector.toResultsString();
957  }
958
959
960  /**
961   * Assembles a text description of the attribute selection results.
962   *
963   * @return a string describing the results of attribute selection.
964   */
965  private String printSelectionResults () {
966    StringBuffer text = new StringBuffer();
967    text.append("\n\n=== Attribute Selection on all input data ===\n\n" 
968                + "Search Method:\n");
969    text.append(m_searchMethod.toString());
970    text.append("\nAttribute ");
971
972    if (m_ASEvaluator instanceof SubsetEvaluator) {
973      text.append("Subset Evaluator (");
974    }
975    else {
976      text.append("Evaluator (");
977    }
978
979    if (!(m_ASEvaluator instanceof UnsupervisedSubsetEvaluator) 
980        && !(m_ASEvaluator instanceof UnsupervisedAttributeEvaluator)) {
981      text.append("supervised, ");
982      text.append("Class (");
983
984      if (m_trainInstances.attribute(m_trainInstances.classIndex())
985          .isNumeric()) {
986        text.append("numeric): ");
987      }
988      else {
989        text.append("nominal): ");
990      }
991
992      text.append((m_trainInstances.classIndex() + 1) 
993                  + " " 
994                  + m_trainInstances.attribute(m_trainInstances
995                                               .classIndex()).name() 
996                  + "):\n");
997    }
998    else {
999      text.append("unsupervised):\n");
1000    }
1001
1002    text.append(m_ASEvaluator.toString() + "\n");
1003    return  text.toString();
1004  }
1005
1006
1007  /**
1008   * Make up the help string giving all the command line options
1009   *
1010   * @param ASEvaluator the attribute evaluator to include options for
1011   * @param searchMethod the search method to include options for
1012   * @return a string detailing the valid command line options
1013   * @throws Exception if something goes wrong
1014   */
1015  private static String makeOptionString (ASEvaluation ASEvaluator, 
1016                                          ASSearch searchMethod)
1017    throws Exception {
1018   
1019    StringBuffer optionsText = new StringBuffer("");
1020    // General options
1021    optionsText.append("\n\nGeneral options:\n\n");
1022    optionsText.append("-h\n\tdisplay this help\n");
1023    optionsText.append("-i <name of input file>\n");
1024    optionsText.append("\tSets training file.\n");
1025    optionsText.append("-c <class index>\n");
1026    optionsText.append("\tSets the class index for supervised attribute\n");
1027    optionsText.append("\tselection. Default=last column.\n");
1028    optionsText.append("-s <class name>\n");
1029    optionsText.append("\tSets search method for subset evaluators.\n");
1030    optionsText.append("-x <number of folds>\n");
1031    optionsText.append("\tPerform a cross validation.\n");
1032    optionsText.append("-n <random number seed>\n");
1033    optionsText.append("\tUse in conjunction with -x.\n");
1034
1035    // Get attribute evaluator-specific options
1036    if (ASEvaluator instanceof OptionHandler) {
1037      optionsText.append("\nOptions specific to " 
1038                         + ASEvaluator.getClass().getName() 
1039                         + ":\n\n");
1040      Enumeration enu = ((OptionHandler)ASEvaluator).listOptions();
1041
1042      while (enu.hasMoreElements()) {
1043        Option option = (Option)enu.nextElement();
1044        optionsText.append(option.synopsis() + '\n');
1045        optionsText.append(option.description() + "\n");
1046      }
1047    }
1048
1049    if (searchMethod != null) {
1050      if (searchMethod instanceof OptionHandler) {
1051        optionsText.append("\nOptions specific to " 
1052                           + searchMethod.getClass().getName() 
1053                           + ":\n\n");
1054        Enumeration enu = ((OptionHandler)searchMethod).listOptions();
1055
1056        while (enu.hasMoreElements()) {
1057          Option option = (Option)enu.nextElement();
1058          optionsText.append(option.synopsis() + '\n');
1059          optionsText.append(option.description() + "\n");
1060        }
1061      }
1062    }
1063    else {
1064      if (ASEvaluator instanceof SubsetEvaluator) {
1065        System.out.println("No search method given.");
1066      }
1067    }
1068
1069    return  optionsText.toString();
1070  }
1071
1072
1073  /**
1074   * Main method for testing this class.
1075   *
1076   * @param args the options
1077   */
1078  public static void main (String[] args) {
1079    try {
1080      if (args.length == 0) {
1081        throw  new Exception("The first argument must be the name of an " 
1082                             + "attribute/subset evaluator");
1083      }
1084
1085      String EvaluatorName = args[0];
1086      args[0] = "";
1087      ASEvaluation newEval = ASEvaluation.forName(EvaluatorName, null);
1088      System.out.println(SelectAttributes(newEval, args));
1089    }
1090    catch (Exception e) {
1091      System.out.println(e.getMessage());
1092    }
1093  }
1094 
1095  /**
1096   * Returns the revision string.
1097   *
1098   * @return            the revision
1099   */
1100  public String getRevision() {
1101    return RevisionUtils.extract("$Revision: 1.47 $");
1102  }
1103}
Note: See TracBrowser for help on using the repository browser.