~ubuntu-branches/ubuntu/precise/weka/precise

« back to all changes in this revision

Viewing changes to weka/classifiers/misc/HyperPipes.java

  • Committer: Bazaar Package Importer
  • Author(s): Soeren Sonnenburg
  • Date: 2008-02-24 09:18:45 UTC
  • Revision ID: james.westby@ubuntu.com-20080224091845-1l8zy6fm6xipbzsr
Tags: upstream-3.5.7+tut1
ImportĀ upstreamĀ versionĀ 3.5.7+tut1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 *    HyperPipes.java
 
19
 *    Copyright (C) 2002 University of Waikato, Hamilton, New Zealand
 
20
 *
 
21
 */
 
22
 
 
23
package weka.classifiers.misc;
 
24
 
 
25
import weka.classifiers.Classifier;
 
26
import weka.core.Attribute;
 
27
import weka.core.Capabilities;
 
28
import weka.core.Instance;
 
29
import weka.core.Instances;
 
30
import weka.core.UnsupportedAttributeTypeException;
 
31
import weka.core.Utils;
 
32
import weka.core.Capabilities.Capability;
 
33
 
 
34
import java.io.Serializable;
 
35
 
 
36
/**
 
37
 <!-- globalinfo-start -->
 
38
 * Class implementing a HyperPipe classifier. For each category a HyperPipe is constructed that contains all points of that category (essentially records the attribute bounds observed for each category). Test instances are classified according to the category that "most contains the instance".<br/>
 
39
 * Does not handle numeric class, or missing values in test cases. Extremely simple algorithm, but has the advantage of being extremely fast, and works quite well when you have "smegloads" of attributes.
 
40
 * <p/>
 
41
 <!-- globalinfo-end -->
 
42
 *
 
43
 <!-- options-start -->
 
44
 * Valid options are: <p/>
 
45
 * 
 
46
 * <pre> -D
 
47
 *  If set, classifier is run in debug mode and
 
48
 *  may output additional info to the console</pre>
 
49
 * 
 
50
 <!-- options-end -->
 
51
 *
 
52
 * @author Lucio de Souza Coelho (lucio@intelligenesis.net)
 
53
 * @author Len Trigg (len@reeltwo.com)
 
54
 * @version $Revision: 1.20 $
 
55
 */ 
 
56
public class HyperPipes 
 
