source: branches/MetisMQI/src/main/java/weka/filters/unsupervised/attribute/Wavelet.java

Last change on this file was 29, checked in by gnappo, 15 years ago

Taggata versione per la demo e aggiunto branch.

File size: 19.5 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 * Wavelet.java
19 * Copyright (C) 2006 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.filters.unsupervised.attribute;
24
25import weka.core.Attribute;
26import weka.core.Capabilities;
27import weka.core.FastVector;
28import weka.core.Instance; 
29import weka.core.DenseInstance;
30import weka.core.Instances;
31import weka.core.Option;
32import weka.core.OptionHandler;
33import weka.core.RevisionUtils;
34import weka.core.SelectedTag;
35import weka.core.Tag;
36import weka.core.TechnicalInformation;
37import weka.core.TechnicalInformationHandler;
38import weka.core.Utils;
39import weka.core.Capabilities.Capability;
40import weka.core.TechnicalInformation.Field;
41import weka.core.TechnicalInformation.Type;
42import weka.filters.Filter;
43import weka.filters.MultiFilter;
44import weka.filters.SimpleBatchFilter;
45
46import java.util.Enumeration;
47import java.util.Vector;
48
49/**
50 <!-- globalinfo-start -->
51 * A filter for wavelet transformation.<br/>
52 * <br/>
53 * For more information see:<br/>
54 * <br/>
55 * Wikipedia (2004). Discrete wavelet transform.<br/>
56 * <br/>
57 * Kristian Sandberg (2000). The Haar wavelet transform. University of Colorado at Boulder, USA.
58 * <p/>
59 <!-- globalinfo-end -->
60 *
61 <!-- technical-bibtex-start -->
62 * BibTeX:
63 * <pre>
64 * &#64;misc{Wikipedia2004,
65 *    author = {Wikipedia},
66 *    title = {Discrete wavelet transform},
67 *    year = {2004},
68 *    HTTP = {http://en.wikipedia.org/wiki/Discrete_wavelet_transform}
69 * }
70 *
71 * &#64;misc{Sandberg2000,
72 *    address = {University of Colorado at Boulder, USA},
73 *    author = {Kristian Sandberg},
74 *    institution = {Dept. of Applied Mathematics},
75 *    title = {The Haar wavelet transform},
76 *    year = {2000},
77 *    HTTP = {http://amath.colorado.edu/courses/5720/2000Spr/Labs/Haar/haar.html}
78 * }
79 * </pre>
80 * <p/>
81 <!-- technical-bibtex-end -->
82 *
83 <!-- options-start -->
84 * Valid options are: <p/>
85 *
86 * <pre> -D
87 *  Turns on output of debugging information.</pre>
88 *
89 * <pre> -A &lt;Haar&gt;
90 *  The algorithm to use.
91 *  (default: HAAR)</pre>
92 *
93 * <pre> -P &lt;Zero&gt;
94 *  The padding to use.
95 *  (default: ZERO)</pre>
96 *
97 * <pre> -F &lt;filter specification&gt;
98 *  The filter to use as preprocessing step (classname and options).
99 *  (default: MultiFilter with ReplaceMissingValues and Normalize)</pre>
100 *
101 * <pre>
102 * Options specific to filter weka.filters.MultiFilter ('-F'):
103 * </pre>
104 *
105 * <pre> -D
106 *  Turns on output of debugging information.</pre>
107 *
108 * <pre> -F &lt;classname [options]&gt;
109 *  A filter to apply (can be specified multiple times).</pre>
110 *
111 <!-- options-end -->
112 *
113 * @author FracPete (fracpete at waikato dot ac dot nz)
114 * @version $Revision: 5987 $
115 */
116public class Wavelet
117  extends SimpleBatchFilter
118  implements TechnicalInformationHandler {
119
120  /** for serialization */
121  static final long serialVersionUID = -3335106965521265631L;
122
123  /** the type of algorithm: Haar wavelet */
124  public static final int ALGORITHM_HAAR = 0;
125  /** the types of algorithm */
126  public static final Tag[] TAGS_ALGORITHM = {
127    new Tag(ALGORITHM_HAAR, "Haar")
128  };
129
130  /** the type of padding: Zero padding */
131  public static final int PADDING_ZERO = 0;
132  /** the types of padding */
133  public static final Tag[] TAGS_PADDING = {
134    new Tag(PADDING_ZERO, "Zero")
135  };
136
137  /** an optional filter for preprocessing of the data */
138  protected Filter m_Filter = null;
139 
140  /** the type of algorithm */
141  protected int m_Algorithm = ALGORITHM_HAAR;
142 
143  /** the type of padding */
144  protected int m_Padding = PADDING_ZERO;
145 
146  /**
147   * default constructor
148   */
149  public Wavelet() {
150    super();
151   
152    m_Filter = new MultiFilter();
153    ((MultiFilter) m_Filter).setFilters(
154        new Filter[]{
155            new weka.filters.unsupervised.attribute.ReplaceMissingValues(),
156            new weka.filters.unsupervised.attribute.Normalize()
157            });
158  }
159 
160  /**
161   * Returns a string describing this classifier.
162   *
163   * @return      a description of the classifier suitable for
164   *              displaying in the explorer/experimenter gui
165   */
166  public String globalInfo() {
167    return 
168        "A filter for wavelet transformation.\n\n"
169      + "For more information see:\n\n"
170      + getTechnicalInformation().toString();
171  }
172
173  /**
174   * Returns an instance of a TechnicalInformation object, containing
175   * detailed information about the technical background of this class,
176   * e.g., paper reference or book this class is based on.
177   *
178   * @return the technical information about this class
179   */
180  public TechnicalInformation getTechnicalInformation() {
181    TechnicalInformation        result;
182    TechnicalInformation        additional;
183   
184    result = new TechnicalInformation(Type.MISC);
185    result.setValue(Field.AUTHOR, "Wikipedia");
186    result.setValue(Field.YEAR, "2004");
187    result.setValue(Field.TITLE, "Discrete wavelet transform");
188    result.setValue(Field.HTTP, "http://en.wikipedia.org/wiki/Discrete_wavelet_transform");
189   
190    additional = result.add(Type.MISC);
191    additional.setValue(Field.AUTHOR, "Kristian Sandberg");
192    additional.setValue(Field.YEAR, "2000");
193    additional.setValue(Field.TITLE, "The Haar wavelet transform");
194    additional.setValue(Field.INSTITUTION, "Dept. of Applied Mathematics");
195    additional.setValue(Field.ADDRESS, "University of Colorado at Boulder, USA");
196    additional.setValue(Field.HTTP, "http://amath.colorado.edu/courses/5720/2000Spr/Labs/Haar/haar.html");
197   
198    return result;
199  }
200
201  /**
202   * Gets an enumeration describing the available options.
203   *
204   * @return an enumeration of all the available options.
205   */
206  public Enumeration listOptions() {
207    Vector              result;
208    Enumeration         enm;
209    String              param;
210    SelectedTag         tag;
211    int                 i;
212
213    result = new Vector();
214
215    enm = super.listOptions();
216    while (enm.hasMoreElements())
217      result.addElement(enm.nextElement());
218
219    param = "";
220    for (i = 0; i < TAGS_ALGORITHM.length; i++) {
221      if (i > 0)
222        param += "|";
223      tag = new SelectedTag(TAGS_ALGORITHM[i].getID(), TAGS_ALGORITHM);
224      param += tag.getSelectedTag().getReadable();
225    }
226    result.addElement(new Option(
227        "\tThe algorithm to use.\n"
228        + "\t(default: HAAR)",
229        "A", 1, "-A <" + param + ">"));
230
231    param = "";
232    for (i = 0; i < TAGS_PADDING.length; i++) {
233      if (i > 0)
234        param += "|";
235      tag = new SelectedTag(TAGS_PADDING[i].getID(), TAGS_PADDING);
236      param += tag.getSelectedTag().getReadable();
237    }
238    result.addElement(new Option(
239        "\tThe padding to use.\n"
240        + "\t(default: ZERO)",
241        "P", 1, "-P <" + param + ">"));
242
243    result.addElement(new Option(
244        "\tThe filter to use as preprocessing step (classname and options).\n"
245        + "\t(default: MultiFilter with ReplaceMissingValues and Normalize)",
246        "F", 1, "-F <filter specification>"));
247
248    if (getFilter() instanceof OptionHandler) {
249      result.addElement(new Option(
250          "",
251          "", 0, "\nOptions specific to filter "
252          + getFilter().getClass().getName() + " ('-F'):"));
253     
254      enm = ((OptionHandler) getFilter()).listOptions();
255      while (enm.hasMoreElements())
256        result.addElement(enm.nextElement());
257    }
258
259    return result.elements();
260  }
261
262  /**
263   * returns the options of the current setup
264   *
265   * @return      the current options
266   */
267  public String[] getOptions() {
268    int       i;
269    Vector    result;
270    String[]  options;
271
272    result = new Vector();
273    options = super.getOptions();
274    for (i = 0; i < options.length; i++)
275      result.add(options[i]);
276
277    result.add("-A");
278    result.add("" + getAlgorithm().getSelectedTag().getReadable());
279
280    result.add("-P");
281    result.add("" + getPadding().getSelectedTag().getReadable());
282
283    result.add("-F");
284    if (getFilter() instanceof OptionHandler)
285      result.add(
286          getFilter().getClass().getName() 
287        + " " 
288        + Utils.joinOptions(((OptionHandler) getFilter()).getOptions()));
289    else
290      result.add(
291          getFilter().getClass().getName());
292
293    return (String[]) result.toArray(new String[result.size()]);         
294  }
295
296  /**
297   * Parses the options for this object. <p/>
298   *
299   <!-- options-start -->
300   * Valid options are: <p/>
301   *
302   * <pre> -D
303   *  Turns on output of debugging information.</pre>
304   *
305   * <pre> -A &lt;Haar&gt;
306   *  The algorithm to use.
307   *  (default: HAAR)</pre>
308   *
309   * <pre> -P &lt;Zero&gt;
310   *  The padding to use.
311   *  (default: ZERO)</pre>
312   *
313   * <pre> -F &lt;filter specification&gt;
314   *  The filter to use as preprocessing step (classname and options).
315   *  (default: MultiFilter with ReplaceMissingValues and Normalize)</pre>
316   *
317   * <pre>
318   * Options specific to filter weka.filters.MultiFilter ('-F'):
319   * </pre>
320   *
321   * <pre> -D
322   *  Turns on output of debugging information.</pre>
323   *
324   * <pre> -F &lt;classname [options]&gt;
325   *  A filter to apply (can be specified multiple times).</pre>
326   *
327   <!-- options-end -->
328   *
329   * @param options     the options to use
330   * @throws Exception  if the option setting fails
331   */
332  public void setOptions(String[] options) throws Exception {
333    String      tmpStr;
334    String[]    tmpOptions;
335    Filter      filter;
336
337    super.setOptions(options);
338
339    tmpStr = Utils.getOption("A", options);
340    if (tmpStr.length() != 0)
341      setAlgorithm(new SelectedTag(tmpStr, TAGS_ALGORITHM));
342    else
343      setAlgorithm(new SelectedTag(ALGORITHM_HAAR, TAGS_ALGORITHM));
344
345    tmpStr = Utils.getOption("P", options);
346    if (tmpStr.length() != 0)
347      setPadding(new SelectedTag(tmpStr, TAGS_PADDING));
348    else
349      setPadding(new SelectedTag(PADDING_ZERO, TAGS_PADDING));
350
351    tmpStr     = Utils.getOption("F", options);
352    tmpOptions = Utils.splitOptions(tmpStr);
353    if (tmpOptions.length != 0) {
354      tmpStr        = tmpOptions[0];
355      tmpOptions[0] = "";
356      setFilter((Filter) Utils.forName(Filter.class, tmpStr, tmpOptions));
357    }
358    else {
359      filter = new MultiFilter();
360      ((MultiFilter) filter).setFilters(
361          new Filter[]{
362              new weka.filters.unsupervised.attribute.ReplaceMissingValues(),
363              new weka.filters.unsupervised.attribute.Normalize()
364              });
365      setFilter(filter);
366    }
367  }
368 
369  /**
370   * Returns the tip text for this property
371   *
372   * @return            tip text for this property suitable for
373   *                    displaying in the explorer/experimenter gui
374   */
375  public String filterTipText() {
376    return "The preprocessing filter to use.";
377  }
378
379  /**
380   * Set the preprocessing filter (only used for setup).
381   *
382   * @param value       the preprocessing filter.
383   */
384  public void setFilter(Filter value) {
385    m_Filter = value;
386  }
387
388  /**
389   * Get the preprocessing filter.
390   *
391   * @return            the preprocessing filter
392   */
393  public Filter getFilter() {
394    return m_Filter;
395  }
396
397  /**
398   * Returns the tip text for this property
399   *
400   * @return            tip text for this property suitable for
401   *                    displaying in the explorer/experimenter gui
402   */
403  public String algorithmTipText() {
404    return "Sets the type of algorithm to use.";
405  }
406
407  /**
408   * Sets the type of algorithm to use
409   *
410   * @param value       the algorithm type
411   */
412  public void setAlgorithm(SelectedTag value) {
413    if (value.getTags() == TAGS_ALGORITHM) {
414      m_Algorithm = value.getSelectedTag().getID();
415    }
416  }
417
418  /**
419   * Gets the type of algorithm to use
420   *
421   * @return            the current algorithm type.
422   */
423  public SelectedTag getAlgorithm() {
424    return new SelectedTag(m_Algorithm, TAGS_ALGORITHM);
425  }
426
427  /**
428   * Returns the tip text for this property
429   *
430   * @return            tip text for this property suitable for
431   *                    displaying in the explorer/experimenter gui
432   */
433  public String paddingTipText() {
434    return "Sets the type of padding to use.";
435  }
436
437  /**
438   * Sets the type of Padding to use
439   *
440   * @param value       the Padding type
441   */
442  public void setPadding(SelectedTag value) {
443    if (value.getTags() == TAGS_PADDING) {
444      m_Padding = value.getSelectedTag().getID();
445    }
446  }
447
448  /**
449   * Gets the type of Padding to use
450   *
451   * @return            the current Padding type.
452   */
453  public SelectedTag getPadding() {
454    return new SelectedTag(m_Padding, TAGS_PADDING);
455  }
456
457  /**
458   * returns the next bigger number that's a power of 2. If the number is
459   * already a power of 2 then this will be returned. The number will be at
460   * least 2^2..
461   *
462   * @param n           the number to start from
463   * @return            the next bigger number
464   */
465  protected static int nextPowerOf2(int n) {
466    int         exp;
467   
468    exp = (int) StrictMath.ceil(StrictMath.log(n) / StrictMath.log(2.0));
469    exp = StrictMath.max(2, exp);
470   
471    return (int) StrictMath.pow(2, exp);
472  }
473 
474  /**
475   * pads the data to conform to the necessary number of attributes
476   *
477   * @param data        the data to pad
478   * @return            the padded data
479   */
480  protected Instances pad(Instances data) {
481    Instances           result;
482    int                 i;
483    int                 n;
484    String              prefix;
485    int                 numAtts;
486    boolean             isLast;
487    int                 index;
488    Vector<Integer>     padded;
489    int[]               indices;
490    FastVector          atts;
491
492    // determine number of padding attributes
493    switch (m_Padding) {
494      case PADDING_ZERO:
495        if (data.classIndex() > -1)
496          numAtts = (nextPowerOf2(data.numAttributes() - 1) + 1) - data.numAttributes();
497        else
498          numAtts = nextPowerOf2(data.numAttributes()) - data.numAttributes();
499        break;
500       
501      default:
502        throw new IllegalStateException(
503            "Padding " + new SelectedTag(m_Algorithm, TAGS_PADDING) 
504            + " not implemented!");
505    }
506
507    result = new Instances(data);
508    prefix = getAlgorithm().getSelectedTag().getReadable();
509
510    // any padding necessary?
511    if (numAtts > 0) {
512      // add padding attributes
513      isLast = (data.classIndex() == data.numAttributes() - 1);
514      padded = new Vector<Integer>();
515      for (i = 0; i < numAtts; i++) {
516        if (isLast)
517          index = result.numAttributes() - 1;
518        else
519          index = result.numAttributes();
520       
521        result.insertAttributeAt(
522            new Attribute(prefix + "_padding_" + (i+1)),
523            index);
524       
525        // record index
526        padded.add(new Integer(index));
527      }
528     
529      // get padded indices
530      indices = new int[padded.size()];
531      for (i = 0; i < padded.size(); i++)
532        indices[i] = padded.get(i);
533     
534      // determine number of padding attributes
535      switch (m_Padding) {
536        case PADDING_ZERO:
537          for (i = 0; i < result.numInstances(); i++) {
538            for (n = 0; n < indices.length; n++)
539              result.instance(i).setValue(indices[n], 0);
540          }
541          break;
542      }
543    }
544
545    // rename all attributes apart from class
546    data = result;
547    atts = new FastVector();
548    n = 0;
549    for (i = 0; i < data.numAttributes(); i++) {
550      n++;
551      if (i == data.classIndex())
552        atts.addElement((Attribute) data.attribute(i).copy());
553      else
554        atts.addElement(new Attribute(prefix + "_" + n));
555    }
556
557    // create new dataset
558    result = new Instances(data.relationName(), atts, data.numInstances());
559    result.setClassIndex(data.classIndex());
560    for (i = 0; i < data.numInstances(); i++)
561      result.add(new DenseInstance(1.0, data.instance(i).toDoubleArray()));
562   
563    return result;
564  }
565 
566  /**
567   * Determines the output format based on the input format and returns
568   * this. In case the output format cannot be returned immediately, i.e.,
569   * immediateOutputFormat() returns false, then this method will be called
570   * from batchFinished().
571   *
572   * @param inputFormat     the input format to base the output format on
573   * @return                the output format
574   * @throws Exception      in case the determination goes wrong
575   * @see   #hasImmediateOutputFormat()
576   * @see   #batchFinished()
577   */
578  protected Instances determineOutputFormat(Instances inputFormat) 
579    throws Exception {
580
581    return pad(new Instances(inputFormat, 0));
582  }
583 
584  /**
585   * processes the instances using the HAAR algorithm
586   *
587   * @param instances   the data to process
588   * @return            the modified data
589   * @throws Exception  in case the processing goes wrong
590   */
591  protected Instances processHAAR(Instances instances) throws Exception {
592    Instances   result;
593    int         i;
594    int         n;
595    int         j;
596    int         clsIdx;
597    double[]    oldVal;
598    double[]    newVal;
599    int         level;
600    int         length;
601    double[]    clsVal;
602    Attribute   clsAtt;
603
604    clsIdx  = instances.classIndex();
605    clsVal  = null;
606    clsAtt  = null;
607    if (clsIdx > -1) {
608      clsVal  = instances.attributeToDoubleArray(clsIdx);
609      clsAtt  = (Attribute) instances.classAttribute().copy();
610      instances.setClassIndex(-1);
611      instances.deleteAttributeAt(clsIdx);
612    }
613    result = new Instances(instances, 0);
614    level  = (int) StrictMath.ceil(
615                        StrictMath.log(instances.numAttributes())
616                        / StrictMath.log(2.0));
617   
618    for (i = 0; i < instances.numInstances(); i++) {
619      oldVal = instances.instance(i).toDoubleArray();
620      newVal = new double[oldVal.length];
621     
622      for (n = level; n > 0; n--) {
623        length = (int) StrictMath.pow(2, n - 1);
624       
625        for (j = 0; j < length; j++) {
626          newVal[j]          = (oldVal[j*2] + oldVal[j*2 + 1]) / StrictMath.sqrt(2);
627          newVal[j + length] = (oldVal[j*2] - oldVal[j*2 + 1]) / StrictMath.sqrt(2);
628        }
629       
630        System.arraycopy(newVal, 0, oldVal, 0, newVal.length);
631      }
632     
633      // add new transformed instance
634      result.add(new DenseInstance(1, newVal));
635    }
636
637    // add class again
638    if (clsIdx > -1) {
639      result.insertAttributeAt(clsAtt, clsIdx);
640      result.setClassIndex(clsIdx);
641      for (i = 0; i < clsVal.length; i++)
642        result.instance(i).setClassValue(clsVal[i]);
643    }
644   
645    return result;
646  }
647
648  /**
649   * Returns the Capabilities of this filter.
650   *
651   * @return            the capabilities of this object
652   * @see               Capabilities
653   */
654  public Capabilities getCapabilities() {
655    Capabilities result = super.getCapabilities();
656    result.disableAll();
657
658    // attribute
659    result.enable(Capability.NUMERIC_ATTRIBUTES);
660    result.enable(Capability.DATE_ATTRIBUTES);
661    result.enable(Capability.MISSING_VALUES);
662   
663    // class
664    result.enable(Capability.NOMINAL_CLASS);
665    result.enable(Capability.NUMERIC_CLASS);
666    result.enable(Capability.DATE_CLASS);
667    result.enable(Capability.NO_CLASS);
668   
669    return result;
670  }
671 
672  /**
673   * Processes the given data (may change the provided dataset) and returns
674   * the modified version. This method is called in batchFinished().
675   *
676   * @param instances   the data to process
677   * @return            the modified data
678   * @throws Exception  in case the processing goes wrong
679   * @see               #batchFinished()
680   */
681  protected Instances process(Instances instances) throws Exception {
682    if (!isFirstBatchDone())
683      m_Filter.setInputFormat(instances);
684    instances = Filter.useFilter(instances, m_Filter);
685   
686    switch (m_Algorithm) {
687      case ALGORITHM_HAAR:
688        return processHAAR(pad(instances));
689      default:
690        throw new IllegalStateException(
691            "Algorithm type '" + m_Algorithm + "' is not recognized!");
692    }
693  }
694 
695  /**
696   * Returns the revision string.
697   *
698   * @return            the revision
699   */
700  public String getRevision() {
701    return RevisionUtils.extract("$Revision: 5987 $");
702  }
703
704  /**
705   * runs the filter with the given arguments
706   *
707   * @param args      the commandline arguments
708   */
709  public static void main(String[] args) {
710    runFilter(new Wavelet(), args);
711  }
712}
Note: See TracBrowser for help on using the repository browser.