source: src/main/java/weka/classifiers/lazy/KStar.java @ 14

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

Import di weka.

File size: 21.4 KB
RevLine 
[4]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 *    KStar.java
19 *    Copyright (C) 1995-97 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.classifiers.lazy;
24
25import weka.classifiers.Classifier;
26import weka.classifiers.AbstractClassifier;
27import weka.classifiers.UpdateableClassifier;
28import weka.classifiers.lazy.kstar.KStarCache;
29import weka.classifiers.lazy.kstar.KStarConstants;
30import weka.classifiers.lazy.kstar.KStarNominalAttribute;
31import weka.classifiers.lazy.kstar.KStarNumericAttribute;
32import weka.core.Attribute;
33import weka.core.Capabilities;
34import weka.core.Instance;
35import weka.core.Instances;
36import weka.core.Option;
37import weka.core.RevisionUtils;
38import weka.core.SelectedTag;
39import weka.core.Tag;
40import weka.core.TechnicalInformation;
41import weka.core.TechnicalInformationHandler;
42import weka.core.Utils;
43import weka.core.Capabilities.Capability;
44import weka.core.TechnicalInformation.Field;
45import weka.core.TechnicalInformation.Type;
46
47import java.util.Enumeration;
48import java.util.Random;
49import java.util.Vector;
50
51/**
52 <!-- globalinfo-start -->
53 * K* is an instance-based classifier, that is the class of a test instance is based upon the class of those training instances similar to it, as determined by some similarity function.  It differs from other instance-based learners in that it uses an entropy-based distance function.<br/>
54 * <br/>
55 * For more information on K*, see<br/>
56 * <br/>
57 * John G. Cleary, Leonard E. Trigg: K*: An Instance-based Learner Using an Entropic Distance Measure. In: 12th International Conference on Machine Learning, 108-114, 1995.
58 * <p/>
59 <!-- globalinfo-end -->
60 *
61 <!-- technical-bibtex-start -->
62 * BibTeX:
63 * <pre>
64 * &#64;inproceedings{Cleary1995,
65 *    author = {John G. Cleary and Leonard E. Trigg},
66 *    booktitle = {12th International Conference on Machine Learning},
67 *    pages = {108-114},
68 *    title = {K*: An Instance-based Learner Using an Entropic Distance Measure},
69 *    year = {1995}
70 * }
71 * </pre>
72 * <p/>
73 <!-- technical-bibtex-end -->
74 *
75 <!-- options-start -->
76 * Valid options are: <p/>
77 *
78 * <pre> -B &lt;num&gt;
79 *  Manual blend setting (default 20%)
80 * </pre>
81 *
82 * <pre> -E
83 *  Enable entropic auto-blend setting (symbolic class only)
84 * </pre>
85 *
86 * <pre> -M &lt;char&gt;
87 *  Specify the missing value treatment mode (default a)
88 *  Valid options are: a(verage), d(elete), m(axdiff), n(ormal)
89 * </pre>
90 *
91 <!-- options-end -->
92 *
93 * @author Len Trigg (len@reeltwo.com)
94 * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz) - Java port
95 * @version $Revision: 5928 $
96 */
97public class KStar 
98  extends AbstractClassifier
99  implements KStarConstants, UpdateableClassifier, TechnicalInformationHandler {
100
101  /** for serialization */
102  static final long serialVersionUID = 332458330800479083L;
103 
104  /** The training instances used for classification. */
105  protected Instances m_Train; 
106
107  /** The number of instances in the dataset */
108  protected int m_NumInstances;
109
110  /** The number of class values */
111  protected int m_NumClasses;
112
113  /** The number of attributes */
114  protected int m_NumAttributes;
115
116  /** The class attribute type */
117  protected int m_ClassType;
118
119  /** Table of random class value colomns */
120  protected int [][] m_RandClassCols;
121
122  /** Flag turning on and off the computation of random class colomns */
123  protected int m_ComputeRandomCols = ON;
124
125  /** Flag turning on and off the initialisation of config variables */
126  protected int m_InitFlag = ON;
127
128  /**
129   * A custom data structure for caching distinct attribute values
130   * and their scale factor or stop parameter.
131   */
132  protected KStarCache [] m_Cache;
133
134  /** missing value treatment */
135  protected int m_MissingMode = M_AVERAGE;
136
137  /** 0 = use specified blend, 1 = entropic blend setting */
138  protected int m_BlendMethod = B_SPHERE;
139
140  /** default sphere of influence blend setting */
141  protected int m_GlobalBlend = 20;
142
143  /** Define possible missing value handling methods */
144  public static final Tag [] TAGS_MISSING = {
145    new Tag(M_DELETE, "Ignore the instances with missing values"),
146    new Tag(M_MAXDIFF, "Treat missing values as maximally different"),
147    new Tag(M_NORMAL, "Normalize over the attributes"),
148    new Tag(M_AVERAGE, "Average column entropy curves")
149      };
150   
151  /**
152   * Returns a string describing classifier
153   * @return a description suitable for
154   * displaying in the explorer/experimenter gui
155   */
156  public String globalInfo() {
157
158    return "K* is an instance-based classifier, that is the class of a test "
159      + "instance is based upon the class of those training instances "
160      + "similar to it, as determined by some similarity function.  It differs "
161      + "from other instance-based learners in that it uses an entropy-based "
162      + "distance function.\n\n"
163      + "For more information on K*, see\n\n"
164      + getTechnicalInformation().toString();
165  }
166
167  /**
168   * Returns an instance of a TechnicalInformation object, containing
169   * detailed information about the technical background of this class,
170   * e.g., paper reference or book this class is based on.
171   *
172   * @return the technical information about this class
173   */
174  public TechnicalInformation getTechnicalInformation() {
175    TechnicalInformation        result;
176   
177    result = new TechnicalInformation(Type.INPROCEEDINGS);
178    result.setValue(Field.AUTHOR, "John G. Cleary and Leonard E. Trigg");
179    result.setValue(Field.TITLE, "K*: An Instance-based Learner Using an Entropic Distance Measure");
180    result.setValue(Field.BOOKTITLE, "12th International Conference on Machine Learning");
181    result.setValue(Field.YEAR, "1995");
182    result.setValue(Field.PAGES, "108-114");
183   
184    return result;
185  }
186
187  /**
188   * Returns default capabilities of the classifier.
189   *
190   * @return      the capabilities of this classifier
191   */
192  public Capabilities getCapabilities() {
193    Capabilities result = super.getCapabilities();
194    result.disableAll();
195
196    // attributes
197    result.enable(Capability.NOMINAL_ATTRIBUTES);
198    result.enable(Capability.NUMERIC_ATTRIBUTES);
199    result.enable(Capability.DATE_ATTRIBUTES);
200    result.enable(Capability.MISSING_VALUES);
201
202    // class
203    result.enable(Capability.NOMINAL_CLASS);
204    result.enable(Capability.NUMERIC_CLASS);
205    result.enable(Capability.DATE_CLASS);
206    result.enable(Capability.MISSING_CLASS_VALUES);
207
208    // instances
209    result.setMinimumNumberInstances(0);
210   
211    return result;
212  }
213
214  /**
215   * Generates the classifier.
216   *
217   * @param instances set of instances serving as training data
218   * @throws Exception if the classifier has not been generated successfully
219   */
220  public void buildClassifier(Instances instances) throws Exception {
221    String debug = "(KStar.buildClassifier) ";
222
223    // can classifier handle the data?
224    getCapabilities().testWithFail(instances);
225
226    // remove instances with missing class
227    instances = new Instances(instances);
228    instances.deleteWithMissingClass();
229   
230    m_Train = new Instances(instances, 0, instances.numInstances());
231
232    // initializes class attributes ** java-speaking! :-) **
233    init_m_Attributes();
234  }
235 
236  /**
237   * Adds the supplied instance to the training set
238   *
239   * @param instance the instance to add
240   * @throws Exception if instance could not be incorporated successfully
241   */
242  public void updateClassifier(Instance instance) throws Exception {
243    String debug = "(KStar.updateClassifier) ";
244
245    if (m_Train.equalHeaders(instance.dataset()) == false)
246      throw new Exception("Incompatible instance types\n" + m_Train.equalHeadersMsg(instance.dataset()));
247    if ( instance.classIsMissing() )
248      return;
249    m_Train.add(instance);
250    // update relevant attributes ...
251    update_m_Attributes();
252  }
253
254  /**
255   * Calculates the class membership probabilities for the given test instance.
256   *
257   * @param instance the instance to be classified
258   * @return predicted class probability distribution
259   * @throws Exception if an error occurred during the prediction
260   */
261  public double [] distributionForInstance(Instance instance) throws Exception {
262
263    String debug = "(KStar.distributionForInstance) ";
264    double transProb = 0.0, temp = 0.0;
265    double [] classProbability = new double[m_NumClasses];
266    double [] predictedValue = new double[1];
267
268    // initialization ...
269    for (int i=0; i<classProbability.length; i++) {
270      classProbability[i] = 0.0;
271    }
272    predictedValue[0] = 0.0;
273    if (m_InitFlag == ON) {
274        // need to compute them only once and will be used for all instances.
275        // We are doing this because the evaluation module controls the calls.
276      if (m_BlendMethod == B_ENTROPY) {
277        generateRandomClassColomns();
278      }
279      m_Cache = new KStarCache[m_NumAttributes];
280      for (int i=0; i<m_NumAttributes;i++) {
281        m_Cache[i] = new KStarCache();
282      }
283      m_InitFlag = OFF;
284      //      System.out.println("Computing...");
285    }
286    // init done.
287    Instance trainInstance;
288    Enumeration enu = m_Train.enumerateInstances();
289    while ( enu.hasMoreElements() ) {
290      trainInstance = (Instance)enu.nextElement();
291      transProb = instanceTransformationProbability(instance, trainInstance);     
292      switch ( m_ClassType )
293        {
294        case Attribute.NOMINAL:
295          classProbability[(int)trainInstance.classValue()] += transProb;
296          break;
297        case Attribute.NUMERIC:
298          predictedValue[0] += transProb * trainInstance.classValue();
299          temp += transProb;
300          break;
301        }
302    }
303    if (m_ClassType == Attribute.NOMINAL) {
304      double sum = Utils.sum(classProbability);
305      if (sum <= 0.0)
306        for (int i=0; i<classProbability.length; i++)
307          classProbability[i] = (double) 1/ (double) m_NumClasses;
308      else Utils.normalize(classProbability, sum);
309      return classProbability;
310    }
311    else {
312      predictedValue[0] = (temp != 0) ? predictedValue[0] / temp : 0.0;
313      return predictedValue;
314    }
315  }
316
317  /**
318   * Calculate the probability of the first instance transforming into the
319   * second instance:
320   * the probability is the product of the transformation probabilities of
321   * the attributes normilized over the number of instances used.
322   *
323   * @param first the test instance
324   * @param second the train instance
325   * @return transformation probability value
326   */
327  private double instanceTransformationProbability(Instance first, 
328                                                   Instance second) {
329    String debug = "(KStar.instanceTransformationProbability) ";
330    double transProb = 1.0;
331    int numMissAttr = 0;
332    for (int i = 0; i < m_NumAttributes; i++) {
333      if (i == m_Train.classIndex()) {
334        continue; // ignore class attribute
335      }
336      if (first.isMissing(i)) { // test instance attribute value is missing
337        numMissAttr++;
338        continue;
339      }
340      transProb *= attrTransProb(first, second, i);
341      // normilize for missing values
342      if (numMissAttr != m_NumAttributes) {
343        transProb = Math.pow(transProb, (double)m_NumAttributes / 
344                             (m_NumAttributes - numMissAttr));
345      }
346      else { // weird case!
347        transProb = 0.0;
348      }
349    }
350    // normilize for the train dataset
351     return transProb / m_NumInstances;
352  }
353
354  /**
355   * Calculates the transformation probability of the indexed test attribute
356   * to the indexed train attribute.
357   *
358   * @param first the test instance.
359   * @param second the train instance.
360   * @param col the index of the attribute in the instance.
361   * @return the value of the transformation probability.
362   */
363  private double attrTransProb(Instance first, Instance second, int col) {
364    String debug = "(KStar.attrTransProb)";
365    double transProb = 0.0;
366    KStarNominalAttribute ksNominalAttr;
367    KStarNumericAttribute ksNumericAttr;
368    switch ( m_Train.attribute(col).type() )
369      {
370      case Attribute.NOMINAL:
371        ksNominalAttr = new KStarNominalAttribute(first, second, col, m_Train, 
372                                                  m_RandClassCols, 
373                                                  m_Cache[col]);
374        ksNominalAttr.setOptions(m_MissingMode, m_BlendMethod, m_GlobalBlend);
375        transProb = ksNominalAttr.transProb();
376        ksNominalAttr = null;
377        break;
378
379      case Attribute.NUMERIC:
380        ksNumericAttr = new KStarNumericAttribute(first, second, col, 
381                                                  m_Train, m_RandClassCols, 
382                                                  m_Cache[col]);
383        ksNumericAttr.setOptions(m_MissingMode, m_BlendMethod, m_GlobalBlend);
384        transProb = ksNumericAttr.transProb();
385        ksNumericAttr = null;
386        break;
387      }
388    return transProb;
389  }
390   
391  /**
392   * Returns the tip text for this property
393   * @return tip text for this property suitable for
394   * displaying in the explorer/experimenter gui
395   */
396  public String missingModeTipText() {
397    return "Determines how missing attribute values are treated.";
398  }
399
400  /**
401   * Gets the method to use for handling missing values. Will be one of
402   * M_NORMAL, M_AVERAGE, M_MAXDIFF or M_DELETE.
403   *
404   * @return the method used for handling missing values.
405   */
406  public SelectedTag getMissingMode() {
407
408    return new SelectedTag(m_MissingMode, TAGS_MISSING);
409  }
410 
411  /**
412   * Sets the method to use for handling missing values. Values other than
413   * M_NORMAL, M_AVERAGE, M_MAXDIFF and M_DELETE will be ignored.
414   *
415   * @param newMode the method to use for handling missing values.
416   */
417  public void setMissingMode(SelectedTag newMode) {
418   
419    if (newMode.getTags() == TAGS_MISSING) {
420      m_MissingMode = newMode.getSelectedTag().getID();
421    }
422  }
423
424  /**
425   * Returns an enumeration describing the available options.
426   *
427   * @return an enumeration of all the available options.
428   */
429  public Enumeration listOptions() {
430
431    Vector optVector = new Vector( 3 );
432    optVector.addElement(new Option(
433              "\tManual blend setting (default 20%)\n",
434              "B", 1, "-B <num>"));
435    optVector.addElement(new Option(
436              "\tEnable entropic auto-blend setting (symbolic class only)\n",
437              "E", 0, "-E"));
438    optVector.addElement(new Option(
439              "\tSpecify the missing value treatment mode (default a)\n"
440              +"\tValid options are: a(verage), d(elete), m(axdiff), n(ormal)\n",
441              "M", 1,"-M <char>"));
442    return optVector.elements();
443  }
444   
445  /**
446   * Returns the tip text for this property
447   * @return tip text for this property suitable for
448   * displaying in the explorer/experimenter gui
449   */
450  public String globalBlendTipText() {
451    return "The parameter for global blending. Values are restricted to [0,100].";
452  }
453
454  /**
455   * Set the global blend parameter
456   * @param b the value for global blending
457   */
458  public void setGlobalBlend(int b) {
459     m_GlobalBlend = b;
460      if ( m_GlobalBlend > 100 ) {
461        m_GlobalBlend = 100;
462      }
463      if ( m_GlobalBlend < 0 ) {
464        m_GlobalBlend = 0;
465      }
466  }
467
468  /**
469   * Get the value of the global blend parameter
470   * @return the value of the global blend parameter
471   */
472  public int getGlobalBlend() {
473    return m_GlobalBlend;
474  }
475   
476  /**
477   * Returns the tip text for this property
478   * @return tip text for this property suitable for
479   * displaying in the explorer/experimenter gui
480   */
481  public String entropicAutoBlendTipText() {
482    return "Whether entropy-based blending is to be used.";
483  }
484
485  /**
486   * Set whether entropic blending is to be used.
487   * @param e true if entropic blending is to be used
488   */
489  public void setEntropicAutoBlend(boolean e) {
490    if (e) {
491      m_BlendMethod = B_ENTROPY;
492    } else {
493      m_BlendMethod = B_SPHERE;
494    }
495  }
496
497  /**
498   * Get whether entropic blending being used
499   * @return true if entropic blending is used
500   */
501  public boolean getEntropicAutoBlend() {
502    if (m_BlendMethod == B_ENTROPY) {
503      return true;
504    }
505
506    return false;
507  }
508
509  /**
510   * Parses a given list of options. <p/>
511   *
512   <!-- options-start -->
513   * Valid options are: <p/>
514   *
515   * <pre> -B &lt;num&gt;
516   *  Manual blend setting (default 20%)
517   * </pre>
518   *
519   * <pre> -E
520   *  Enable entropic auto-blend setting (symbolic class only)
521   * </pre>
522   *
523   * <pre> -M &lt;char&gt;
524   *  Specify the missing value treatment mode (default a)
525   *  Valid options are: a(verage), d(elete), m(axdiff), n(ormal)
526   * </pre>
527   *
528   <!-- options-end -->
529   *
530   * @param options the list of options as an array of strings
531   * @throws Exception if an option is not supported
532   */
533  public void setOptions(String[] options) throws Exception {
534    String debug = "(KStar.setOptions)";
535    String blendStr = Utils.getOption('B', options);
536    if (blendStr.length() != 0) {
537      setGlobalBlend(Integer.parseInt(blendStr));
538    }
539
540    setEntropicAutoBlend(Utils.getFlag('E', options));
541   
542    String missingModeStr = Utils.getOption('M', options);
543    if (missingModeStr.length() != 0) {
544      switch ( missingModeStr.charAt(0) ) {
545      case 'a':
546        setMissingMode(new SelectedTag(M_AVERAGE, TAGS_MISSING));
547        break;
548      case 'd':
549        setMissingMode(new SelectedTag(M_DELETE, TAGS_MISSING));
550        break;
551      case 'm':
552        setMissingMode(new SelectedTag(M_MAXDIFF, TAGS_MISSING));
553        break;
554      case 'n':
555        setMissingMode(new SelectedTag(M_NORMAL, TAGS_MISSING));
556        break;
557      default:
558        setMissingMode(new SelectedTag(M_AVERAGE, TAGS_MISSING));
559      }
560    }
561    Utils.checkForRemainingOptions(options);
562  }
563
564
565  /**
566   * Gets the current settings of K*.
567   *
568   * @return an array of strings suitable for passing to setOptions()
569   */
570  public String [] getOptions() {
571    // -B <num> -E -M <char>
572    String [] options = new String [ 5 ];
573    int itr = 0;
574    options[itr++] = "-B";
575    options[itr++] = "" + m_GlobalBlend;
576
577    if (getEntropicAutoBlend()) {
578      options[itr++] = "-E";
579    }
580
581    options[itr++] = "-M";
582    if (m_MissingMode == M_AVERAGE) {
583      options[itr++] = "" + "a";
584    }
585    else if (m_MissingMode == M_DELETE) {
586      options[itr++] = "" + "d";
587    }
588    else if (m_MissingMode == M_MAXDIFF) {
589      options[itr++] = "" + "m";
590    }
591    else if (m_MissingMode == M_NORMAL) {
592      options[itr++] = "" + "n";
593    }
594    while (itr < options.length) {
595      options[itr++] = "";
596    }
597    return options;
598  }
599
600  /**
601   * Returns a description of this classifier.
602   *
603   * @return a description of this classifier as a string.
604   */
605  public String toString() {
606    StringBuffer st = new StringBuffer();
607    st.append("KStar Beta Verion (0.1b).\n"
608              +"Copyright (c) 1995-97 by Len Trigg (trigg@cs.waikato.ac.nz).\n"
609              +"Java port to Weka by Abdelaziz Mahoui "
610              +"(am14@cs.waikato.ac.nz).\n\nKStar options : ");
611    String [] ops = getOptions();
612    for (int i=0;i<ops.length;i++) {
613      st.append(ops[i]+' ');
614    }
615    return st.toString();
616  }
617 
618  /**
619   * Main method for testing this class.
620   *
621   * @param argv should contain command line options (see setOptions)
622   */
623  public static void main(String [] argv) {
624    runClassifier(new KStar(), argv);
625  }
626
627  /**
628   * Initializes the m_Attributes of the class.
629   */
630  private void init_m_Attributes() {
631    try {
632      m_NumInstances = m_Train.numInstances();
633      m_NumClasses = m_Train.numClasses();
634      m_NumAttributes = m_Train.numAttributes();
635      m_ClassType = m_Train.classAttribute().type();
636      m_InitFlag = ON;
637    } catch(Exception e) {
638      e.printStackTrace();
639    }
640  }
641
642  /**
643   * Updates the m_attributes of the class.
644   */
645  private void update_m_Attributes() {
646    m_NumInstances = m_Train.numInstances();
647    m_InitFlag = ON;
648  }
649
650  /**
651   * Note: for Nominal Class Only!
652   * Generates a set of random versions of the class colomn.
653   */
654  private void generateRandomClassColomns() {
655    String debug = "(KStar.generateRandomClassColomns)";
656    Random generator = new Random(42);
657    //    Random generator = new Random();
658    m_RandClassCols = new int [NUM_RAND_COLS+1][];
659    int [] classvals = classValues();
660    for (int i=0; i < NUM_RAND_COLS; i++) {
661      // generate a randomized version of the class colomn
662      m_RandClassCols[i] = randomize(classvals, generator);
663    }
664    // original colomn is preserved in colomn NUM_RAND_COLS
665    m_RandClassCols[NUM_RAND_COLS] = classvals;
666  }
667
668  /**
669   * Note: for Nominal Class Only!
670   * Returns an array of the class values
671   *
672   * @return an array of class values
673   */
674  private int [] classValues() {
675    String debug = "(KStar.classValues)";
676    int [] classval = new int[m_NumInstances];
677    for (int i=0; i < m_NumInstances; i++) {
678      try {
679        classval[i] = (int)m_Train.instance(i).classValue();
680      } catch (Exception ex) {
681        ex.printStackTrace();
682      }
683    }
684    return classval;
685  }
686
687  /**
688   * Returns a copy of the array with its elements randomly redistributed.
689   *
690   * @param array the array to randomize.
691   * @param generator the random number generator to use
692   * @return a copy of the array with its elements randomly redistributed.
693   */
694  private int [] randomize(int [] array, Random generator) {
695    String debug = "(KStar.randomize)";
696    int index;
697    int temp;
698    int [] newArray = new int[array.length];
699    System.arraycopy(array, 0, newArray, 0, array.length);
700    for (int j = newArray.length - 1; j > 0; j--) {
701      index = (int) ( generator.nextDouble() * (double)j );
702      temp = newArray[j];
703      newArray[j] = newArray[index];
704      newArray[index] = temp;
705    }
706    return newArray;
707  }
708 
709  /**
710   * Returns the revision string.
711   *
712   * @return            the revision
713   */
714  public String getRevision() {
715    return RevisionUtils.extract("$Revision: 5928 $");
716  }
717
718} // class end
719
Note: See TracBrowser for help on using the repository browser.