57
  extends Classifier {
 
58
 
 
59
  /** for serialization */
 
60
  static final long serialVersionUID = -7527596632268975274L;
 
61
  
 
62
  /** The index of the class attribute */
 
63
  protected int m_ClassIndex;
 
64
 
 
65
  /** The structure of the training data */
 
66
  protected Instances m_Instances;
 
67
 
 
68
  /** Stores the HyperPipe for each class */
 
69
  protected HyperPipe [] m_HyperPipes;
 
70
 
 
71
  /** a ZeroR model in case no model can be built from the data */
 
72
  protected Classifier m_ZeroR;
 
73
    
 
74
  /**
 
75
   * Returns a string describing classifier
 
76
   * @return a description suitable for
 
77
   * displaying in the explorer/experimenter gui
 
78
   */
 
79
  public String globalInfo() {
 
80
 
 
81
    return "Class implementing a HyperPipe classifier. For each category a "
 
82
    +  "HyperPipe is constructed that contains all points of that category "
 
83
      + "(essentially records the attribute bounds observed for each category). "
 
84
      + "Test instances are classified according to the category that \"most "
 
85
      + "contains the instance\".\n" 
 
86
      + "Does not handle numeric class, or missing values in test cases. Extremely "
 
87
      + "simple algorithm, but has the advantage of being extremely fast, and "
 
88
      + "works quite well when you have \"smegloads\" of attributes.";
 
89
  }
 
90
 
 
91
  /**
 
92
   * Represents an n-dimensional structure that bounds all instances 
 
93
   * passed to it (generally all of a given class value).
 
94
   */
 
95
  class HyperPipe 
 
96
    implements Serializable {
 
97
    
 
98
    /** for serialization */
 
99
    static final long serialVersionUID = 3972254260367902025L;
 
100
 
 
101
    /** Contains the numeric bounds of all instances in the HyperPipe */
 
102
    protected double [][] m_NumericBounds;
 
103
 
 
104
    /** Contains the nominal bounds of all instances in the HyperPipe */
 
105
    protected boolean [][] m_NominalBounds;
 
106
 
 
107
    /**
 
108
     * Creates the HyperPipe as the n-dimensional parallel-piped 
 
109
     * with minimum volume containing all the points in
 
110
     * pointSet.
 
111
     *
 
112
     * @param instances all instances belonging to the same class
 
113
     * @throws Exception if missing values are found
 
114
     */
 
115
    public HyperPipe(Instances instances) throws Exception {
 
116
      
 
117
      m_NumericBounds = new double [instances.numAttributes()][];
 
118
      m_NominalBounds = new boolean [instances.numAttributes()][];
 
119
 
 
120
      for (int i = 0; i < instances.numAttributes(); i++) {
 
121
        switch (instances.attribute(i).type()) {
 
122
        case Attribute.NUMERIC:
 
123
          m_NumericBounds[i] = new double [2];
 
124
          m_NumericBounds[i][0] = Double.POSITIVE_INFINITY;
 
125
          m_NumericBounds[i][1] = Double.NEGATIVE_INFINITY;
 
126
          break;
 
127
        case Attribute.NOMINAL:
 
128
          m_NominalBounds[i] = new boolean [instances.attribute(i).numValues()];
 
129
          break;
 
130
        default:
 
131
          throw new UnsupportedAttributeTypeException("Cannot process string attributes!");
 
132
        }
 
133
      }
 
134
 
 
135
      for (int i = 0; i < instances.numInstances(); i++) {
 
136
        addInstance(instances.instance(i));
 
137
      }
 
138
    }
 
139
 
 
140
 
 
141
    /**
 
142
     * Updates the bounds arrays with a single instance. Missing values
 
143
     * are ignored (i.e. they don't change the bounds for that attribute)
 
144
     *
 
145
     * @param instance the instance
 
146
     * @throws Exception if any missing values are encountered
 
147
     */
 
148
    public void addInstance(Instance instance) throws Exception {
 
149
 
 
150
      for (int j = 0; j < instance.numAttributes(); j++) {
 
151
        if ((j != m_ClassIndex) && (!instance.isMissing(j))) {
 
152
 
 
153
          double current = instance.value(j);
 
154
 
 
155
          if (m_NumericBounds[j] != null) { // i.e. a numeric attribute
 
156
            if (current < m_NumericBounds[j][0])
 
157
              m_NumericBounds[j][0] = current;
 
158
            if (current > m_NumericBounds[j][1])
 
159
              m_NumericBounds[j][1] = current;
 
160
 
 
161
          } else { // i.e. a nominal attribute
 
162
            m_NominalBounds[j][(int) current] = true;
 
163
          }
 
164
        }
 
165
      }
 
166
    }
 
167
 
 
168
 
 
169
    /**
 
170
     * Returns the fraction of the dimensions of a given instance with
 
171
     * values lying within the corresponding bounds of the HyperPipe.
 
172
     *
 
173
     * @param instance the instance
 
174
     * @return the fraction of dimensions
 
175
     * @throws Exception if any missing values are encountered
 
176
     */
 
177
    public double partialContains(Instance instance) throws Exception {
 
178
      
 
179
      int count = 0;
 
180
      for (int i = 0; i < instance.numAttributes(); i++) {
 
181
 
 
182
        if (i == m_ClassIndex) {
 
183
          continue;
 
184
        }
 
185
        if (instance.isMissing(i)) {
 
186
          continue;
 
187
        }
 
188
 
 
189
        double current = instance.value(i);
 
190
 
 
191
        if (m_NumericBounds[i] != null) { // i.e. a numeric attribute
 
192
          if ((current >= m_NumericBounds[i][0]) 
 
193
              && (current <= m_NumericBounds[i][1])) {
 
194
            count++;
 
195
          }
 
196
        } else { // i.e. a nominal attribute
 
197
          if (m_NominalBounds[i][(int) current]) {
 
198
            count++;
 
199
          }
 
200
        }
 
201
      }
 
202
 
 
203
      return ((double)count) / (instance.numAttributes() - 1);
 
204
    }
 
205
  }
 
206
 
 
207
 
 
208
  /**
 
209
   * Returns default capabilities of the classifier.
 
210
   *
 
211
   * @return      the capabilities of this classifier
 
212
   */
 
213
  public Capabilities getCapabilities() {
 
214
    Capabilities result = super.getCapabilities();
 
215
 
 
216
    // attributes
 
217
    result.enable(Capability.NOMINAL_ATTRIBUTES);
 
218
    result.enable(Capability.NUMERIC_ATTRIBUTES);
 
219
    result.enable(Capability.DATE_ATTRIBUTES);
 
220
    result.enable(Capability.MISSING_VALUES);
 
221
 
 
222
    // class
 
223
    result.enable(Capability.NOMINAL_CLASS);
 
224
    result.enable(Capability.MISSING_CLASS_VALUES);
 
225
 
 
226
    // instances
 
227
    result.setMinimumNumberInstances(0);
 
228
    
 
229
    return result;
 
230
  }
 
231
 
 
232
  /**
 
233
   * Generates the classifier.
 
234
   *
 
235
   * @param instances set of instances serving as training data 
 
236
   * @throws Exception if the classifier has not been generated successfully
 
237
   */
 
238
  public void buildClassifier(Instances instances) throws Exception {
 
239
    
 
240
    // can classifier handle the data?
 
241
    getCapabilities().testWithFail(instances);
 
242
 
 
243
    // remove instances with missing class
 
244
    instances = new Instances(instances);
 
245
    instances.deleteWithMissingClass();
 
246
    
 
247
    // only class? -> build ZeroR model
 
248
    if (instances.numAttributes() == 1) {
 
249
      System.err.println(
 
250
          "Cannot build model (only class attribute present in data!), "
 
251
          + "using ZeroR model instead!");
 
252
      m_ZeroR = new weka.classifiers.rules.ZeroR();
 
253
      m_ZeroR.buildClassifier(instances);
 
254
      return;
 
255
    }
 
256
    else {
 
257
      m_ZeroR = null;
 
258
    }
 
259
    
 
260
    m_ClassIndex = instances.classIndex();
 
261
    m_Instances = new Instances(instances, 0); // Copy the structure for ref
 
262
 
 
263
    // Create the HyperPipe for each class
 
264
    m_HyperPipes = new HyperPipe [instances.numClasses()];
 
265
    for (int i = 0; i < m_HyperPipes.length; i++) {
 
266
      m_HyperPipes[i] = new HyperPipe(new Instances(instances, 0));
 
267
    }
 
268
 
 
269
    // Add the instances
 
270
    for (int i = 0; i < instances.numInstances(); i++) {
 
271
      updateClassifier(instances.instance(i));
 
272
    }
 
273
  }
 
274
 
 
275
 
 
276
  /**
 
277
   * Updates the classifier.
 
278
   *
 
279
   * @param instance the instance to be put into the classifier
 
280
   * @throws Exception if the instance could not be included successfully
 
281
   */
 
282
  public void updateClassifier(Instance instance) throws Exception {
 
283
  
 
284
    if (instance.classIsMissing()) {
 
285
      return;
 
286
    }
 
287
    m_HyperPipes[(int) instance.classValue()].addInstance(instance);
 
288
  }
 
289
 
 
290
 
 
291
  /**
 
292
   * Classifies the given test instance.
 
293
   *
 
294
   * @param instance the instance to be classified
 
295
   * @return the predicted class for the instance 
 
296
   * @throws Exception if the instance can't be classified
 
297
   */
 
298
  public double [] distributionForInstance(Instance instance) throws Exception {
 
299
        
 
300
    // default model?
 
301
    if (m_ZeroR != null) {
 
302
      return m_ZeroR.distributionForInstance(instance);
 
303
    }
 
304
    
 
305
    double [] dist = new double[m_HyperPipes.length];
 
306
 
 
307
    for (int j = 0; j < m_HyperPipes.length; j++) {
 
308
      dist[j] = m_HyperPipes[j].partialContains(instance);
 
309
    }
 
310
 
 
311
    double sum = Utils.sum(dist);
 
312
    if (sum <= 0) {
 
313
      for (int j = 0; j < dist.length; j++) {
 
314
        dist[j] = 1.0 / (double)dist.length;
 
315
      }
 
316
      return dist;
 
317
    } else {
 
318
      Utils.normalize(dist, sum);
 
319
      return dist;
 
320
    }
 
321
  }
 
322
 
 
323
 
 
324
  /**
 
325
   * Returns a description of this classifier.
 
326
   *
 
327
   * @return a description of this classifier as a string.
 
328
   */
 
329
  public String toString() {
 
330
 
 
331
    // only ZeroR model?
 
332
    if (m_ZeroR != null) {
 
333
      StringBuffer buf = new StringBuffer();
 
334
      buf.append(this.getClass().getName().replaceAll(".*\\.", "") + "\n");
 
335
      buf.append(this.getClass().getName().replaceAll(".*\\.", "").replaceAll(".", "=") + "\n\n");
 
336
      buf.append("Warning: No model could be built, hence ZeroR model is used:\n\n");
 
337
      buf.append(m_ZeroR.toString());
 
338
      return buf.toString();
 
339
    }
 
340
    
 
341
    if (m_HyperPipes == null) {
 
342
      return ("HyperPipes classifier");
 
343
    }
 
344
 
 
345
    StringBuffer text = new StringBuffer("HyperPipes classifier\n");
 
346
 
 
347
    /* Perhaps print out the bounds for each HyperPipe.
 
348
    for (int i = 0; i < m_HyperPipes.length; i++) {
 
349
      text.append("HyperPipe for class: " 
 
350
                  + m_Instances.attribute(m_ClassIndex).value(i) + "\n");
 
351
      text.append(m_HyperPipes[i] + "\n\n");
 
352
    }
 
353
    */
 
354
 
 
355
    return text.toString();
 
356
  }
 
357
 
 
358
 
 
359
  /**
 
360
   * Main method for testing this class.
 
361
   *
 
362
   * @param argv should contain command line arguments for evaluation
 
363
   * (see Evaluation).
 
364
   */
 
365
  public static void main(String [] argv) {
 
366
    runClassifier(new HyperPipes(), argv);
 
367
  }
 
368
}