source: tags/MetisMQIDemo/src/main/java/weka/classifiers/misc/monotone/OSDLCore.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: 55.6 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 *    OSDLCore.java
19 *    Copyright (C) 2004 Stijn Lievens
20 */
21
22package weka.classifiers.misc.monotone;
23
24import weka.classifiers.Classifier;
25import weka.classifiers.AbstractClassifier;
26import weka.core.Capabilities;
27import weka.core.Instance;
28import weka.core.DenseInstance;
29import weka.core.Instances;
30import weka.core.Option;
31import weka.core.SelectedTag;
32import weka.core.Tag;
33import weka.core.TechnicalInformation;
34import weka.core.TechnicalInformationHandler;
35import weka.core.Utils;
36import weka.core.Capabilities.Capability;
37import weka.core.TechnicalInformation.Field;
38import weka.core.TechnicalInformation.Type;
39import weka.estimators.DiscreteEstimator;
40
41import java.util.Arrays;
42import java.util.Enumeration;
43import java.util.HashMap;
44import java.util.Iterator;
45import java.util.Map;
46import java.util.Vector;
47
48/**
49 <!-- globalinfo-start -->
50 * This class is an implementation of the Ordinal Stochastic Dominance Learner.<br/>
51 * Further information regarding the OSDL-algorithm can be found in:<br/>
52 * <br/>
53 * S. Lievens, B. De Baets, K. Cao-Van (2006). A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting. Annals of Operations Research..<br/>
54 * <br/>
55 * Kim Cao-Van (2003). Supervised ranking: from semantics to algorithms.<br/>
56 * <br/>
57 * Stijn Lievens (2004). Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken.<br/>
58 * <br/>
59 * For more information about supervised ranking, see<br/>
60 * <br/>
61 * http://users.ugent.be/~slievens/supervised_ranking.php
62 * <p/>
63 <!-- globalinfo-end -->
64 *
65 <!-- technical-bibtex-start -->
66 * BibTeX:
67 * <pre>
68 * &#64;article{Lievens2006,
69 *    author = {S. Lievens and B. De Baets and K. Cao-Van},
70 *    journal = {Annals of Operations Research},
71 *    title = {A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting},
72 *    year = {2006}
73 * }
74 *
75 * &#64;phdthesis{Cao-Van2003,
76 *    author = {Kim Cao-Van},
77 *    school = {Ghent University},
78 *    title = {Supervised ranking: from semantics to algorithms},
79 *    year = {2003}
80 * }
81 *
82 * &#64;mastersthesis{Lievens2004,
83 *    author = {Stijn Lievens},
84 *    school = {Ghent University},
85 *    title = {Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken},
86 *    year = {2004}
87 * }
88 * </pre>
89 * <p/>
90 <!-- technical-bibtex-end -->
91 *
92 <!-- options-start -->
93 * Valid options are: <p/>
94 *
95 * <pre> -D
96 *  If set, classifier is run in debug mode and
97 *  may output additional info to the console</pre>
98 *
99 * <pre> -C &lt;REG|WSUM|MAX|MED|RMED&gt;
100 *  Sets the classification type to be used.
101 *  (Default: MED)</pre>
102 *
103 * <pre> -B
104 *  Use the balanced version of the Ordinal Stochastic Dominance Learner</pre>
105 *
106 * <pre> -W
107 *  Use the weighted version of the Ordinal Stochastic Dominance Learner</pre>
108 *
109 * <pre> -S &lt;value of interpolation parameter&gt;
110 *  Sets the value of the interpolation parameter (not with -W/T/P/L/U)
111 *  (default: 0.5).</pre>
112 *
113 * <pre> -T
114 *  Tune the interpolation parameter (not with -W/S)
115 *  (default: off)</pre>
116 *
117 * <pre> -L &lt;Lower bound for interpolation parameter&gt;
118 *  Lower bound for the interpolation parameter (not with -W/S)
119 *  (default: 0)</pre>
120 *
121 * <pre> -U &lt;Upper bound for interpolation parameter&gt;
122 *  Upper bound for the interpolation parameter (not with -W/S)
123 *  (default: 1)</pre>
124 *
125 * <pre> -P &lt;Number of parts&gt;
126 *  Determines the step size for tuning the interpolation
127 *  parameter, nl. (U-L)/P (not with -W/S)
128 *  (default: 10)</pre>
129 *
130 <!-- options-end -->
131 *
132 * @author Stijn Lievens (stijn.lievens@ugent.be)
133 * @version $Revision: 5987 $
134 */
135public abstract class OSDLCore
136  extends AbstractClassifier
137  implements TechnicalInformationHandler {
138
139  /** for serialization */
140  private static final long serialVersionUID = -9209888846680062897L;
141
142  /**
143   * Constant indicating that the classification type is
144   * regression (probabilistic weighted sum).
145   */
146  public static final int CT_REGRESSION = 0;
147
148  /**
149   * Constant indicating that the classification type is 
150   * the probabilistic weighted sum.
151   */
152  public static final int CT_WEIGHTED_SUM = 1;
153
154  /**
155   * Constant indicating that the classification type is 
156   * the mode of the distribution.
157   */
158  public static final int CT_MAXPROB = 2;
159
160  /**
161   * Constant indicating that the classification type is 
162   * the median.
163   */
164  public static final int CT_MEDIAN = 3;
165
166  /**
167   *  Constant indicating that the classification type is
168   *  the median, but not rounded to the nearest class.
169   */
170  public static final int CT_MEDIAN_REAL = 4;
171
172  /** the classification types */
173  public static final Tag[] TAGS_CLASSIFICATIONTYPES = {
174    new Tag(CT_REGRESSION, "REG", "Regression"),
175    new Tag(CT_WEIGHTED_SUM, "WSUM", "Weighted Sum"),
176    new Tag(CT_MAXPROB, "MAX", "Maximum probability"),
177    new Tag(CT_MEDIAN, "MED", "Median"),
178    new Tag(CT_MEDIAN_REAL, "RMED", "Median without rounding")
179  };
180
181  /**
182   * The classification type, by default set to CT_MEDIAN.
183   */
184  private int m_ctype = CT_MEDIAN;
185
186  /**
187   * The training examples.
188   */
189  private Instances m_train;
190
191  /**
192   * Collection of (Coordinates,DiscreteEstimator) pairs.
193   * This Map is build from the training examples.
194   * The DiscreteEstimator is over the classes.
195   * Each DiscreteEstimator indicates how many training examples
196   * there are with the specified classes.
197   */
198  private Map m_estimatedDistributions;
199
200
201  /**
202   * Collection of (Coordinates,CumulativeDiscreteDistribution) pairs.
203   * This Map is build from the training examples, and more
204   * specifically from the previous map. 
205   */
206  private Map m_estimatedCumulativeDistributions;
207
208
209  /**
210   * The interpolationparameter s. 
211   * By default set to 1/2.
212   */
213  private double m_s = 0.5;
214
215  /**
216   * Lower bound for the interpolationparameter s.
217   * Default value is 0.
218   */
219  private double m_sLower = 0.;
220
221  /**
222   * Upper bound for the interpolationparameter s.
223   * Default value is 1.
224   */
225  private double m_sUpper = 1.0;
226
227  /**
228   * The number of parts the interval [m_sLower,m_sUpper] is
229   * divided in, while searching for the best parameter s.
230   * This thus determines the granularity of the search.
231   * m_sNrParts + 1 values of the interpolationparameter will
232   * be tested.
233   */
234  private int m_sNrParts = 10;
235
236  /**
237   * Indicates whether the interpolationparameter is to be tuned
238   * using leave-one-out cross validation.  <code> true </code> if
239   * this is the case (default is <code> false </code>).
240   */
241  private boolean m_tuneInterpolationParameter = false;
242
243  /**
244   * Indicates whether the current value of the interpolationparamter
245   * is valid.  More specifically if <code>
246   * m_tuneInterpolationParameter == true </code>, and
247   * <code> m_InterpolationParameter == false </code>,
248   * this means that the current interpolation parameter is not valid.
249   * This parameter is only relevant if <code> m_tuneInterpolationParameter
250   * == true </code>.
251   *
252   * If <code> m_tuneInterpolationParameter </code> and <code>
253   * m_interpolationParameterValid </code> are both <code> true </code>,
254   * then <code> m_s </code> should always be between
255   * <code> m_sLower </code> and <code> m_sUpper </code>.
256   */
257  private boolean m_interpolationParameterValid = false;
258
259
260  /**
261   * Constant to switch between balanced and unbalanced OSDL.
262   * <code> true </code> means that one chooses balanced OSDL
263   * (default: <code> false </code>).
264   */
265  private boolean m_balanced = false;
266
267  /**
268   * Constant to choose the weighted variant of the OSDL algorithm.
269   */
270  private boolean m_weighted = false;
271
272  /**
273   * Coordinates representing the smallest element of the data space.
274   */
275  private Coordinates smallestElement;
276
277  /**
278   * Coordinates representing the biggest element of the data space.
279   */
280  private Coordinates biggestElement;
281
282  /**
283   * Returns a string describing the classifier.
284   * @return a description suitable for displaying in the
285   * explorer/experimenter gui
286   */
287  public String globalInfo() {
288    return "This class is an implementation of the Ordinal Stochastic "
289    + "Dominance Learner.\n" 
290    + "Further information regarding the OSDL-algorithm can be found in:\n\n"
291    + getTechnicalInformation().toString() + "\n\n"
292    + "For more information about supervised ranking, see\n\n"
293    + "http://users.ugent.be/~slievens/supervised_ranking.php";
294  }
295
296  /**
297   * Returns an instance of a TechnicalInformation object, containing
298   * detailed information about the technical background of this class,
299   * e.g., paper reference or book this class is based on.
300   *
301   * @return the technical information about this class
302   */
303  public TechnicalInformation getTechnicalInformation() {
304    TechnicalInformation result;
305    TechnicalInformation additional;
306
307    result = new TechnicalInformation(Type.ARTICLE);
308    result.setValue(Field.AUTHOR, "S. Lievens and B. De Baets and K. Cao-Van");
309    result.setValue(Field.YEAR, "2006");
310    result.setValue(Field.TITLE, "A Probabilistic Framework for the Design of Instance-Based Supervised Ranking Algorithms in an Ordinal Setting");
311    result.setValue(Field.JOURNAL, "Annals of Operations Research");
312
313    additional = result.add(Type.PHDTHESIS);
314    additional.setValue(Field.AUTHOR, "Kim Cao-Van");
315    additional.setValue(Field.YEAR, "2003");
316    additional.setValue(Field.TITLE, "Supervised ranking: from semantics to algorithms");
317    additional.setValue(Field.SCHOOL, "Ghent University");
318
319    additional = result.add(Type.MASTERSTHESIS);
320    additional.setValue(Field.AUTHOR, "Stijn Lievens");
321    additional.setValue(Field.YEAR, "2004");
322    additional.setValue(Field.TITLE, "Studie en implementatie van instantie-gebaseerde algoritmen voor gesuperviseerd rangschikken");
323    additional.setValue(Field.SCHOOL, "Ghent University");
324
325    return result;
326  }
327
328  /**
329   * Returns default capabilities of the classifier.
330   *
331   * @return      the capabilities of this classifier
332   */
333  public Capabilities getCapabilities() {
334    Capabilities result = super.getCapabilities();
335    result.disableAll();
336
337    // attributes
338    result.enable(Capability.NOMINAL_ATTRIBUTES);
339
340    // class
341    result.enable(Capability.NOMINAL_CLASS);
342    result.enable(Capability.MISSING_CLASS_VALUES);
343
344    // instances
345    result.setMinimumNumberInstances(0);
346
347    return result;
348  }
349
350  /**
351   * Classifies a given instance using the current settings
352   * of the classifier.
353   *
354   * @param instance the instance to be classified
355   * @throws Exception if for some reason no distribution
356   *         could be predicted
357   * @return the classification for the instance.  Depending on the
358   * settings of the classifier this is a double representing
359   * a classlabel (internal WEKA format) or a real value in the sense
360   * of regression.
361   */
362  public double classifyInstance(Instance instance)
363    throws Exception { 
364   
365    try {
366      return classifyInstance(instance, m_s, m_ctype);
367    } catch (IllegalArgumentException e) {
368      throw new AssertionError(e);
369    }
370  }
371
372  /**
373   * Classifies a given instance using the settings in the paramater
374   * list.  This doesn't change the internal settings of the classifier.
375   * In particular the interpolationparameter <code> m_s </code>
376   * and the classification type <code> m_ctype </code> are not changed.
377   *
378   * @param instance the instance to be classified
379   * @param s the value of the interpolationparameter to be used
380   * @param ctype the classification type to be used 
381   * @throws IllegalStateException for some reason no distribution
382   *         could be predicted
383   * @throws IllegalArgumentException if the interpolation parameter or the
384   *         classification type is not valid
385   * @return the label assigned to the instance.  It is given in internal floating point format.
386   */
387  private double classifyInstance(Instance instance, double s, int ctype) 
388    throws IllegalArgumentException, IllegalStateException {
389   
390    if (s < 0 || s > 1) {
391      throw new IllegalArgumentException("Interpolation parameter is not valid " + s);
392    }
393
394    DiscreteDistribution dist = null;
395    if (!m_balanced) {
396      dist = distributionForInstance(instance, s);
397    } else {
398      dist = distributionForInstanceBalanced(instance, s);
399    }
400
401    if (dist == null) {
402      throw new IllegalStateException("Null distribution predicted");
403    }
404
405    double value = 0;
406    switch(ctype) {
407      case CT_REGRESSION:
408      case CT_WEIGHTED_SUM:
409        value = dist.mean();
410        if (ctype == CT_WEIGHTED_SUM) {
411          value = Math.round(value);
412        }
413        break;
414
415      case CT_MAXPROB:
416        value = dist.modes()[0];
417        break;
418
419      case CT_MEDIAN:
420      case CT_MEDIAN_REAL:
421        value = dist.median();
422        if (ctype == CT_MEDIAN) {
423          value = Math.round(value);
424        }
425        break;
426
427      default:
428        throw new IllegalArgumentException("Not a valid classification type!"); 
429    }
430    return value;
431  }
432
433  /**
434   * Calculates the class probabilities for the given test instance.
435   * Uses the current settings of the parameters if these are valid.
436   * If necessary it updates the interpolationparameter first, and hence
437   * this may change the classifier.
438   *
439   * @param instance the instance to be classified
440   * @return an array of doubles representing the predicted
441   * probability distribution over the class labels
442   */
443  public double[] distributionForInstance(Instance instance) {
444
445    if (m_tuneInterpolationParameter
446        && !m_interpolationParameterValid) {
447      tuneInterpolationParameter();
448    }
449
450    if (!m_balanced) {
451      return distributionForInstance(instance, m_s).toArray();
452    } 
453    // balanced variant
454    return distributionForInstanceBalanced(instance, m_s).toArray();
455  }
456
457  /**
458   * Calculates the cumulative class probabilities for the given test
459   * instance. Uses the current settings of the parameters if these are
460   * valid. If necessary it updates the interpolationparameter first,
461   * and hence this may change the classifier.
462   *
463   * @param instance the instance to be classified
464   * @return an array of doubles representing the predicted
465   * cumulative probability distribution over the class labels
466   */
467  public double[] cumulativeDistributionForInstance(Instance instance) {
468
469    if (m_tuneInterpolationParameter
470        && !m_interpolationParameterValid) {
471      tuneInterpolationParameter();
472    }
473
474    if (!m_balanced) {
475      return cumulativeDistributionForInstance(instance, m_s).toArray();
476    } 
477    return cumulativeDistributionForInstanceBalanced(instance, m_s).toArray();
478  }
479
480  /**
481   * Calculates the class probabilities for the given test instance.
482   * Uses the interpolation parameter from the parameterlist, and
483   * always performs the ordinary or weighted OSDL algorithm,
484   * according to the current settings of the classifier.
485   * This method doesn't change the classifier. 
486   *
487   * @param instance the instance to classify
488   * @param s value of the interpolationparameter to use
489   * @return the calculated distribution
490   */
491  private DiscreteDistribution distributionForInstance(Instance instance, double s) {
492    return new DiscreteDistribution(cumulativeDistributionForInstance(instance, s));
493  }
494
495  /**
496   * Calculates the class probabilities for the given test
497   * instance. Uses the interpolationparameter from the parameterlist, and
498   * always performs the balanced OSDL algorithm.
499   * This method doesn't change the classifier. 
500   *
501   * @param instance the instance to classify
502   * @param s value of the interpolationparameter to use
503   * @return the calculated distribution
504   */
505  private DiscreteDistribution distributionForInstanceBalanced(
506      Instance instance, double s) {
507   
508    return new DiscreteDistribution(cumulativeDistributionForInstanceBalanced(instance,s));
509  }
510
511  /**
512   * Calculates the cumulative class probabilities for the given test
513   * instance. Uses the interpolationparameter from the parameterlist, and
514   * always performs the ordinary or weighted OSDL algorithm,
515   * according to the current settings of the classifier.
516   * This method doesn't change the classifier. 
517   *
518   * @param instance the instance to classify
519   * @param s value of the interpolationparameter to use
520   * @return the calculated distribution
521   */
522  private CumulativeDiscreteDistribution cumulativeDistributionForInstance(
523      Instance instance, double s) {
524   
525    Coordinates xc = new Coordinates(instance);
526    int n = instance.numClasses();
527    int nrSmaller = 0; 
528    int nrGreater = 0;
529
530    if (!containsSmallestElement()) {
531      // corresponds to adding the minimal element to the data space
532      nrSmaller = 1; // avoid division by zero
533    }
534
535    if (!containsBiggestElement()) {
536      // corresponds to adding the maximal element to the data space
537      nrGreater = 1; // avoid division by zero 
538    }
539
540
541    // Create fMin and fMax
542    CumulativeDiscreteDistribution fMin =
543      DistributionUtils.getMinimalCumulativeDiscreteDistribution(n);
544    CumulativeDiscreteDistribution fMax =
545      DistributionUtils.getMaximalCumulativeDiscreteDistribution(n);
546
547    // Cycle through all the map of cumulative distribution functions
548    for (Iterator i = m_estimatedCumulativeDistributions.keySet().iterator();
549    i.hasNext(); ) {
550      Coordinates yc = (Coordinates) i.next();
551      CumulativeDiscreteDistribution cdf = 
552        (CumulativeDiscreteDistribution) 
553        m_estimatedCumulativeDistributions.get(yc);
554
555      if (yc.equals(xc)) {
556        nrSmaller++;
557        fMin = DistributionUtils.takeMin(fMin,cdf);
558        nrGreater++;
559        fMax = DistributionUtils.takeMax(fMax,cdf);
560      } else if (yc.strictlySmaller(xc)) {
561        nrSmaller++;
562        fMin = DistributionUtils.takeMin(fMin,cdf);
563      } else if (xc.strictlySmaller(yc)) {
564        nrGreater++;
565        fMax = DistributionUtils.takeMax(fMax,cdf);
566      }
567    }
568
569    if (m_weighted) {
570      s = ( (double) nrSmaller) / (nrSmaller + nrGreater);
571      if (m_Debug) {
572        System.err.println("Weighted OSDL: interpolation parameter"
573            + " is s = " + s);
574      }
575    }
576
577    // calculate s*fMin + (1-s)*fMax
578    return DistributionUtils.interpolate(fMin, fMax, 1 - s);
579  }
580
581  /**
582   * @return true if the learning examples contain an element for which
583   * the coordinates are the minimal element of the data space, false
584   * otherwise
585   */
586  private boolean containsSmallestElement() {
587    return m_estimatedCumulativeDistributions.containsKey(smallestElement);     
588  }
589
590  /**
591   * @return true if the learning examples contain an element for which
592   * the coordinates are the maximal element of the data space, false
593   * otherwise
594   */
595  private boolean containsBiggestElement() {
596    return m_estimatedCumulativeDistributions.containsKey(biggestElement);     
597  }
598
599
600  /**
601   * Calculates the cumulative class probabilities for the given test
602   * instance. Uses the interpolationparameter from the parameterlist, and
603   * always performs the single or double balanced OSDL algorithm.
604   * This method doesn't change the classifier. 
605   *
606   * @param instance the instance to classify
607   * @param s value of the interpolationparameter to use
608   * @return the calculated distribution
609   */
610  private CumulativeDiscreteDistribution cumulativeDistributionForInstanceBalanced(
611      Instance instance, double s) {
612
613    Coordinates xc = new Coordinates(instance);
614    int n = instance.numClasses();
615
616    // n_m[i] represents the number of examples smaller or equal
617    // than xc and with a class label strictly greater than i
618    int[] n_m = new int[n];
619
620    // n_M[i] represents the number of examples greater or equal
621    // than xc and with a class label smaller or equal than i
622    int[] n_M = new int[n];
623
624    // Create fMin and fMax
625    CumulativeDiscreteDistribution fMin =
626      DistributionUtils.getMinimalCumulativeDiscreteDistribution(n);
627    CumulativeDiscreteDistribution fMax =
628      DistributionUtils.getMaximalCumulativeDiscreteDistribution(n);
629
630    // Cycle through all the map of cumulative distribution functions
631    for (Iterator i = 
632      m_estimatedCumulativeDistributions.keySet().iterator();
633    i.hasNext(); ) {
634      Coordinates yc = (Coordinates) i.next();
635      CumulativeDiscreteDistribution cdf = 
636        (CumulativeDiscreteDistribution) 
637        m_estimatedCumulativeDistributions.get(yc);
638
639      if (yc.equals(xc)) {
640        // update n_m and n_M
641        DiscreteEstimator df = 
642          (DiscreteEstimator) m_estimatedDistributions.get(yc);
643        updateN_m(n_m,df);
644        updateN_M(n_M,df);
645
646        fMin = DistributionUtils.takeMin(fMin,cdf);
647        fMax = DistributionUtils.takeMax(fMax,cdf);
648      } else if (yc.strictlySmaller(xc)) {
649        // update n_m
650        DiscreteEstimator df = 
651          (DiscreteEstimator) m_estimatedDistributions.get(yc);
652        updateN_m(n_m, df);
653        fMin = DistributionUtils.takeMin(fMin,cdf);
654      }
655      else if (xc.strictlySmaller(yc)) {
656        // update n_M
657        DiscreteEstimator df = 
658          (DiscreteEstimator) m_estimatedDistributions.get(yc);
659        updateN_M(n_M, df);
660        fMax = DistributionUtils.takeMax(fMax,cdf);
661      }
662    }
663
664    double[] dd = new double[n];
665
666    // for each label decide what formula to use, either using
667    // n_m[i] and n_M[i] (if fMin[i]<fMax[i]) or using the
668    // interpolationparameter s or using the double balanced version
669    for (int i = 0; i < n; i++) {
670      double fmin = fMin.getCumulativeProbability(i);
671      double fmax = fMax.getCumulativeProbability(i);
672
673      if (m_weighted == true) { // double balanced version
674        if (fmin < fmax) { // reversed preference
675          dd[i] =  (n_m[i] * fmin + n_M[i] * fmax) 
676          / (n_m[i] + n_M[i]);
677        } else {
678          if (n_m[i] + n_M[i] == 0) { // avoid division by zero
679            dd[i] = s * fmin + (1 - s) * fmax;
680          } else {
681            dd[i] = (n_M[i] * fmin + n_m[i] * fmax) 
682            / (n_m[i] + n_M[i]) ;
683          }
684        }
685      } else {  // singly balanced version
686        dd[i] = (fmin < fmax) 
687        ? (n_m[i] * fmin + n_M[i] * fmax) / (n_m[i] + n_M[i])
688            : s * fmin + (1 - s) * fmax;
689      }
690    } try {
691      return new CumulativeDiscreteDistribution(dd);
692    } catch (IllegalArgumentException e) {
693      // this shouldn't happen.
694      System.err.println("We tried to create a cumulative "
695          + "discrete distribution from the following array");
696      for (int i = 0; i < dd.length; i++) {
697        System.err.print(dd[i] + " ");
698      }
699      System.err.println();
700      throw new AssertionError(dd);
701    }
702  }
703
704
705  /**
706   * Update the array n_m using the given <code> DiscreteEstimator </code>.
707   *
708   * @param n_m the array n_m that will be updated.
709   * @param de the <code> DiscreteEstimator </code> that gives the
710   *        count over the different class labels.
711   */
712  private void updateN_m(int[] n_m, DiscreteEstimator de) {
713    int[] tmp = new int[n_m.length];
714
715    // all examples have a class labels strictly greater
716    // than 0, except those that have class label 0.
717    tmp[0] = (int) de.getSumOfCounts() - (int) de.getCount(0);
718    n_m[0] += tmp[0];
719    for (int i = 1; i < n_m.length; i++) {
720
721      // the examples with a class label strictly greater
722      // than i are exactly those that have a class label strictly
723      // greater than i-1, except those that have class label i.
724      tmp[i] = tmp[i - 1] - (int) de.getCount(i);
725      n_m[i] += tmp[i];
726    }
727
728    if (n_m[n_m.length - 1] != 0) {
729      // this shouldn't happen
730      System.err.println("******** Problem with n_m in " 
731          + m_train.relationName());
732      System.err.println("Last argument is non-zero, namely : " 
733          + n_m[n_m.length - 1]);
734    }
735  }
736
737  /**
738   * Update the array n_M using the given <code> DiscreteEstimator </code>.
739   *
740   * @param n_M the array n_M that will be updated.
741   * @param de the <code> DiscreteEstimator </code> that gives the
742   *        count over the different class labels.
743   */
744  private void updateN_M(int[] n_M, DiscreteEstimator de) {
745    int n = n_M.length;
746    int[] tmp = new int[n];
747
748    // all examples have a class label smaller or equal
749    // than n-1 (which is the maximum class label)
750    tmp[n - 1] = (int) de.getSumOfCounts();
751    n_M[n - 1] += tmp[n - 1];
752    for (int i = n - 2; i >= 0; i--) {
753
754      // the examples with a class label smaller or equal
755      // than i are exactly those that have a class label
756      // smaller or equal than i+1, except those that have
757      // class label i+1.
758      tmp[i] = tmp[i + 1] - (int) de.getCount(i + 1);
759      n_M[i] += tmp[i];
760    }
761  }
762
763  /**
764   * Builds the classifier.
765   * This means that all relevant examples are stored into memory.
766   * If necessary the interpolation parameter is tuned.
767   *
768   * @param instances the instances to be used for building the classifier
769   * @throws Exception if the classifier can't be built successfully
770   */
771  public void buildClassifier(Instances instances) throws Exception {
772
773    getCapabilities().testWithFail(instances);
774
775    // copy the dataset
776    m_train = new Instances(instances);
777
778    // new dataset in which examples with missing class value are removed
779    m_train.deleteWithMissingClass();
780
781    // build the Map for the estimatedDistributions
782    m_estimatedDistributions = new HashMap(m_train.numInstances()/2);
783
784    // cycle through all instances
785    for (Iterator it = 
786      new EnumerationIterator(instances.enumerateInstances()); 
787    it.hasNext();) {
788      Instance instance = (Instance) it.next();
789      Coordinates c = new Coordinates(instance);
790
791      // get DiscreteEstimator from the map
792      DiscreteEstimator df = 
793        (DiscreteEstimator) m_estimatedDistributions.get(c);
794
795      // if no DiscreteEstimator is present in the map, create one
796      if (df == null) {
797        df = new DiscreteEstimator(instances.numClasses(),0);
798      }
799      df.addValue(instance.classValue(),instance.weight()); // update
800      m_estimatedDistributions.put(c,df); // put back in map
801    }
802
803
804    // build the map of cumulative distribution functions
805    m_estimatedCumulativeDistributions = 
806      new HashMap(m_estimatedDistributions.size()/2);
807
808    // Cycle trough the map of discrete distributions, and create a new
809    // one containing cumulative discrete distributions
810    for (Iterator it=m_estimatedDistributions.keySet().iterator();
811    it.hasNext();) {
812      Coordinates c = (Coordinates) it.next();
813      DiscreteEstimator df = 
814        (DiscreteEstimator) m_estimatedDistributions.get(c);
815      m_estimatedCumulativeDistributions.put
816      (c, new CumulativeDiscreteDistribution(df));
817    }
818
819    // check if the interpolation parameter needs to be tuned
820    if (m_tuneInterpolationParameter && !m_interpolationParameterValid) {
821      tuneInterpolationParameter();
822    }
823
824    // fill in the smallest and biggest element (for use in the
825    // quasi monotone version of the algorithm)
826    double[] tmpAttValues = new double[instances.numAttributes()];
827    Instance instance = new DenseInstance(1, tmpAttValues);
828    instance.setDataset(instances);
829    smallestElement = new Coordinates(instance);
830    if (m_Debug) {
831      System.err.println("minimal element of data space = " 
832          + smallestElement);
833    }
834    for (int i = 0; i < tmpAttValues.length; i++) {
835      tmpAttValues[i] = instances.attribute(i).numValues() - 1; 
836    }
837
838    instance = new DenseInstance(1, tmpAttValues);
839    instance.setDataset(instances);
840    biggestElement = new Coordinates(instance);
841    if (m_Debug) {
842      System.err.println("maximal element of data space = " 
843          + biggestElement);
844    }
845  }
846
847  /**
848   * Returns the tip text for this property.
849   *
850   * @return tip text for this property suitable for
851   * displaying in the explorer/experimenter gui
852   */
853  public String classificationTypeTipText() {
854    return "Sets the way in which a single label will be extracted "
855    + "from the estimated distribution.";
856  }
857
858  /**
859   * Sets the classification type.  Currently <code> ctype </code>
860   * must be one of:
861   * <ul>
862   * <li> <code> CT_REGRESSION </code> : use expectation value of
863   * distribution.  (Non-ordinal in nature).
864   * <li> <code> CT_WEIGHTED_SUM </code> : use expectation value of
865   * distribution rounded to nearest class label. (Non-ordinal in
866   * nature).
867   * <li> <code> CT_MAXPROB </code> : use the mode of the distribution.
868   * (May deliver non-monotone results).
869   * <li> <code> CT_MEDIAN </code> : use the median of the distribution
870   * (rounded to the nearest class label).
871   * <li> <code> CT_MEDIAN_REAL </code> : use the median of the distribution
872   * but not rounded to the nearest class label.
873   * </ul>
874   *
875   * @param value the classification type
876   */
877  public void setClassificationType(SelectedTag value) {
878    if (value.getTags() == TAGS_CLASSIFICATIONTYPES)
879      m_ctype = value.getSelectedTag().getID();
880  }
881
882  /**
883   * Returns the classification type.
884   *
885   * @return the classification type
886   */
887  public SelectedTag getClassificationType() {
888    return new SelectedTag(m_ctype, TAGS_CLASSIFICATIONTYPES);
889  }
890
891
892  /**
893   * Returns the tip text for this property.
894   *
895   * @return tip text for this property suitable for
896   * displaying in the explorer/experimenter gui
897   */
898  public String tuneInterpolationParameterTipText() {
899    return "Whether to tune the interpolation parameter based on the bounds.";
900  }
901 
902  /**
903   * Sets whether the interpolation parameter is to be tuned based on the
904   * bounds.
905   *
906   * @param value if true the parameter is tuned
907   */
908  public void setTuneInterpolationParameter(boolean value) {
909    m_tuneInterpolationParameter = value;
910  }
911 
912  /**
913   * Returns whether the interpolation parameter is to be tuned based on the
914   * bounds.
915   *
916   * @return true if the parameter is to be tuned
917   */
918  public boolean getTuneInterpolationParameter() {
919    return m_tuneInterpolationParameter;
920  }
921
922  /**
923   * Returns the tip text for this property.
924   *
925   * @return tip text for this property suitable for
926   * displaying in the explorer/experimenter gui
927   */
928  public String interpolationParameterLowerBoundTipText() {
929    return "Sets the lower bound for the interpolation parameter tuning (0 <= x < 1).";
930  }
931 
932  /**
933   * Sets the lower bound for the interpolation parameter tuning
934   * (0 &lt;= x &lt; 1).
935   *
936   * @param value the tne lower bound
937   * @throws IllegalArgumentException if bound is invalid
938   */
939  public void setInterpolationParameterLowerBound(double value) {
940    if ( (value < 0) || (value >= 1) || (value > getInterpolationParameterUpperBound()) )
941      throw new IllegalArgumentException("Illegal lower bound");
942   
943    m_sLower = value;
944    m_tuneInterpolationParameter = true;
945    m_interpolationParameterValid = false;
946  }
947 
948  /**
949   * Returns the lower bound for the interpolation parameter tuning
950   * (0 &lt;= x &lt; 1).
951   *
952   * @return the lower bound
953   */
954  public double getInterpolationParameterLowerBound() {
955    return m_sLower;
956  }
957
958  /**
959   * Returns the tip text for this property.
960   *
961   * @return tip text for this property suitable for
962   * displaying in the explorer/experimenter gui
963   */
964  public String interpolationParameterUpperBoundTipText() {
965    return "Sets the upper bound for the interpolation parameter tuning (0 < x <= 1).";
966  }
967 
968  /**
969   * Sets the upper bound for the interpolation parameter tuning
970   * (0 &lt; x &lt;= 1).
971   *
972   * @param value the tne upper bound
973   * @throws IllegalArgumentException if bound is invalid
974   */
975  public void setInterpolationParameterUpperBound(double value) {
976    if ( (value <= 0) || (value > 1) || (value < getInterpolationParameterLowerBound()) )
977      throw new IllegalArgumentException("Illegal upper bound");
978   
979    m_sUpper = value;
980    m_tuneInterpolationParameter = true;
981    m_interpolationParameterValid = false;
982  }
983 
984  /**
985   * Returns the upper bound for the interpolation parameter tuning
986   * (0 &lt; x &lt;= 1).
987   *
988   * @return the upper bound
989   */
990  public double getInterpolationParameterUpperBound() {
991    return m_sUpper;
992  }
993 
994  /**
995   * Sets the interpolation bounds for the interpolation parameter.
996   * When tuning the interpolation parameter only values in the interval
997   * <code> [sLow, sUp] </code> are considered.
998   * It is important to note that using this method immediately
999   * implies that the interpolation parameter is to be tuned.
1000   *
1001   * @param sLow lower bound for the interpolation parameter,
1002   * should not be smaller than 0 or greater than <code> sUp </code>
1003   * @param sUp upper bound for the interpolation parameter,
1004   * should not exceed 1 or be smaller than <code> sLow </code>
1005   * @throws IllegalArgumentException if one of the above conditions
1006   * is not satisfied.
1007   */
1008  public void setInterpolationParameterBounds(double sLow, double sUp) 
1009    throws IllegalArgumentException {
1010   
1011    if (sLow < 0. || sUp > 1. || sLow > sUp) 
1012      throw new IllegalArgumentException("Illegal upper and lower bounds");
1013    m_sLower = sLow;
1014    m_sUpper = sUp;
1015    m_tuneInterpolationParameter = true;
1016    m_interpolationParameterValid = false;
1017  }
1018
1019  /**
1020   * Returns the tip text for this property.
1021   *
1022   * @return tip text for this property suitable for
1023   * displaying in the explorer/experimenter gui
1024   */
1025  public String interpolationParameterTipText() {
1026    return "Sets the value of the interpolation parameter s;"
1027    + "Estimated distribution is s * f_min + (1 - s) *  f_max. ";
1028  }
1029
1030  /**
1031   * Sets the interpolation parameter.  This immediately means that
1032   * the interpolation parameter is not to be tuned.
1033   *
1034   * @param s value for the interpolation parameter.
1035   * @throws IllegalArgumentException if <code> s </code> is not in
1036   * the range [0,1].
1037   */
1038  public void setInterpolationParameter(double s) 
1039    throws IllegalArgumentException {
1040   
1041    if (0 > s || s > 1)
1042      throw new IllegalArgumentException("Interpolationparameter exceeds bounds");
1043    m_tuneInterpolationParameter = false;
1044    m_interpolationParameterValid = false;
1045    m_s = s;
1046  }
1047
1048  /**
1049   * Returns the current value of the interpolation parameter.
1050   *
1051   * @return the value of the interpolation parameter
1052   */
1053  public double getInterpolationParameter() {
1054    return m_s;
1055  }
1056
1057  /**
1058   * Returns the tip text for this property.
1059   *
1060   * @return tip text for this property suitable for
1061   * displaying in the explorer/experimenter gui
1062   */
1063  public String numberOfPartsForInterpolationParameterTipText() {
1064    return "Sets the granularity for tuning the interpolation parameter; "
1065    + "For instance if the value is 32 then 33 values for the "
1066    + "interpolation are checked."; 
1067  }
1068
1069  /**
1070   * Sets the granularity for tuning the interpolation parameter.
1071   * The interval between lower and upper bounds for the interpolation
1072   * parameter is divided into <code> sParts </code> parts, i.e.
1073   * <code> sParts + 1 </code> values will be checked when
1074   * <code> tuneInterpolationParameter </code> is invoked.
1075   * This also means that the interpolation parameter is to
1076   * be tuned.
1077   *
1078   * @param sParts the number of parts
1079   * @throws IllegalArgumentException if <code> sParts </code> is
1080   * smaller or equal than 0.
1081   */
1082  public void setNumberOfPartsForInterpolationParameter(int sParts) 
1083    throws IllegalArgumentException {
1084   
1085    if (sParts <= 0)
1086      throw new IllegalArgumentException("Number of parts is negative");
1087
1088    m_tuneInterpolationParameter = true;
1089    if (m_sNrParts != sParts) {
1090      m_interpolationParameterValid = false;
1091      m_sNrParts = sParts;
1092    }
1093  }
1094
1095  /**
1096   * Gets the granularity for tuning the interpolation parameter.
1097   *
1098   * @return the number of parts in which the interval
1099   * <code> [s_low, s_up] </code> is to be split
1100   */
1101  public int getNumberOfPartsForInterpolationParameter() {
1102    return m_sNrParts;
1103  }
1104
1105  /**
1106   * Returns a string suitable for displaying in the gui/experimenter.
1107   *
1108   * @return tip text for this property suitable for
1109   * displaying in the explorer/experimenter gui
1110   */
1111  public String balancedTipText() {
1112    return "If true, the balanced version of the OSDL-algorithm is used\n"
1113    + "This means that distinction is made between the normal and "
1114    + "reversed preference situation.";
1115  }
1116
1117  /**
1118   * If <code> balanced </code> is <code> true </code> then the balanced
1119   * version of OSDL will be used, otherwise the ordinary version of
1120   * OSDL will be in effect.
1121   *
1122   * @param balanced if <code> true </code> then B-OSDL is used, otherwise
1123   * it is OSDL
1124   */
1125  public void setBalanced(boolean balanced) {
1126    m_balanced = balanced;
1127  }
1128
1129  /**
1130   * Returns if the balanced version of OSDL is in effect.
1131   *
1132   * @return <code> true </code> if the balanced version is in effect,
1133   * <code> false </code> otherwise
1134   */
1135  public boolean getBalanced() {
1136    return m_balanced;
1137  }
1138
1139  /**
1140   * Returns a string suitable for displaying in the gui/experimenter.
1141   *
1142   * @return tip text for this property suitable for
1143   * displaying in the explorer/experimenter gui
1144   */
1145  public String weightedTipText() {
1146    return "If true, the weighted version of the OSDL-algorithm is used";
1147  }
1148
1149  /**
1150   * If <code> weighted </code> is <code> true </code> then the
1151   * weighted version of the OSDL is used.
1152   * Note: using the weighted (non-balanced) version only ensures the
1153   * quasi monotonicity of the results w.r.t. to training set.
1154   *
1155   * @param weighted <code> true </code> if the weighted version to be used,
1156   * <code> false </code> otherwise
1157   */
1158  public void setWeighted(boolean weighted) {
1159    m_weighted = weighted;
1160  }
1161
1162  /**
1163   * Returns if the weighted version is in effect.
1164   *
1165   * @return <code> true </code> if the weighted version is in effect,
1166   * <code> false </code> otherwise.
1167   */
1168  public boolean getWeighted() {
1169    return m_weighted;
1170  }
1171
1172  /**
1173   * Returns the current value of the lower bound for the interpolation
1174   * parameter.
1175   *
1176   * @return the current value of the lower bound for the interpolation
1177   * parameter
1178   */
1179  public double getLowerBound() {
1180    return m_sLower;
1181  }
1182
1183  /**
1184   * Returns the current value of the upper bound for the interpolation
1185   * parameter.
1186   *
1187   * @return the current value of the upper bound for the interpolation
1188   * parameter
1189   */
1190  public double getUpperBound() {
1191    return m_sUpper;
1192  }
1193
1194  /**
1195   * Returns the number of instances in the training set.
1196   *
1197   * @return the number of instances used for training
1198   */
1199  public int getNumInstances() {
1200    return m_train.numInstances();
1201  }
1202
1203  /** Tune the interpolation parameter using the current
1204   *  settings of the classifier.
1205   *  This also sets the interpolation parameter.
1206   *  @return the value of the tuned interpolation parameter.
1207   */
1208  public double tuneInterpolationParameter() {
1209    try {
1210      return tuneInterpolationParameter(m_sLower, m_sUpper, m_sNrParts, m_ctype);
1211    } catch (IllegalArgumentException e) {
1212      throw new AssertionError(e);
1213    }
1214  }
1215
1216  /**
1217   *  Tunes the interpolation parameter using the given settings.
1218   *  The parameters of the classifier are updated accordingly!
1219   *  Marks the interpolation parameter as valid.
1220   * 
1221   *  @param sLow lower end point of interval of paramters to be examined
1222   *  @param sUp upper end point of interval of paramters to be examined
1223   *  @param sParts number of parts the interval is divided into.  This thus determines
1224   *  the granularity of the search
1225   *  @param ctype the classification type to use
1226   *  @return the value of the tuned interpolation parameter
1227   *  @throws IllegalArgumentException if the given parameter list is not
1228   *  valid
1229   */
1230  public double tuneInterpolationParameter(double sLow, double sUp, int sParts, int ctype) 
1231    throws IllegalArgumentException {
1232   
1233    setInterpolationParameterBounds(sLow, sUp);
1234    setNumberOfPartsForInterpolationParameter(sParts);
1235    setClassificationType(new SelectedTag(ctype, TAGS_CLASSIFICATIONTYPES));
1236
1237    m_s = crossValidate(sLow, sUp, sParts, ctype);
1238    m_tuneInterpolationParameter = true;
1239    m_interpolationParameterValid = true;
1240    return m_s;
1241  }
1242
1243  /**
1244   *  Tunes the interpolation parameter using the current settings
1245   *  of the classifier.  This doesn't change the classifier, i.e.
1246   *  none of the internal parameters is changed!
1247   *
1248   *  @return the tuned value of the interpolation parameter
1249   *  @throws IllegalArgumentException if somehow the current settings of the
1250   *  classifier are illegal.
1251   */
1252  public double crossValidate() throws IllegalArgumentException {
1253    return crossValidate(m_sLower, m_sUpper, m_sNrParts, m_ctype);
1254  }
1255
1256  /**
1257   *  Tune the interpolation parameter using leave-one-out
1258   *  cross validation, the loss function used is the 1-0 loss
1259   *  function.
1260   *  <p>
1261   *  The given settings are used, but the classifier is not
1262   *  updated!.  Also, the interpolation parameter s is not
1263   *  set.
1264   *  </p>
1265   *
1266   *  @param sLow lower end point of interval of paramters to be examined
1267   *  @param sUp upper end point of interval of paramters to be examined
1268   *  @param sNrParts number of parts the interval is divided into.  This thus determines
1269   *  the granularity of the search
1270   *  @param ctype the classification type to use
1271   *  @return the best value for the interpolation parameter
1272   *  @throws IllegalArgumentException if the settings for the
1273   *  interpolation parameter are not valid or if the classification
1274   *  type is not valid
1275   */
1276  public double crossValidate (double sLow, double sUp, int sNrParts, int ctype) 
1277    throws IllegalArgumentException {
1278
1279    double[] performanceStats = new double[sNrParts + 1];
1280    return crossValidate(sLow, sUp, sNrParts, ctype, 
1281        performanceStats, new ZeroOneLossFunction());
1282  }
1283
1284  /**
1285   * Tune the interpolation parameter using leave-one-out
1286   * cross validation.  The given parameters are used, but
1287   * the classifier is not changed, in particular, the interpolation
1288   * parameter remains unchanged.
1289   *
1290   * @param sLow lower bound for interpolation parameter
1291   * @param sUp upper bound for interpolation parameter
1292   * @param sNrParts determines the granularity of the search
1293   * @param ctype the classification type to use
1294   * @param performanceStats array acting as output, and that will
1295   * contain the total loss of the leave-one-out cross validation for
1296   * each considered value of the interpolation parameter
1297   * @param lossFunction the loss function to use
1298   * @return the value of the interpolation parameter that is considered
1299   * best
1300   * @throws IllegalArgumentException the length of the array
1301   * <code> performanceStats </code> is not sufficient
1302   * @throws IllegalArgumentException if the interpolation parameters
1303   * are not valid
1304   * @throws IllegalArgumentException if the classification type is
1305   * not valid
1306   */
1307  public double crossValidate(double sLow, double sUp, int sNrParts, 
1308      int ctype, double[] performanceStats, 
1309      NominalLossFunction lossFunction) throws IllegalArgumentException {
1310
1311    if (performanceStats.length < sNrParts + 1) {
1312      throw new IllegalArgumentException("Length of array is not sufficient");
1313    }
1314
1315    if (!interpolationParametersValid(sLow, sUp, sNrParts)) {
1316      throw new IllegalArgumentException("Interpolation parameters are not valid");
1317    }
1318
1319    if (!classificationTypeValid(ctype)) {
1320      throw new IllegalArgumentException("Not a valid classification type " + ctype);
1321    }
1322
1323    Arrays.fill(performanceStats, 0, sNrParts + 1, 0);
1324
1325    // cycle through all instances
1326    for (Iterator it = 
1327      new EnumerationIterator(m_train.enumerateInstances());
1328    it.hasNext(); ) {
1329      Instance instance = (Instance) it.next();
1330      double classValue = instance.classValue();
1331      removeInstance(instance); 
1332
1333      double s = sLow;
1334      double step = (sUp - sLow) / sNrParts; //step size
1335      for (int i = 0; i <= sNrParts; i++, s += step) {
1336        try {
1337          performanceStats[i] += 
1338            lossFunction.loss(classValue,
1339                classifyInstance(instance, s, ctype));
1340        } catch (Exception exception) {
1341
1342          // XXX what should I do here, normally we shouldn't be here
1343          System.err.println(exception.getMessage());
1344          System.exit(1);
1345        }
1346      }
1347
1348      // XXX may be done more efficiently
1349      addInstance(instance); // update
1350    }
1351
1352    // select the 'best' value for s
1353    // to this end, we sort the array with the leave-one-out
1354    // performance statistics, and we choose the middle one
1355    // off all those that score 'best'
1356
1357    // new code, august 2004
1358    // new code, june 2005.  If performanceStats is longer than
1359    // necessary, copy it first
1360    double[] tmp = performanceStats;
1361    if (performanceStats.length > sNrParts + 1) {
1362      tmp = new double[sNrParts + 1];
1363      System.arraycopy(performanceStats, 0, tmp, 0, tmp.length);
1364    }
1365    int[] sort = Utils.stableSort(tmp);
1366    int minIndex = 0;
1367    while (minIndex + 1 < tmp.length 
1368        && tmp[sort[minIndex + 1]] == tmp[sort[minIndex]]) {
1369      minIndex++;
1370    }
1371    minIndex = sort[minIndex / 2];  // middle one
1372    // int minIndex = Utils.minIndex(performanceStats); // OLD code
1373
1374    return  sLow + minIndex * (sUp - sLow) / sNrParts;
1375  }
1376
1377  /**
1378   * Checks if <code> ctype </code> is a valid classification
1379   * type.
1380   * @param ctype the int to be checked
1381   * @return true if ctype is a valid classification type, false otherwise
1382   */
1383  private boolean classificationTypeValid(int ctype) {
1384    return ctype == CT_REGRESSION || ctype == CT_WEIGHTED_SUM
1385    || ctype == CT_MAXPROB || ctype == CT_MEDIAN
1386    || ctype == CT_MEDIAN_REAL;
1387  }
1388
1389  /**
1390   * Checks if the given parameters are valid interpolation parameters.
1391   * @param sLow lower bound for the interval
1392   * @param sUp upper bound for the interval
1393   * @param sNrParts the number of parts the interval has to be divided in
1394   * @return true is the given parameters are valid interpolation parameters,
1395   * false otherwise
1396   */
1397  private boolean interpolationParametersValid(double sLow, double sUp, int sNrParts) {
1398    return sLow >= 0 && sUp <= 1 && sLow < sUp && sNrParts > 0
1399    || sLow == sUp && sNrParts == 0; 
1400    // special case included
1401  }
1402
1403  /**
1404   * Remove an instance from the classifier.  Updates the hashmaps.
1405   * @param instance the instance to be removed. 
1406   */
1407  private void removeInstance(Instance instance) {
1408    Coordinates c = new Coordinates(instance);
1409
1410    // Remove instance temporarily from the Maps with the distributions
1411    DiscreteEstimator df = 
1412      (DiscreteEstimator) m_estimatedDistributions.get(c);
1413
1414    // remove from df
1415    df.addValue(instance.classValue(),-instance.weight());
1416
1417    if (Math.abs(df.getSumOfCounts() - 0) < Utils.SMALL) {
1418
1419      /* There was apparently only one example with coordinates c
1420       * in the training set, and now we removed it.
1421       * Remove the key c from both maps.
1422       */
1423      m_estimatedDistributions.remove(c);
1424      m_estimatedCumulativeDistributions.remove(c);
1425    }
1426    else {
1427
1428      // update both maps
1429      m_estimatedDistributions.put(c,df);
1430      m_estimatedCumulativeDistributions.put
1431      (c, new CumulativeDiscreteDistribution(df));
1432    }
1433  }
1434
1435  /**
1436   * Update the classifier using the given instance.  Updates the hashmaps
1437   * @param instance the instance to be added
1438   */
1439  private void addInstance(Instance instance) {
1440
1441    Coordinates c = new Coordinates(instance);
1442
1443    // Get DiscreteEstimator from the map
1444    DiscreteEstimator df = 
1445      (DiscreteEstimator) m_estimatedDistributions.get(c);
1446
1447    // If no DiscreteEstimator is present in the map, create one
1448    if (df == null) {
1449      df = new DiscreteEstimator(instance.dataset().numClasses(),0);
1450    }
1451    df.addValue(instance.classValue(),instance.weight()); // update df
1452    m_estimatedDistributions.put(c,df); // put back in map
1453    m_estimatedCumulativeDistributions.put
1454    (c, new CumulativeDiscreteDistribution(df));
1455  }
1456
1457  /**
1458   * Returns an enumeration describing the available options.
1459   * For a list of available options, see <code> setOptions </code>.
1460   *
1461   * @return an enumeration of all available options.
1462   */
1463  public Enumeration listOptions() {
1464    Vector options = new Vector();
1465
1466    Enumeration enm = super.listOptions();
1467    while (enm.hasMoreElements())
1468      options.addElement(enm.nextElement());
1469
1470    String description = 
1471      "\tSets the classification type to be used.\n"
1472      + "\t(Default: " + new SelectedTag(CT_MEDIAN, TAGS_CLASSIFICATIONTYPES) + ")";
1473    String synopsis = "-C " + Tag.toOptionList(TAGS_CLASSIFICATIONTYPES);
1474    String name = "C";
1475    options.addElement(new Option(description, name, 1, synopsis));
1476
1477    description = "\tUse the balanced version of the " 
1478      + "Ordinal Stochastic Dominance Learner";
1479    synopsis = "-B";
1480    name = "B";
1481    options.addElement(new Option(description, name, 1, synopsis));
1482
1483    description = "\tUse the weighted version of the " 
1484      + "Ordinal Stochastic Dominance Learner";
1485    synopsis = "-W";
1486    name = "W";
1487    options.addElement(new Option(description, name, 1, synopsis));
1488
1489    description = 
1490      "\tSets the value of the interpolation parameter (not with -W/T/P/L/U)\n" 
1491      + "\t(default: 0.5).";
1492    synopsis = "-S <value of interpolation parameter>";
1493    name = "S";
1494    options.addElement(new Option(description, name, 1, synopsis));
1495
1496    description = 
1497      "\tTune the interpolation parameter (not with -W/S)\n" 
1498      + "\t(default: off)";
1499    synopsis = "-T";
1500    name = "T";
1501    options.addElement(new Option(description, name, 0, synopsis));
1502
1503    description = 
1504      "\tLower bound for the interpolation parameter (not with -W/S)\n" 
1505      + "\t(default: 0)";
1506    synopsis = "-L <Lower bound for interpolation parameter>";
1507    name="L";
1508    options.addElement(new Option(description, name, 1, synopsis));
1509
1510    description = 
1511      "\tUpper bound for the interpolation parameter (not with -W/S)\n" 
1512      + "\t(default: 1)";
1513    synopsis = "-U <Upper bound for interpolation parameter>";
1514    name="U";
1515    options.addElement(new Option(description, name, 1, synopsis));
1516
1517    description = 
1518      "\tDetermines the step size for tuning the interpolation\n" 
1519      + "\tparameter, nl. (U-L)/P (not with -W/S)\n"
1520      + "\t(default: 10)";
1521    synopsis = "-P <Number of parts>";
1522    name="P";
1523    options.addElement(new Option(description, name, 1, synopsis));
1524
1525    return options.elements();
1526  }
1527
1528  /**
1529   * Parses the options for this object. <p/>
1530   *
1531   <!-- options-start -->
1532   * Valid options are: <p/>
1533   *
1534   * <pre> -D
1535   *  If set, classifier is run in debug mode and
1536   *  may output additional info to the console</pre>
1537   *
1538   * <pre> -C &lt;REG|WSUM|MAX|MED|RMED&gt;
1539   *  Sets the classification type to be used.
1540   *  (Default: MED)</pre>
1541   *
1542   * <pre> -B
1543   *  Use the balanced version of the Ordinal Stochastic Dominance Learner</pre>
1544   *
1545   * <pre> -W
1546   *  Use the weighted version of the Ordinal Stochastic Dominance Learner</pre>
1547   *
1548   * <pre> -S &lt;value of interpolation parameter&gt;
1549   *  Sets the value of the interpolation parameter (not with -W/T/P/L/U)
1550   *  (default: 0.5).</pre>
1551   *
1552   * <pre> -T
1553   *  Tune the interpolation parameter (not with -W/S)
1554   *  (default: off)</pre>
1555   *
1556   * <pre> -L &lt;Lower bound for interpolation parameter&gt;
1557   *  Lower bound for the interpolation parameter (not with -W/S)
1558   *  (default: 0)</pre>
1559   *
1560   * <pre> -U &lt;Upper bound for interpolation parameter&gt;
1561   *  Upper bound for the interpolation parameter (not with -W/S)
1562   *  (default: 1)</pre>
1563   *
1564   * <pre> -P &lt;Number of parts&gt;
1565   *  Determines the step size for tuning the interpolation
1566   *  parameter, nl. (U-L)/P (not with -W/S)
1567   *  (default: 10)</pre>
1568   *
1569   <!-- options-end -->
1570   *
1571   * @param options the list of options as an array of strings
1572   * @throws Exception if an option is not supported
1573   */
1574  public void setOptions(String[] options) throws Exception {
1575    String args;
1576
1577    args = Utils.getOption('C',options);
1578    if (args.length() != 0) 
1579      setClassificationType(new SelectedTag(args, TAGS_CLASSIFICATIONTYPES));
1580    else
1581      setClassificationType(new SelectedTag(CT_MEDIAN, TAGS_CLASSIFICATIONTYPES));
1582
1583    setBalanced(Utils.getFlag('B',options));
1584
1585    if (Utils.getFlag('W', options)) {
1586      m_weighted = true;
1587      // ignore any T, S, P, L and U options
1588      Utils.getOption('T', options);
1589      Utils.getOption('S', options);
1590      Utils.getOption('P', options);
1591      Utils.getOption('L', options);
1592      Utils.getOption('U', options);
1593    } else {
1594      m_tuneInterpolationParameter = Utils.getFlag('T', options);
1595
1596      if (!m_tuneInterpolationParameter) {
1597        // ignore P, L, U
1598        Utils.getOption('P', options);
1599        Utils.getOption('L', options);
1600        Utils.getOption('U', options);
1601
1602        // value of s
1603        args = Utils.getOption('S',options);
1604        if (args.length() != 0)
1605          setInterpolationParameter(Double.parseDouble(args));
1606        else
1607          setInterpolationParameter(0.5);
1608      }
1609      else {
1610        // ignore S
1611        Utils.getOption('S', options);
1612       
1613        args = Utils.getOption('L',options);
1614        double l = m_sLower;
1615        if (args.length() != 0)
1616          l = Double.parseDouble(args);
1617        else
1618          l = 0.0;
1619
1620        args = Utils.getOption('U',options);
1621        double u = m_sUpper;
1622        if (args.length() != 0)
1623          u = Double.parseDouble(args);
1624        else
1625          u = 1.0;
1626
1627        if (m_tuneInterpolationParameter)
1628          setInterpolationParameterBounds(l, u);
1629
1630        args = Utils.getOption('P',options);
1631        if (args.length() != 0)
1632          setNumberOfPartsForInterpolationParameter(Integer.parseInt(args));
1633        else
1634          setNumberOfPartsForInterpolationParameter(10);
1635      }
1636    }
1637   
1638    super.setOptions(options);
1639  }
1640
1641  /**
1642   * Gets the current settings of the OSDLCore classifier.
1643   *
1644   * @return an array of strings suitable for passing
1645   * to <code> setOptions </code>
1646   */
1647  public String[] getOptions() {
1648    int         i;
1649    Vector      result;
1650    String[]    options;
1651
1652    result = new Vector();
1653
1654    options = super.getOptions();
1655    for (i = 0; i < options.length; i++)
1656      result.add(options[i]);
1657
1658    // classification type
1659    result.add("-C");
1660    result.add("" + getClassificationType());
1661
1662    if (m_balanced)
1663      result.add("-B");
1664
1665    if (m_weighted) {
1666      result.add("-W");
1667    }
1668    else {
1669      // interpolation parameter
1670      if (!m_tuneInterpolationParameter) {
1671        result.add("-S");
1672        result.add(Double.toString(m_s));
1673      }
1674      else {
1675        result.add("-T");
1676        result.add("-L");
1677        result.add(Double.toString(m_sLower));
1678        result.add("-U");
1679        result.add(Double.toString(m_sUpper));
1680        result.add("-P");
1681        result.add(Integer.toString(m_sNrParts));
1682      }
1683    }
1684
1685    return (String[]) result.toArray(new String[result.size()]);
1686  }
1687
1688  /**
1689   * Returns a description of the classifier.
1690   * Attention: if debugging is on, the description can be become
1691   * very lengthy.
1692   *
1693   * @return a string containing the description
1694   */
1695  public String toString() {
1696    StringBuffer sb = new StringBuffer();
1697
1698    // balanced or ordinary OSDL
1699    if (m_balanced) {
1700      sb.append("Balanced OSDL\n=============\n\n");
1701    } else {
1702      sb.append("Ordinary OSDL\n=============\n\n");
1703    }
1704
1705    if (m_weighted) {
1706      sb.append("Weighted variant\n");
1707    }
1708
1709    // classification type used
1710    sb.append("Classification type: " + getClassificationType() + "\n");
1711
1712    // parameter s
1713    if (!m_weighted) {
1714      sb.append("Interpolation parameter: " + m_s + "\n");
1715      if (m_tuneInterpolationParameter) {
1716        sb.append("Bounds and stepsize: " + m_sLower + " " + m_sUpper + 
1717            " " + m_sNrParts + "\n");
1718        if (!m_interpolationParameterValid) {
1719          sb.append("Interpolation parameter is not valid");
1720        }
1721      }
1722    }
1723
1724
1725    if(m_Debug) {
1726
1727      if (m_estimatedCumulativeDistributions != null) { 
1728        /*
1729         * Cycle through all the map of cumulative distribution functions
1730         * and print each cumulative distribution function
1731         */
1732        for (Iterator i = 
1733          m_estimatedCumulativeDistributions.keySet().iterator();
1734        i.hasNext(); ) {
1735          Coordinates yc = (Coordinates) i.next();
1736          CumulativeDiscreteDistribution cdf = 
1737            (CumulativeDiscreteDistribution) 
1738            m_estimatedCumulativeDistributions.get(yc);
1739          sb.append( "[" + yc.hashCode() + "] " + yc.toString() 
1740              + " --> " + cdf.toString() + "\n");
1741        }
1742      }
1743    }
1744    return sb.toString();
1745  }
1746}
Note: See TracBrowser for help on using the repository browser.