source: src/main/java/weka/attributeSelection/LFSMethods.java @ 13

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

Import di weka.

File size: 19.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 *    LFSMethods.java
19 *    Copyright (C) 2007 Martin Guetlein
20 *
21 */
22package weka.attributeSelection;
23
24import weka.core.FastVector;
25import weka.core.Instances;
26import weka.core.RevisionHandler;
27import weka.core.RevisionUtils;
28import weka.core.Utils;
29
30import java.io.Serializable;
31import java.util.BitSet;
32import java.util.Hashtable;
33
34/**
35 * @author Martin Guetlein (martin.guetlein@gmail.com)
36 * @version $Revision: 4899 $
37 */
38public class LFSMethods
39  implements RevisionHandler {
40 
41  /** max-size of array bestGroupOfSize, should be suffient */
42  private final static int MAX_SUBSET_SIZE = 200;
43  private BitSet m_bestGroup;
44  private double m_bestMerit;
45  private int m_evalsTotal;
46  private int m_evalsCached;
47  private BitSet[] m_bestGroupOfSize = new BitSet[MAX_SUBSET_SIZE];
48
49  /**
50   * empty constructor
51   *
52   * methods are not static because of access to inner class Link2 and
53   * LinkedList2
54   *
55   */
56  public LFSMethods() {
57  }
58
59  /**
60   * @return best group found by forwardSearch/floatingForwardSearch
61   */
62  public BitSet getBestGroup() {
63    return m_bestGroup;
64  }
65
66  /**
67   * @return merit of best group found by forwardSearch/floatingForwardSearch
68   */
69  public double getBestMerit() {
70    return m_bestMerit;
71  }
72
73  /**
74   * @return best group of size found by forwardSearch
75   */
76  public BitSet getBestGroupOfSize(int size) {
77    return m_bestGroupOfSize[size];
78  }
79
80  /**
81   * @return number of cached / not performed evaluations
82   */
83  public int getNumEvalsCached() {
84    return m_evalsCached;
85  }
86
87  /**
88   * @return number totally performed evaluations
89   */
90  public int getNumEvalsTotal() {
91    return m_evalsTotal;
92  }
93
94  /**
95   * @return ranking (integer array) of attributes in data with evaluator (sorting is NOT stable!)
96   */
97  public int[] rankAttributes(Instances data, SubsetEvaluator evaluator,
98                              boolean verbose) throws Exception {
99    if (verbose) {
100      System.out.println("Ranking attributes with " +
101                         evaluator.getClass().getName());
102    }
103
104    double[] merit = new double[data.numAttributes()];
105    BitSet group = new BitSet(data.numAttributes());
106
107    for (int k = 0; k < data.numAttributes(); k++) {
108      if (k != data.classIndex()) {
109        group.set(k);
110        merit[k] -= evaluator.evaluateSubset(group);
111        m_evalsTotal++;
112        group.clear(k);
113      } else {
114        merit[k] = Double.MAX_VALUE;
115      }
116
117      if (verbose) {
118        System.out.println(k + ": " + merit[k]);
119      }
120    }
121
122    int[] ranking = Utils.sort(merit);
123
124    if (verbose) {
125      System.out.print("Ranking [ ");
126
127      for (int i = 0; i < ranking.length; i++) {
128        System.out.print(ranking[i] + " ");
129      }
130
131      System.out.println("]\n");
132    }
133
134    return ranking;
135  }
136
137  /**
138   * Performs linear forward selection
139   *
140   * @param cacheSize         chacheSize (times number of instances) to store already evaluated sets
141   * @param startGroup        start group for search (can be null)
142   * @param ranking                ranking of attributes (as produced by rankAttributes), no ranking would be [0,1,2,3,4..]
143   * @param k                                number of top k attributes that are taken into account
144   * @param incrementK        true -> fixed-set, false -> fixed-width
145   * @param maxStale                number of times the search proceeds even though no improvement was found (1 = hill-climbing)
146   * @param forceResultSize        stopping criteria changed from no-improvement (forceResultSize=-1) to subset-size
147   * @param data
148   * @param evaluator
149   * @param verbose
150   * @return                                BitSet, that cotains the best-group found
151   * @throws Exception
152   */
153  public BitSet forwardSearch(int cacheSize, BitSet startGroup, int[] ranking,
154                              int k, boolean incrementK, int maxStale, int forceResultSize,
155                              Instances data, SubsetEvaluator evaluator, boolean verbose)
156    throws Exception {
157    if ((forceResultSize > 0) && (maxStale > 1)) {
158      throw new Exception("Forcing result size only works for maxStale=1");
159    }
160
161    if (verbose) {
162      System.out.println("Starting forward selection");
163    }
164
165    BitSet bestGroup;
166    BitSet tempGroup;
167    int bestSize = 0;
168    int tempSize = 0;
169    double bestMerit;
170    double tempMerit = 0;
171    Link2 link;
172    LinkedList2 list = new LinkedList2(maxStale);
173    Hashtable alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
174    int insertCount = 0;
175    int stale = 0;
176    boolean improvement;
177    int thisK = k;
178    int evalsTotal = 0;
179    int evalsCached = 0;
180
181    bestGroup = (BitSet) startGroup.clone();
182
183    String hashKey = bestGroup.toString();
184    bestMerit = evaluator.evaluateSubset(bestGroup);
185
186    if (verbose) {
187      System.out.print("Group: ");
188      printGroup(bestGroup, data.numAttributes());
189      System.out.println("Merit: " + tempMerit);
190      System.out.println("----------");
191    }
192
193    alreadyExpanded.put(hashKey, new Double(bestMerit));
194    insertCount++;
195    bestSize = bestGroup.cardinality();
196
197    //the list is only used if best-first search is applied
198    if (maxStale > 1) {
199      Object[] best = new Object[1];
200      best[0] = bestGroup.clone();
201      list.addToList(best, bestMerit);
202    }
203
204    while (stale < maxStale) {
205      improvement = false;
206
207      //best-first: take first elem from list
208      if (maxStale > 1) {
209        if (list.size() == 0) {
210          stale = maxStale;
211
212          break;
213        }
214
215        link = list.getLinkAt(0);
216        tempGroup = (BitSet) (link.getData()[0]);
217        tempGroup = (BitSet) tempGroup.clone();
218        list.removeLinkAt(0);
219
220        tempSize = 0;
221
222        for (int i = 0; i < data.numAttributes(); i++) {
223          if (tempGroup.get(i)) {
224            tempSize++;
225          }
226        }
227      } else //hill-climbing
228        {
229          tempGroup = (BitSet) bestGroup.clone();
230          tempSize = bestSize;
231        }
232
233      //set number of top k attributes that are taken into account
234      if (incrementK) {
235        thisK = Math.min(Math.max(thisK, k + tempSize), data.numAttributes());
236      } else {
237        thisK = k;
238      }
239
240      //temporarilly add attributes to current set
241      for (int i = 0; i < thisK; i++) {
242        if ((ranking[i] == data.classIndex()) || tempGroup.get(ranking[i])) {
243          continue;
244        }
245
246        tempGroup.set(ranking[i]);
247        tempSize++;
248        hashKey = tempGroup.toString();
249
250        if (!alreadyExpanded.containsKey(hashKey)) {
251          evalsTotal++;
252          tempMerit = evaluator.evaluateSubset(tempGroup);
253
254          if (insertCount > (cacheSize * data.numAttributes())) {
255            alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
256            insertCount = 0;
257          }
258
259          alreadyExpanded.put(hashKey, new Double(tempMerit));
260          insertCount++;
261        } else {
262          evalsCached++;
263          tempMerit = ((Double) alreadyExpanded.get(hashKey)).doubleValue();
264        }
265
266        if (verbose) {
267          System.out.print("Group: ");
268          printGroup(tempGroup, data.numAttributes());
269          System.out.println("Merit: " + tempMerit);
270        }
271
272        if (((tempMerit - bestMerit) > 0.00001) ||
273            ((forceResultSize >= tempSize) && (tempSize > bestSize))) {
274          improvement = true;
275          stale = 0;
276          bestMerit = tempMerit;
277          bestSize = tempSize;
278          bestGroup = (BitSet) (tempGroup.clone());
279          m_bestGroupOfSize[bestSize] = (BitSet) (tempGroup.clone());
280        }
281
282        if (maxStale > 1) {
283          Object[] add = new Object[1];
284          add[0] = tempGroup.clone();
285          list.addToList(add, tempMerit);
286        }
287
288        tempGroup.clear(ranking[i]);
289        tempSize--;
290      }
291
292      if (verbose) {
293        System.out.println("----------");
294      }
295
296      //handle stopping criteria
297      if (!improvement || (forceResultSize == bestSize)) {
298        stale++;
299      }
300
301      if ((forceResultSize > 0) && (bestSize == forceResultSize)) {
302        break;
303      }
304    }
305
306    if (verbose) {
307      System.out.println("Best Group: ");
308      printGroup(bestGroup, data.numAttributes());
309      System.out.println();
310    }
311
312    m_bestGroup = bestGroup;
313    m_bestMerit = bestMerit;
314    m_evalsTotal += evalsTotal;
315    m_evalsCached += evalsCached;
316
317    return bestGroup;
318  }
319
320  /**
321   * Performs linear floating forward selection
322   * ( the stopping criteria cannot be changed to a specific size value )
323   *
324   *
325   * @param cacheSize         chacheSize (times number of instances) to store already evaluated sets
326   * @param startGroup        start group for search (can be null)
327   * @param ranking                ranking of attributes (as produced by rankAttributes), no ranking would be [0,1,2,3,4..]
328   * @param k                                number of top k attributes that are taken into account
329   * @param incrementK        true -> fixed-set, false -> fixed-width
330   * @param maxStale                number of times the search proceeds even though no improvement was found (1 = hill-climbing)
331   * @param data
332   * @param evaluator
333   * @param verbose
334   * @return                                BitSet, that cotains the best-group found
335   * @throws Exception
336   */
337  public BitSet floatingForwardSearch(int cacheSize, BitSet startGroup,
338                                      int[] ranking, int k, boolean incrementK, int maxStale, Instances data,
339                                      SubsetEvaluator evaluator, boolean verbose) throws Exception {
340    if (verbose) {
341      System.out.println("Starting floating forward selection");
342    }
343
344    BitSet bestGroup;
345    BitSet tempGroup;
346    int bestSize = 0;
347    int tempSize = 0;
348    double bestMerit;
349    double tempMerit = 0;
350    Link2 link;
351    LinkedList2 list = new LinkedList2(maxStale);
352    Hashtable alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
353    int insertCount = 0;
354    int backtrackingSteps = 0;
355    boolean improvement;
356    boolean backward;
357    int thisK = k;
358    int evalsTotal = 0;
359    int evalsCached = 0;
360
361    bestGroup = (BitSet) startGroup.clone();
362
363    String hashKey = bestGroup.toString();
364    bestMerit = evaluator.evaluateSubset(bestGroup);
365
366    if (verbose) {
367      System.out.print("Group: ");
368      printGroup(bestGroup, data.numAttributes());
369      System.out.println("Merit: " + tempMerit);
370      System.out.println("----------");
371    }
372
373    alreadyExpanded.put(hashKey, new Double(bestMerit));
374    insertCount++;
375    bestSize = bestGroup.cardinality();
376
377    if (maxStale > 1) {
378      Object[] best = new Object[1];
379      best[0] = bestGroup.clone();
380      list.addToList(best, bestMerit);
381    }
382
383    backward = improvement = true;
384
385    while (true) {
386      // we are search in backward direction ->
387      // continue backward search as long as a new best set is found
388      if (backward) {
389        if (!improvement) {
390          backward = false;
391        }
392      }
393      // we are searching forward -> 
394      // stop search or start backward step
395      else {
396        if (!improvement && (backtrackingSteps >= maxStale)) {
397          break;
398        }
399
400        backward = true;
401      }
402
403      improvement = false;
404
405      // best-first: take first elem from list
406      if (maxStale > 1) {
407        if (list.size() == 0) {
408          backtrackingSteps = maxStale;
409
410          break;
411        }
412
413        link = list.getLinkAt(0);
414        tempGroup = (BitSet) (link.getData()[0]);
415        tempGroup = (BitSet) tempGroup.clone();
416        list.removeLinkAt(0);
417
418        tempSize = 0;
419
420        for (int i = 0; i < data.numAttributes(); i++) {
421          if (tempGroup.get(i)) {
422            tempSize++;
423          }
424        }
425      } else //hill-climbing
426        {
427          tempGroup = (BitSet) bestGroup.clone();
428          tempSize = bestSize;
429        }
430
431      //backward search only makes sense for set-size bigger than 2
432      if (backward && (tempSize <= 2)) {
433        backward = false;
434      }
435
436      //set number of top k attributes that are taken into account
437      if (incrementK) {
438        thisK = Math.max(thisK,
439                         Math.min(Math.max(thisK, k + tempSize), data.numAttributes()));
440      } else {
441        thisK = k;
442      }
443
444      //temporarilly add/remove attributes to/from current set
445      for (int i = 0; i < thisK; i++) {
446        if (ranking[i] == data.classIndex()) {
447          continue;
448        }
449
450        if (backward) {
451          if (!tempGroup.get(ranking[i])) {
452            continue;
453          }
454
455          tempGroup.clear(ranking[i]);
456          tempSize--;
457        } else {
458          if ((ranking[i] == data.classIndex()) || tempGroup.get(ranking[i])) {
459            continue;
460          }
461
462          tempGroup.set(ranking[i]);
463          tempSize++;
464        }
465
466        hashKey = tempGroup.toString();
467
468        if (!alreadyExpanded.containsKey(hashKey)) {
469          evalsTotal++;
470          tempMerit = evaluator.evaluateSubset(tempGroup);
471
472          if (insertCount > (cacheSize * data.numAttributes())) {
473            alreadyExpanded = new Hashtable(cacheSize * data.numAttributes());
474            insertCount = 0;
475          }
476
477          alreadyExpanded.put(hashKey, new Double(tempMerit));
478          insertCount++;
479        } else {
480          evalsCached++;
481          tempMerit = ((Double) alreadyExpanded.get(hashKey)).doubleValue();
482        }
483
484        if (verbose) {
485          System.out.print("Group: ");
486          printGroup(tempGroup, data.numAttributes());
487          System.out.println("Merit: " + tempMerit);
488        }
489
490        if ((tempMerit - bestMerit) > 0.00001) {
491          improvement = true;
492          backtrackingSteps = 0;
493          bestMerit = tempMerit;
494          bestSize = tempSize;
495          bestGroup = (BitSet) (tempGroup.clone());
496        }
497
498        if (maxStale > 1) {
499          Object[] add = new Object[1];
500          add[0] = tempGroup.clone();
501          list.addToList(add, tempMerit);
502        }
503
504        if (backward) {
505          tempGroup.set(ranking[i]);
506          tempSize++;
507        } else {
508          tempGroup.clear(ranking[i]);
509          tempSize--;
510        }
511      }
512
513      if (verbose) {
514        System.out.println("----------");
515      }
516
517      if ((maxStale > 1) && backward && !improvement) {
518        Object[] add = new Object[1];
519        add[0] = tempGroup.clone();
520        list.addToList(add, Double.MAX_VALUE);
521      }
522
523      if (!backward && !improvement) {
524        backtrackingSteps++;
525      }
526    }
527
528    if (verbose) {
529      System.out.println("Best Group: ");
530      printGroup(bestGroup, data.numAttributes());
531      System.out.println();
532    }
533
534    m_bestGroup = bestGroup;
535    m_bestMerit = bestMerit;
536    m_evalsTotal += evalsTotal;
537    m_evalsCached += evalsCached;
538
539    return bestGroup;
540  }
541
542  /**
543   * Debug-out
544   */
545  protected static void printGroup(BitSet tt, int numAttribs) {
546    System.out.print("{ ");
547
548    for (int i = 0; i < numAttribs; i++) {
549      if (tt.get(i) == true) {
550        System.out.print((i + 1) + " ");
551      }
552    }
553
554    System.out.println("}");
555  }
556
557  // Inner classes
558  /**
559   * Class for a node in a linked list. Used in best first search.
560   * Copied from BestFirstSearch
561   *
562   * @author Mark Hall (mhall@cs.waikato.ac.nz)
563   */
564  public class Link2
565    implements Serializable, RevisionHandler {
566   
567    /** for serialization. */
568    private static final long serialVersionUID = -7422719407475185086L;
569   
570    /* BitSet group; */
571    Object[] m_data;
572    double m_merit;
573
574    // Constructor
575    public Link2(Object[] data, double mer) {
576      // group = (BitSet)gr.clone();
577      m_data = data;
578      m_merit = mer;
579    }
580
581    /** Get a group */
582    public Object[] getData() {
583      return m_data;
584    }
585
586    public String toString() {
587      return ("Node: " + m_data.toString() + "  " + m_merit);
588    }
589   
590    /**
591     * Returns the revision string.
592     *
593     * @return          the revision
594     */
595    public String getRevision() {
596      return RevisionUtils.extract("$Revision: 4899 $");
597    }
598  }
599
600  /**
601   * Class for handling a linked list. Used in best first search. Extends the
602   * Vector class.
603   *
604   * @author Mark Hall (mhall@cs.waikato.ac.nz)
605   */
606  public class LinkedList2
607    extends FastVector {
608   
609    /** for serialization. */
610    private static final long serialVersionUID = -7776010892419656105L;
611   
612    // Max number of elements in the list
613    int m_MaxSize;
614
615    // ================
616    // Public methods
617    // ================
618    public LinkedList2(int sz) {
619      super();
620      m_MaxSize = sz;
621    }
622
623    /**
624     * removes an element (Link) at a specific index from the list.
625     *
626     * @param index
627     *            the index of the element to be removed.
628     */
629    public void removeLinkAt(int index) throws Exception {
630      if ((index >= 0) && (index < size())) {
631        removeElementAt(index);
632      } else {
633        throw new Exception("index out of range (removeLinkAt)");
634      }
635    }
636
637    /**
638     * returns the element (Link) at a specific index from the list.
639     *
640     * @param index
641     *            the index of the element to be returned.
642     */
643    public Link2 getLinkAt(int index) throws Exception {
644      if (size() == 0) {
645        throw new Exception("List is empty (getLinkAt)");
646      } else {
647        if ((index >= 0) && (index < size())) {
648          return ((Link2) (elementAt(index)));
649        } else {
650          throw new Exception("index out of range (getLinkAt)");
651        }
652      }
653    }
654
655    /**
656     * adds an element (Link) to the list.
657     *
658     * @param data
659     *            the data to add
660     * @param mer
661     *            the "merit" of this attribute set
662     */
663    public void addToList(Object[] data, double mer) throws Exception {
664      Link2 newL = new Link2(data, mer);
665
666      if (size() == 0) {
667        addElement(newL);
668      } else {
669        if (mer > ((Link2) (firstElement())).m_merit) {
670          if (size() == m_MaxSize) {
671            removeLinkAt(m_MaxSize - 1);
672          }
673
674          // ----------
675          insertElementAt(newL, 0);
676        } else {
677          int i = 0;
678          int size = size();
679          boolean done = false;
680
681          // ------------
682          // don't insert if list contains max elements an this
683          // is worst than the last
684          if ((size == m_MaxSize) &&
685              (mer <= ((Link2) (lastElement())).m_merit)) {
686          }
687          // ---------------
688          else {
689            while ((!done) && (i < size)) {
690              if (mer > ((Link2) (elementAt(i))).m_merit) {
691                if (size == m_MaxSize) {
692                  removeLinkAt(m_MaxSize - 1);
693                }
694
695                // ---------------------
696                insertElementAt(newL, i);
697                done = true;
698              } else {
699                if (i == (size - 1)) {
700                  addElement(newL);
701                  done = true;
702                } else {
703                  i++;
704                }
705              }
706            }
707          }
708        }
709      }
710    }
711   
712    /**
713     * Returns the revision string.
714     *
715     * @return          the revision
716     */
717    public String getRevision() {
718      return RevisionUtils.extract("$Revision: 4899 $");
719    }
720  }
721 
722  /**
723   * Returns the revision string.
724   *
725   * @return            the revision
726   */
727  public String getRevision() {
728    return RevisionUtils.extract("$Revision: 4899 $");
729  }
730}
Note: See TracBrowser for help on using the repository browser.