source: src/main/java/weka/classifiers/functions/SMO.java @ 9

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

Import di weka.

File size: 61.2 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 *    SMO.java
19 *    Copyright (C) 1999 University of Waikato, Hamilton, New Zealand
20 *
21 */
22
23package weka.classifiers.functions;
24
25import weka.classifiers.Classifier;
26import weka.classifiers.AbstractClassifier;
27import weka.classifiers.functions.supportVector.Kernel;
28import weka.classifiers.functions.supportVector.PolyKernel;
29import weka.classifiers.functions.supportVector.SMOset;
30import weka.core.Attribute;
31import weka.core.Capabilities;
32import weka.core.FastVector;
33import weka.core.Instance;
34import weka.core.DenseInstance;
35import weka.core.Instances;
36import weka.core.Option;
37import weka.core.OptionHandler;
38import weka.core.RevisionUtils;
39import weka.core.SelectedTag;
40import weka.core.SerializedObject;
41import weka.core.Tag;
42import weka.core.TechnicalInformation;
43import weka.core.TechnicalInformationHandler;
44import weka.core.Utils;
45import weka.core.WeightedInstancesHandler;
46import weka.core.Capabilities.Capability;
47import weka.core.TechnicalInformation.Field;
48import weka.core.TechnicalInformation.Type;
49import weka.filters.Filter;
50import weka.filters.unsupervised.attribute.NominalToBinary;
51import weka.filters.unsupervised.attribute.Normalize;
52import weka.filters.unsupervised.attribute.ReplaceMissingValues;
53import weka.filters.unsupervised.attribute.Standardize;
54
55import java.io.Serializable;
56import java.util.Enumeration;
57import java.util.Random;
58import java.util.Vector;
59
60/**
61 <!-- globalinfo-start -->
62 * Implements John Platt's sequential minimal optimization algorithm for training a support vector classifier.<br/>
63 * <br/>
64 * This implementation globally replaces all missing values and transforms nominal attributes into binary ones. It also normalizes all attributes by default. (In that case the coefficients in the output are based on the normalized data, not the original data --- this is important for interpreting the classifier.)<br/>
65 * <br/>
66 * Multi-class problems are solved using pairwise classification (1-vs-1 and if logistic models are built pairwise coupling according to Hastie and Tibshirani, 1998).<br/>
67 * <br/>
68 * To obtain proper probability estimates, use the option that fits logistic regression models to the outputs of the support vector machine. In the multi-class case the predicted probabilities are coupled using Hastie and Tibshirani's pairwise coupling method.<br/>
69 * <br/>
70 * Note: for improved speed normalization should be turned off when operating on SparseInstances.<br/>
71 * <br/>
72 * For more information on the SMO algorithm, see<br/>
73 * <br/>
74 * J. Platt: Fast Training of Support Vector Machines using Sequential Minimal Optimization. In B. Schoelkopf and C. Burges and A. Smola, editors, Advances in Kernel Methods - Support Vector Learning, 1998.<br/>
75 * <br/>
76 * S.S. Keerthi, S.K. Shevade, C. Bhattacharyya, K.R.K. Murthy (2001). Improvements to Platt's SMO Algorithm for SVM Classifier Design. Neural Computation. 13(3):637-649.<br/>
77 * <br/>
78 * Trevor Hastie, Robert Tibshirani: Classification by Pairwise Coupling. In: Advances in Neural Information Processing Systems, 1998.
79 * <p/>
80 <!-- globalinfo-end -->
81 *
82 <!-- technical-bibtex-start -->
83 * BibTeX:
84 * <pre>
85 * &#64;incollection{Platt1998,
86 *    author = {J. Platt},
87 *    booktitle = {Advances in Kernel Methods - Support Vector Learning},
88 *    editor = {B. Schoelkopf and C. Burges and A. Smola},
89 *    publisher = {MIT Press},
90 *    title = {Fast Training of Support Vector Machines using Sequential Minimal Optimization},
91 *    year = {1998},
92 *    URL = {http://research.microsoft.com/\~jplatt/smo.html},
93 *    PS = {http://research.microsoft.com/\~jplatt/smo-book.ps.gz},
94 *    PDF = {http://research.microsoft.com/\~jplatt/smo-book.pdf}
95 * }
96 *
97 * &#64;article{Keerthi2001,
98 *    author = {S.S. Keerthi and S.K. Shevade and C. Bhattacharyya and K.R.K. Murthy},
99 *    journal = {Neural Computation},
100 *    number = {3},
101 *    pages = {637-649},
102 *    title = {Improvements to Platt's SMO Algorithm for SVM Classifier Design},
103 *    volume = {13},
104 *    year = {2001},
105 *    PS = {http://guppy.mpe.nus.edu.sg/\~mpessk/svm/smo_mod_nc.ps.gz}
106 * }
107 *
108 * &#64;inproceedings{Hastie1998,
109 *    author = {Trevor Hastie and Robert Tibshirani},
110 *    booktitle = {Advances in Neural Information Processing Systems},
111 *    editor = {Michael I. Jordan and Michael J. Kearns and Sara A. Solla},
112 *    publisher = {MIT Press},
113 *    title = {Classification by Pairwise Coupling},
114 *    volume = {10},
115 *    year = {1998},
116 *    PS = {http://www-stat.stanford.edu/\~hastie/Papers/2class.ps}
117 * }
118 * </pre>
119 * <p/>
120 <!-- technical-bibtex-end -->
121 *
122 <!-- options-start -->
123 * Valid options are: <p/>
124 *
125 * <pre> -D
126 *  If set, classifier is run in debug mode and
127 *  may output additional info to the console</pre>
128 *
129 * <pre> -no-checks
130 *  Turns off all checks - use with caution!
131 *  Turning them off assumes that data is purely numeric, doesn't
132 *  contain any missing values, and has a nominal class. Turning them
133 *  off also means that no header information will be stored if the
134 *  machine is linear. Finally, it also assumes that no instance has
135 *  a weight equal to 0.
136 *  (default: checks on)</pre>
137 *
138 * <pre> -C &lt;double&gt;
139 *  The complexity constant C. (default 1)</pre>
140 *
141 * <pre> -N
142 *  Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)</pre>
143 *
144 * <pre> -L &lt;double&gt;
145 *  The tolerance parameter. (default 1.0e-3)</pre>
146 *
147 * <pre> -P &lt;double&gt;
148 *  The epsilon for round-off error. (default 1.0e-12)</pre>
149 *
150 * <pre> -M
151 *  Fit logistic models to SVM outputs. </pre>
152 *
153 * <pre> -V &lt;double&gt;
154 *  The number of folds for the internal
155 *  cross-validation. (default -1, use training data)</pre>
156 *
157 * <pre> -W &lt;double&gt;
158 *  The random number seed. (default 1)</pre>
159 *
160 * <pre> -K &lt;classname and parameters&gt;
161 *  The Kernel to use.
162 *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
163 *
164 * <pre>
165 * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
166 * </pre>
167 *
168 * <pre> -D
169 *  Enables debugging output (if available) to be printed.
170 *  (default: off)</pre>
171 *
172 * <pre> -no-checks
173 *  Turns off all checks - use with caution!
174 *  (default: checks on)</pre>
175 *
176 * <pre> -C &lt;num&gt;
177 *  The size of the cache (a prime number), 0 for full cache and
178 *  -1 to turn it off.
179 *  (default: 250007)</pre>
180 *
181 * <pre> -E &lt;num&gt;
182 *  The Exponent to use.
183 *  (default: 1.0)</pre>
184 *
185 * <pre> -L
186 *  Use lower-order terms.
187 *  (default: no)</pre>
188 *
189 <!-- options-end -->
190 *
191 * @author Eibe Frank (eibe@cs.waikato.ac.nz)
192 * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
193 * @author Stuart Inglis (stuart@reeltwo.com) (sparse vector code)
194 * @version $Revision: 6024 $
195 */
196public class SMO 
197  extends AbstractClassifier
198  implements WeightedInstancesHandler, TechnicalInformationHandler {
199
200  /** for serialization */
201  static final long serialVersionUID = -6585883636378691736L;
202 
203  /**
204   * Returns a string describing classifier
205   * @return a description suitable for
206   * displaying in the explorer/experimenter gui
207   */
208  public String globalInfo() {
209
210    return  "Implements John Platt's sequential minimal optimization "
211      + "algorithm for training a support vector classifier.\n\n"
212      + "This implementation globally replaces all missing values and "
213      + "transforms nominal attributes into binary ones. It also "
214      + "normalizes all attributes by default. (In that case the coefficients "
215      + "in the output are based on the normalized data, not the "
216      + "original data --- this is important for interpreting the classifier.)\n\n"
217      + "Multi-class problems are solved using pairwise classification "
218      + "(1-vs-1 and if logistic models are built pairwise coupling "
219      + "according to Hastie and Tibshirani, 1998).\n\n"
220      + "To obtain proper probability estimates, use the option that fits "
221      + "logistic regression models to the outputs of the support vector "
222      + "machine. In the multi-class case the predicted probabilities "
223      + "are coupled using Hastie and Tibshirani's pairwise coupling "
224      + "method.\n\n"
225      + "Note: for improved speed normalization should be turned off when "
226      + "operating on SparseInstances.\n\n"
227      + "For more information on the SMO algorithm, see\n\n"
228      + getTechnicalInformation().toString();
229  }
230
231  /**
232   * Returns an instance of a TechnicalInformation object, containing
233   * detailed information about the technical background of this class,
234   * e.g., paper reference or book this class is based on.
235   *
236   * @return the technical information about this class
237   */
238  public TechnicalInformation getTechnicalInformation() {
239    TechnicalInformation        result;
240    TechnicalInformation        additional;
241   
242    result = new TechnicalInformation(Type.INCOLLECTION);
243    result.setValue(Field.AUTHOR, "J. Platt");
244    result.setValue(Field.YEAR, "1998");
245    result.setValue(Field.TITLE, "Fast Training of Support Vector Machines using Sequential Minimal Optimization");
246    result.setValue(Field.BOOKTITLE, "Advances in Kernel Methods - Support Vector Learning");
247    result.setValue(Field.EDITOR, "B. Schoelkopf and C. Burges and A. Smola");
248    result.setValue(Field.PUBLISHER, "MIT Press");
249    result.setValue(Field.URL, "http://research.microsoft.com/~jplatt/smo.html");
250    result.setValue(Field.PDF, "http://research.microsoft.com/~jplatt/smo-book.pdf");
251    result.setValue(Field.PS, "http://research.microsoft.com/~jplatt/smo-book.ps.gz");
252   
253    additional = result.add(Type.ARTICLE);
254    additional.setValue(Field.AUTHOR, "S.S. Keerthi and S.K. Shevade and C. Bhattacharyya and K.R.K. Murthy");
255    additional.setValue(Field.YEAR, "2001");
256    additional.setValue(Field.TITLE, "Improvements to Platt's SMO Algorithm for SVM Classifier Design");
257    additional.setValue(Field.JOURNAL, "Neural Computation");
258    additional.setValue(Field.VOLUME, "13");
259    additional.setValue(Field.NUMBER, "3");
260    additional.setValue(Field.PAGES, "637-649");
261    additional.setValue(Field.PS, "http://guppy.mpe.nus.edu.sg/~mpessk/svm/smo_mod_nc.ps.gz");
262   
263    additional = result.add(Type.INPROCEEDINGS);
264    additional.setValue(Field.AUTHOR, "Trevor Hastie and Robert Tibshirani");
265    additional.setValue(Field.YEAR, "1998");
266    additional.setValue(Field.TITLE, "Classification by Pairwise Coupling");
267    additional.setValue(Field.BOOKTITLE, "Advances in Neural Information Processing Systems");
268    additional.setValue(Field.VOLUME, "10");
269    additional.setValue(Field.PUBLISHER, "MIT Press");
270    additional.setValue(Field.EDITOR, "Michael I. Jordan and Michael J. Kearns and Sara A. Solla");
271    additional.setValue(Field.PS, "http://www-stat.stanford.edu/~hastie/Papers/2class.ps");
272   
273    return result;
274  }
275
276  /**
277   * Class for building a binary support vector machine.
278   */
279  public class BinarySMO 
280    implements Serializable {
281   
282    /** for serialization */
283    static final long serialVersionUID = -8246163625699362456L;
284   
285    /** The Lagrange multipliers. */
286    protected double[] m_alpha;
287
288    /** The thresholds. */
289    protected double m_b, m_bLow, m_bUp;
290
291    /** The indices for m_bLow and m_bUp */
292    protected int m_iLow, m_iUp;
293
294    /** The training data. */
295    protected Instances m_data;
296
297    /** Weight vector for linear machine. */
298    protected double[] m_weights;
299
300    /** Variables to hold weight vector in sparse form.
301        (To reduce storage requirements.) */
302    protected double[] m_sparseWeights;
303    protected int[] m_sparseIndices;
304
305    /** Kernel to use **/
306    protected Kernel m_kernel;
307
308    /** The transformed class values. */
309    protected double[] m_class;
310
311    /** The current set of errors for all non-bound examples. */
312    protected double[] m_errors;
313
314    /* The five different sets used by the algorithm. */
315    /** {i: 0 < m_alpha[i] < C} */
316    protected SMOset m_I0;
317    /**  {i: m_class[i] = 1, m_alpha[i] = 0} */
318    protected SMOset m_I1; 
319    /**  {i: m_class[i] = -1, m_alpha[i] =C} */
320    protected SMOset m_I2; 
321    /** {i: m_class[i] = 1, m_alpha[i] = C} */
322    protected SMOset m_I3;
323    /**  {i: m_class[i] = -1, m_alpha[i] = 0} */
324    protected SMOset m_I4; 
325
326    /** The set of support vectors */
327    protected SMOset m_supportVectors; // {i: 0 < m_alpha[i]}
328
329    /** Stores logistic regression model for probability estimate */
330    protected Logistic m_logistic = null;
331
332    /** Stores the weight of the training instances */
333    protected double m_sumOfWeights = 0;
334
335    /**
336     * Fits logistic regression model to SVM outputs analogue
337     * to John Platt's method. 
338     *
339     * @param insts the set of training instances
340     * @param cl1 the first class' index
341     * @param cl2 the second class' index
342     * @param numFolds the number of folds for cross-validation
343     * @param random for randomizing the data
344     * @throws Exception if the sigmoid can't be fit successfully
345     */
346    protected void fitLogistic(Instances insts, int cl1, int cl2,
347                             int numFolds, Random random) 
348      throws Exception {
349
350      // Create header of instances object
351      FastVector atts = new FastVector(2);
352      atts.addElement(new Attribute("pred"));
353      FastVector attVals = new FastVector(2);
354      attVals.addElement(insts.classAttribute().value(cl1));
355      attVals.addElement(insts.classAttribute().value(cl2));
356      atts.addElement(new Attribute("class", attVals));
357      Instances data = new Instances("data", atts, insts.numInstances());
358      data.setClassIndex(1);
359
360      // Collect data for fitting the logistic model
361      if (numFolds <= 0) {
362
363        // Use training data
364        for (int j = 0; j < insts.numInstances(); j++) {
365          Instance inst = insts.instance(j);
366          double[] vals = new double[2];
367          vals[0] = SVMOutput(-1, inst);
368          if (inst.classValue() == cl2) {
369            vals[1] = 1;
370          }
371          data.add(new DenseInstance(inst.weight(), vals));
372        }
373      } else {
374
375        // Check whether number of folds too large
376        if (numFolds > insts.numInstances()) {
377          numFolds = insts.numInstances();
378        }
379
380        // Make copy of instances because we will shuffle them around
381        insts = new Instances(insts);
382       
383        // Perform three-fold cross-validation to collect
384        // unbiased predictions
385        insts.randomize(random);
386        insts.stratify(numFolds);
387        for (int i = 0; i < numFolds; i++) {
388          Instances train = insts.trainCV(numFolds, i, random);
389          /*      SerializedObject so = new SerializedObject(this);
390                  BinarySMO smo = (BinarySMO)so.getObject(); */
391          BinarySMO smo = new BinarySMO();
392          smo.setKernel(Kernel.makeCopy(SMO.this.m_kernel));
393          smo.buildClassifier(train, cl1, cl2, false, -1, -1);
394          Instances test = insts.testCV(numFolds, i);
395          for (int j = 0; j < test.numInstances(); j++) {
396            double[] vals = new double[2];
397            vals[0] = smo.SVMOutput(-1, test.instance(j));
398            if (test.instance(j).classValue() == cl2) {
399              vals[1] = 1;
400            }
401            data.add(new DenseInstance(test.instance(j).weight(), vals));
402          }
403        }
404      }
405
406      // Build logistic regression model
407      m_logistic = new Logistic();
408      m_logistic.buildClassifier(data);
409    }
410   
411    /**
412     * sets the kernel to use
413     *
414     * @param value     the kernel to use
415     */
416    public void setKernel(Kernel value) {
417      m_kernel = value;
418    }
419   
420    /**
421     * Returns the kernel to use
422     *
423     * @return          the current kernel
424     */
425    public Kernel getKernel() {
426      return m_kernel;
427    }
428
429    /**
430     * Method for building the binary classifier.
431     *
432     * @param insts the set of training instances
433     * @param cl1 the first class' index
434     * @param cl2 the second class' index
435     * @param fitLogistic true if logistic model is to be fit
436     * @param numFolds number of folds for internal cross-validation
437     * @param randomSeed random number generator for cross-validation
438     * @throws Exception if the classifier can't be built successfully
439     */
440    protected void buildClassifier(Instances insts, int cl1, int cl2,
441                                 boolean fitLogistic, int numFolds,
442                                 int randomSeed) throws Exception {
443     
444      // Initialize some variables
445      m_bUp = -1; m_bLow = 1; m_b = 0; 
446      m_alpha = null; m_data = null; m_weights = null; m_errors = null;
447      m_logistic = null; m_I0 = null; m_I1 = null; m_I2 = null;
448      m_I3 = null; m_I4 = null; m_sparseWeights = null; m_sparseIndices = null;
449
450      // Store the sum of weights
451      m_sumOfWeights = insts.sumOfWeights();
452     
453      // Set class values
454      m_class = new double[insts.numInstances()];
455      m_iUp = -1; m_iLow = -1;
456      for (int i = 0; i < m_class.length; i++) {
457        if ((int) insts.instance(i).classValue() == cl1) {
458          m_class[i] = -1; m_iLow = i;
459        } else if ((int) insts.instance(i).classValue() == cl2) {
460          m_class[i] = 1; m_iUp = i;
461        } else {
462          throw new Exception ("This should never happen!");
463        }
464      }
465
466      // Check whether one or both classes are missing
467      if ((m_iUp == -1) || (m_iLow == -1)) {
468        if (m_iUp != -1) {
469          m_b = -1;
470        } else if (m_iLow != -1) {
471          m_b = 1;
472        } else {
473          m_class = null;
474          return;
475        }
476        if (m_KernelIsLinear) {
477          m_sparseWeights = new double[0];
478          m_sparseIndices = new int[0];
479          m_class = null;
480        } else {
481          m_supportVectors = new SMOset(0);
482          m_alpha = new double[0];
483          m_class = new double[0];
484        }
485
486        // Fit sigmoid if requested
487        if (fitLogistic) {
488          fitLogistic(insts, cl1, cl2, numFolds, new Random(randomSeed));
489        }
490        return;
491      }
492     
493      // Set the reference to the data
494      m_data = insts;
495
496      // If machine is linear, reserve space for weights
497      if (m_KernelIsLinear) {
498        m_weights = new double[m_data.numAttributes()];
499      } else {
500        m_weights = null;
501      }
502     
503      // Initialize alpha array to zero
504      m_alpha = new double[m_data.numInstances()];
505     
506      // Initialize sets
507      m_supportVectors = new SMOset(m_data.numInstances());
508      m_I0 = new SMOset(m_data.numInstances());
509      m_I1 = new SMOset(m_data.numInstances());
510      m_I2 = new SMOset(m_data.numInstances());
511      m_I3 = new SMOset(m_data.numInstances());
512      m_I4 = new SMOset(m_data.numInstances());
513
514      // Clean out some instance variables
515      m_sparseWeights = null;
516      m_sparseIndices = null;
517     
518      // init kernel
519      m_kernel.buildKernel(m_data);
520     
521      // Initialize error cache
522      m_errors = new double[m_data.numInstances()];
523      m_errors[m_iLow] = 1; m_errors[m_iUp] = -1;
524     
525      // Build up I1 and I4
526      for (int i = 0; i < m_class.length; i++ ) {
527        if (m_class[i] == 1) {
528          m_I1.insert(i);
529        } else {
530          m_I4.insert(i);
531        }
532      }
533     
534      // Loop to find all the support vectors
535      int numChanged = 0;
536      boolean examineAll = true;
537      while ((numChanged > 0) || examineAll) {
538        numChanged = 0;
539        if (examineAll) {
540          for (int i = 0; i < m_alpha.length; i++) {
541            if (examineExample(i)) {
542              numChanged++;
543            }
544          }
545        } else {
546         
547          // This code implements Modification 1 from Keerthi et al.'s paper
548          for (int i = 0; i < m_alpha.length; i++) {
549            if ((m_alpha[i] > 0) && 
550                (m_alpha[i] < m_C * m_data.instance(i).weight())) {
551              if (examineExample(i)) {
552                numChanged++;
553              }
554             
555              // Is optimality on unbound vectors obtained?
556              if (m_bUp > m_bLow - 2 * m_tol) {
557                numChanged = 0;
558                break;
559              }
560            }
561          }
562         
563          //This is the code for Modification 2 from Keerthi et al.'s paper
564          /*boolean innerLoopSuccess = true;
565            numChanged = 0;
566            while ((m_bUp < m_bLow - 2 * m_tol) && (innerLoopSuccess == true)) {
567            innerLoopSuccess = takeStep(m_iUp, m_iLow, m_errors[m_iLow]);
568            }*/
569        }
570       
571        if (examineAll) {
572          examineAll = false;
573        } else if (numChanged == 0) {
574          examineAll = true;
575        }
576      }
577     
578      // Set threshold
579      m_b = (m_bLow + m_bUp) / 2.0;
580     
581      // Save memory
582      m_kernel.clean(); 
583     
584      m_errors = null;
585      m_I0 = m_I1 = m_I2 = m_I3 = m_I4 = null;
586     
587      // If machine is linear, delete training data
588      // and store weight vector in sparse format
589      if (m_KernelIsLinear) {
590       
591        // We don't need to store the set of support vectors
592        m_supportVectors = null;
593
594        // We don't need to store the class values either
595        m_class = null;
596       
597        // Clean out training data
598        if (!m_checksTurnedOff) {
599          m_data = new Instances(m_data, 0);
600        } else {
601          m_data = null;
602        }
603       
604        // Convert weight vector
605        double[] sparseWeights = new double[m_weights.length];
606        int[] sparseIndices = new int[m_weights.length];
607        int counter = 0;
608        for (int i = 0; i < m_weights.length; i++) {
609          if (m_weights[i] != 0.0) {
610            sparseWeights[counter] = m_weights[i];
611            sparseIndices[counter] = i;
612            counter++;
613          }
614        }
615        m_sparseWeights = new double[counter];
616        m_sparseIndices = new int[counter];
617        System.arraycopy(sparseWeights, 0, m_sparseWeights, 0, counter);
618        System.arraycopy(sparseIndices, 0, m_sparseIndices, 0, counter);
619       
620        // Clean out weight vector
621        m_weights = null;
622       
623        // We don't need the alphas in the linear case
624        m_alpha = null;
625      }
626     
627      // Fit sigmoid if requested
628      if (fitLogistic) {
629        fitLogistic(insts, cl1, cl2, numFolds, new Random(randomSeed));
630      }
631
632    }
633   
634    /**
635     * Computes SVM output for given instance.
636     *
637     * @param index the instance for which output is to be computed
638     * @param inst the instance
639     * @return the output of the SVM for the given instance
640     * @throws Exception in case of an error
641     */
642    public double SVMOutput(int index, Instance inst) throws Exception {
643     
644      double result = 0;
645     
646      // Is the machine linear?
647      if (m_KernelIsLinear) {
648       
649        // Is weight vector stored in sparse format?
650        if (m_sparseWeights == null) {
651          int n1 = inst.numValues(); 
652          for (int p = 0; p < n1; p++) {
653            if (inst.index(p) != m_classIndex) {
654              result += m_weights[inst.index(p)] * inst.valueSparse(p);
655            }
656          }
657        } else {
658          int n1 = inst.numValues(); int n2 = m_sparseWeights.length;
659          for (int p1 = 0, p2 = 0; p1 < n1 && p2 < n2;) {
660            int ind1 = inst.index(p1); 
661            int ind2 = m_sparseIndices[p2];
662            if (ind1 == ind2) {
663              if (ind1 != m_classIndex) {
664                result += inst.valueSparse(p1) * m_sparseWeights[p2];
665              }
666              p1++; p2++;
667            } else if (ind1 > ind2) {
668              p2++;
669            } else { 
670              p1++;
671            }
672          }
673        }
674      } else {
675        for (int i = m_supportVectors.getNext(-1); i != -1; 
676             i = m_supportVectors.getNext(i)) {
677          result += m_class[i] * m_alpha[i] * m_kernel.eval(index, i, inst);
678        }
679      }
680      result -= m_b;
681     
682      return result;
683    }
684
685    /**
686     * Prints out the classifier.
687     *
688     * @return a description of the classifier as a string
689     */
690    public String toString() {
691
692      StringBuffer text = new StringBuffer();
693      int printed = 0;
694
695      if ((m_alpha == null) && (m_sparseWeights == null)) {
696        return "BinarySMO: No model built yet.\n";
697      }
698      try {
699        text.append("BinarySMO\n\n");
700
701        // If machine linear, print weight vector
702        if (m_KernelIsLinear) {
703          text.append("Machine linear: showing attribute weights, ");
704          text.append("not support vectors.\n\n");
705
706          // We can assume that the weight vector is stored in sparse
707          // format because the classifier has been built
708          for (int i = 0; i < m_sparseWeights.length; i++) {
709            if (m_sparseIndices[i] != (int)m_classIndex) {
710              if (printed > 0) {
711                text.append(" + ");
712              } else {
713                text.append("   ");
714              }
715              text.append(Utils.doubleToString(m_sparseWeights[i], 12, 4) +
716                          " * ");
717              if (m_filterType == FILTER_STANDARDIZE) {
718                text.append("(standardized) ");
719              } else if (m_filterType == FILTER_NORMALIZE) {
720                text.append("(normalized) ");
721              }
722              if (!m_checksTurnedOff) {
723                text.append(m_data.attribute(m_sparseIndices[i]).name()+"\n");
724              } else {
725                text.append("attribute with index " + 
726                            m_sparseIndices[i] +"\n");
727              }
728              printed++;
729            }
730          }
731        } else {
732          for (int i = 0; i < m_alpha.length; i++) {
733            if (m_supportVectors.contains(i)) {
734              double val = m_alpha[i];
735              if (m_class[i] == 1) {
736                if (printed > 0) {
737                  text.append(" + ");
738                }
739              } else {
740                text.append(" - ");
741              }
742              text.append(Utils.doubleToString(val, 12, 4) 
743                          + " * <");
744              for (int j = 0; j < m_data.numAttributes(); j++) {
745                if (j != m_data.classIndex()) {
746                  text.append(m_data.instance(i).toString(j));
747                }
748                if (j != m_data.numAttributes() - 1) {
749                  text.append(" ");
750                }
751              }
752              text.append("> * X]\n");
753              printed++;
754            }
755          }
756        }
757        if (m_b > 0) {
758          text.append(" - " + Utils.doubleToString(m_b, 12, 4));
759        } else {
760          text.append(" + " + Utils.doubleToString(-m_b, 12, 4));
761        }
762
763        if (!m_KernelIsLinear) {
764          text.append("\n\nNumber of support vectors: " + 
765                      m_supportVectors.numElements());
766        }
767        int numEval = 0;
768        int numCacheHits = -1;
769        if (m_kernel != null) {
770          numEval = m_kernel.numEvals();
771          numCacheHits = m_kernel.numCacheHits();
772        }
773        text.append("\n\nNumber of kernel evaluations: " + numEval);
774        if (numCacheHits >= 0 && numEval > 0) {
775          double hitRatio = 1 - numEval*1.0/(numCacheHits+numEval);
776          text.append(" (" + Utils.doubleToString(hitRatio*100, 7, 3).trim() + "% cached)");
777        }
778
779      } catch (Exception e) {
780        e.printStackTrace();
781
782        return "Can't print BinarySMO classifier.";
783      }
784   
785      return text.toString();
786    }
787
788    /**
789     * Examines instance.
790     *
791     * @param i2 index of instance to examine
792     * @return true if examination was successfull
793     * @throws Exception if something goes wrong
794     */
795    protected boolean examineExample(int i2) throws Exception {
796   
797      double y2, F2;
798      int i1 = -1;
799   
800      y2 = m_class[i2];
801      if (m_I0.contains(i2)) {
802        F2 = m_errors[i2];
803      } else {
804        F2 = SVMOutput(i2, m_data.instance(i2)) + m_b - y2;
805        m_errors[i2] = F2;
806     
807        // Update thresholds
808        if ((m_I1.contains(i2) || m_I2.contains(i2)) && (F2 < m_bUp)) {
809          m_bUp = F2; m_iUp = i2;
810        } else if ((m_I3.contains(i2) || m_I4.contains(i2)) && (F2 > m_bLow)) {
811          m_bLow = F2; m_iLow = i2;
812        }
813      }
814
815      // Check optimality using current bLow and bUp and, if
816      // violated, find an index i1 to do joint optimization
817      // with i2...
818      boolean optimal = true;
819      if (m_I0.contains(i2) || m_I1.contains(i2) || m_I2.contains(i2)) {
820        if (m_bLow - F2 > 2 * m_tol) {
821          optimal = false; i1 = m_iLow;
822        }
823      }
824      if (m_I0.contains(i2) || m_I3.contains(i2) || m_I4.contains(i2)) {
825        if (F2 - m_bUp > 2 * m_tol) {
826          optimal = false; i1 = m_iUp;
827        }
828      }
829      if (optimal) {
830        return false;
831      }
832
833      // For i2 unbound choose the better i1...
834      if (m_I0.contains(i2)) {
835        if (m_bLow - F2 > F2 - m_bUp) {
836          i1 = m_iLow;
837        } else {
838          i1 = m_iUp;
839        }
840      }
841      if (i1 == -1) {
842        throw new Exception("This should never happen!");
843      }
844      return takeStep(i1, i2, F2);
845    }
846
847    /**
848     * Method solving for the Lagrange multipliers for
849     * two instances.
850     *
851     * @param i1 index of the first instance
852     * @param i2 index of the second instance
853     * @param F2
854     * @return true if multipliers could be found
855     * @throws Exception if something goes wrong
856     */
857    protected boolean takeStep(int i1, int i2, double F2) throws Exception {
858
859      double alph1, alph2, y1, y2, F1, s, L, H, k11, k12, k22, eta,
860        a1, a2, f1, f2, v1, v2, Lobj, Hobj;
861      double C1 = m_C * m_data.instance(i1).weight();
862      double C2 = m_C * m_data.instance(i2).weight();
863
864      // Don't do anything if the two instances are the same
865      if (i1 == i2) {
866        return false;
867      }
868
869      // Initialize variables
870      alph1 = m_alpha[i1]; alph2 = m_alpha[i2];
871      y1 = m_class[i1]; y2 = m_class[i2];
872      F1 = m_errors[i1];
873      s = y1 * y2;
874
875      // Find the constraints on a2
876      if (y1 != y2) {
877        L = Math.max(0, alph2 - alph1); 
878        H = Math.min(C2, C1 + alph2 - alph1);
879      } else {
880        L = Math.max(0, alph1 + alph2 - C1);
881        H = Math.min(C2, alph1 + alph2);
882      }
883      if (L >= H) {
884        return false;
885      }
886
887      // Compute second derivative of objective function
888      k11 = m_kernel.eval(i1, i1, m_data.instance(i1));
889      k12 = m_kernel.eval(i1, i2, m_data.instance(i1));
890      k22 = m_kernel.eval(i2, i2, m_data.instance(i2));
891      eta = 2 * k12 - k11 - k22;
892
893      // Check if second derivative is negative
894      if (eta < 0) {
895
896        // Compute unconstrained maximum
897        a2 = alph2 - y2 * (F1 - F2) / eta;
898
899        // Compute constrained maximum
900        if (a2 < L) {
901          a2 = L;
902        } else if (a2 > H) {
903          a2 = H;
904        }
905      } else {
906
907        // Look at endpoints of diagonal
908        f1 = SVMOutput(i1, m_data.instance(i1));
909        f2 = SVMOutput(i2, m_data.instance(i2));
910        v1 = f1 + m_b - y1 * alph1 * k11 - y2 * alph2 * k12; 
911        v2 = f2 + m_b - y1 * alph1 * k12 - y2 * alph2 * k22; 
912        double gamma = alph1 + s * alph2;
913        Lobj = (gamma - s * L) + L - 0.5 * k11 * (gamma - s * L) * (gamma - s * L) - 
914          0.5 * k22 * L * L - s * k12 * (gamma - s * L) * L - 
915          y1 * (gamma - s * L) * v1 - y2 * L * v2;
916        Hobj = (gamma - s * H) + H - 0.5 * k11 * (gamma - s * H) * (gamma - s * H) - 
917          0.5 * k22 * H * H - s * k12 * (gamma - s * H) * H - 
918          y1 * (gamma - s * H) * v1 - y2 * H * v2;
919        if (Lobj > Hobj + m_eps) {
920          a2 = L;
921        } else if (Lobj < Hobj - m_eps) {
922          a2 = H;
923        } else {
924          a2 = alph2;
925        }
926      }
927      if (Math.abs(a2 - alph2) < m_eps * (a2 + alph2 + m_eps)) {
928        return false;
929      }
930     
931      // To prevent precision problems
932      if (a2 > C2 - m_Del * C2) {
933        a2 = C2;
934      } else if (a2 <= m_Del * C2) {
935        a2 = 0;
936      }
937     
938      // Recompute a1
939      a1 = alph1 + s * (alph2 - a2);
940     
941      // To prevent precision problems
942      if (a1 > C1 - m_Del * C1) {
943        a1 = C1;
944      } else if (a1 <= m_Del * C1) {
945        a1 = 0;
946      }
947     
948      // Update sets
949      if (a1 > 0) {
950        m_supportVectors.insert(i1);
951      } else {
952        m_supportVectors.delete(i1);
953      }
954      if ((a1 > 0) && (a1 < C1)) {
955        m_I0.insert(i1);
956      } else {
957        m_I0.delete(i1);
958      }
959      if ((y1 == 1) && (a1 == 0)) {
960        m_I1.insert(i1);
961      } else {
962        m_I1.delete(i1);
963      }
964      if ((y1 == -1) && (a1 == C1)) {
965        m_I2.insert(i1);
966      } else {
967        m_I2.delete(i1);
968      }
969      if ((y1 == 1) && (a1 == C1)) {
970        m_I3.insert(i1);
971      } else {
972        m_I3.delete(i1);
973      }
974      if ((y1 == -1) && (a1 == 0)) {
975        m_I4.insert(i1);
976      } else {
977        m_I4.delete(i1);
978      }
979      if (a2 > 0) {
980        m_supportVectors.insert(i2);
981      } else {
982        m_supportVectors.delete(i2);
983      }
984      if ((a2 > 0) && (a2 < C2)) {
985        m_I0.insert(i2);
986      } else {
987        m_I0.delete(i2);
988      }
989      if ((y2 == 1) && (a2 == 0)) {
990        m_I1.insert(i2);
991      } else {
992        m_I1.delete(i2);
993      }
994      if ((y2 == -1) && (a2 == C2)) {
995        m_I2.insert(i2);
996      } else {
997        m_I2.delete(i2);
998      }
999      if ((y2 == 1) && (a2 == C2)) {
1000        m_I3.insert(i2);
1001      } else {
1002        m_I3.delete(i2);
1003      }
1004      if ((y2 == -1) && (a2 == 0)) {
1005        m_I4.insert(i2);
1006      } else {
1007        m_I4.delete(i2);
1008      }
1009     
1010      // Update weight vector to reflect change a1 and a2, if linear SVM
1011      if (m_KernelIsLinear) {
1012        Instance inst1 = m_data.instance(i1);
1013        for (int p1 = 0; p1 < inst1.numValues(); p1++) {
1014          if (inst1.index(p1) != m_data.classIndex()) {
1015            m_weights[inst1.index(p1)] += 
1016              y1 * (a1 - alph1) * inst1.valueSparse(p1);
1017          }
1018        }
1019        Instance inst2 = m_data.instance(i2);
1020        for (int p2 = 0; p2 < inst2.numValues(); p2++) {
1021          if (inst2.index(p2) != m_data.classIndex()) {
1022            m_weights[inst2.index(p2)] += 
1023              y2 * (a2 - alph2) * inst2.valueSparse(p2);
1024          }
1025        }
1026      }
1027     
1028      // Update error cache using new Lagrange multipliers
1029      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
1030        if ((j != i1) && (j != i2)) {
1031          m_errors[j] += 
1032            y1 * (a1 - alph1) * m_kernel.eval(i1, j, m_data.instance(i1)) + 
1033            y2 * (a2 - alph2) * m_kernel.eval(i2, j, m_data.instance(i2));
1034        }
1035      }
1036     
1037      // Update error cache for i1 and i2
1038      m_errors[i1] += y1 * (a1 - alph1) * k11 + y2 * (a2 - alph2) * k12;
1039      m_errors[i2] += y1 * (a1 - alph1) * k12 + y2 * (a2 - alph2) * k22;
1040     
1041      // Update array with Lagrange multipliers
1042      m_alpha[i1] = a1;
1043      m_alpha[i2] = a2;
1044     
1045      // Update thresholds
1046      m_bLow = -Double.MAX_VALUE; m_bUp = Double.MAX_VALUE;
1047      m_iLow = -1; m_iUp = -1;
1048      for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
1049        if (m_errors[j] < m_bUp) {
1050          m_bUp = m_errors[j]; m_iUp = j;
1051        }
1052        if (m_errors[j] > m_bLow) {
1053          m_bLow = m_errors[j]; m_iLow = j;
1054        }
1055      }
1056      if (!m_I0.contains(i1)) {
1057        if (m_I3.contains(i1) || m_I4.contains(i1)) {
1058          if (m_errors[i1] > m_bLow) {
1059            m_bLow = m_errors[i1]; m_iLow = i1;
1060          } 
1061        } else {
1062          if (m_errors[i1] < m_bUp) {
1063            m_bUp = m_errors[i1]; m_iUp = i1;
1064          }
1065        }
1066      }
1067      if (!m_I0.contains(i2)) {
1068        if (m_I3.contains(i2) || m_I4.contains(i2)) {
1069          if (m_errors[i2] > m_bLow) {
1070            m_bLow = m_errors[i2]; m_iLow = i2;
1071          }
1072        } else {
1073          if (m_errors[i2] < m_bUp) {
1074            m_bUp = m_errors[i2]; m_iUp = i2;
1075          }
1076        }
1077      }
1078      if ((m_iLow == -1) || (m_iUp == -1)) {
1079        throw new Exception("This should never happen!");
1080      }
1081
1082      // Made some progress.
1083      return true;
1084    }
1085 
1086    /**
1087     * Quick and dirty check whether the quadratic programming problem is solved.
1088     *
1089     * @throws Exception if checking fails
1090     */
1091    protected void checkClassifier() throws Exception {
1092
1093      double sum = 0;
1094      for (int i = 0; i < m_alpha.length; i++) {
1095        if (m_alpha[i] > 0) {
1096          sum += m_class[i] * m_alpha[i];
1097        }
1098      }
1099      System.err.println("Sum of y(i) * alpha(i): " + sum);
1100
1101      for (int i = 0; i < m_alpha.length; i++) {
1102        double output = SVMOutput(i, m_data.instance(i));
1103        if (Utils.eq(m_alpha[i], 0)) {
1104          if (Utils.sm(m_class[i] * output, 1)) {
1105            System.err.println("KKT condition 1 violated: " + m_class[i] * output);
1106          }
1107        } 
1108        if (Utils.gr(m_alpha[i], 0) && 
1109            Utils.sm(m_alpha[i], m_C * m_data.instance(i).weight())) {
1110          if (!Utils.eq(m_class[i] * output, 1)) {
1111            System.err.println("KKT condition 2 violated: " + m_class[i] * output);
1112          }
1113        } 
1114        if (Utils.eq(m_alpha[i], m_C * m_data.instance(i).weight())) {
1115          if (Utils.gr(m_class[i] * output, 1)) {
1116            System.err.println("KKT condition 3 violated: " + m_class[i] * output);
1117          }
1118        } 
1119      }
1120    } 
1121   
1122    /**
1123     * Returns the revision string.
1124     *
1125     * @return          the revision
1126     */
1127    public String getRevision() {
1128      return RevisionUtils.extract("$Revision: 6024 $");
1129    }
1130  }
1131
1132  /** filter: Normalize training data */
1133  public static final int FILTER_NORMALIZE = 0;
1134  /** filter: Standardize training data */
1135  public static final int FILTER_STANDARDIZE = 1;
1136  /** filter: No normalization/standardization */
1137  public static final int FILTER_NONE = 2;
1138  /** The filter to apply to the training data */
1139  public static final Tag [] TAGS_FILTER = {
1140    new Tag(FILTER_NORMALIZE, "Normalize training data"),
1141    new Tag(FILTER_STANDARDIZE, "Standardize training data"),
1142    new Tag(FILTER_NONE, "No normalization/standardization"),
1143  };
1144
1145  /** The binary classifier(s) */
1146  protected BinarySMO[][] m_classifiers = null;
1147 
1148  /** The complexity parameter. */
1149  protected double m_C = 1.0;
1150 
1151  /** Epsilon for rounding. */
1152  protected double m_eps = 1.0e-12;
1153 
1154  /** Tolerance for accuracy of result. */
1155  protected double m_tol = 1.0e-3;
1156
1157  /** Whether to normalize/standardize/neither */
1158  protected int m_filterType = FILTER_NORMALIZE;
1159
1160  /** The filter used to make attributes numeric. */
1161  protected NominalToBinary m_NominalToBinary;
1162
1163  /** The filter used to standardize/normalize all values. */
1164  protected Filter m_Filter = null;
1165
1166  /** The filter used to get rid of missing values. */
1167  protected ReplaceMissingValues m_Missing;
1168
1169  /** The class index from the training data */
1170  protected int m_classIndex = -1;
1171
1172  /** The class attribute */
1173  protected Attribute m_classAttribute;
1174 
1175  /** whether the kernel is a linear one */
1176  protected boolean m_KernelIsLinear = false;
1177
1178  /** Turn off all checks and conversions? Turning them off assumes
1179      that data is purely numeric, doesn't contain any missing values,
1180      and has a nominal class. Turning them off also means that
1181      no header information will be stored if the machine is linear.
1182      Finally, it also assumes that no instance has a weight equal to 0.*/
1183  protected boolean m_checksTurnedOff;
1184
1185  /** Precision constant for updating sets */
1186  protected static double m_Del = 1000 * Double.MIN_VALUE;
1187
1188  /** Whether logistic models are to be fit */
1189  protected boolean m_fitLogisticModels = false;
1190
1191  /** The number of folds for the internal cross-validation */
1192  protected int m_numFolds = -1;
1193
1194  /** The random number seed  */
1195  protected int m_randomSeed = 1;
1196
1197  /** the kernel to use */
1198  protected Kernel m_kernel = new PolyKernel();
1199 
1200  /**
1201   * Turns off checks for missing values, etc. Use with caution.
1202   */
1203  public void turnChecksOff() {
1204
1205    m_checksTurnedOff = true;
1206  }
1207
1208  /**
1209   * Turns on checks for missing values, etc.
1210   */
1211  public void turnChecksOn() {
1212
1213    m_checksTurnedOff = false;
1214  }
1215
1216  /**
1217   * Returns default capabilities of the classifier.
1218   *
1219   * @return      the capabilities of this classifier
1220   */
1221  public Capabilities getCapabilities() {
1222    Capabilities result = getKernel().getCapabilities();
1223    result.setOwner(this);
1224   
1225    // attribute
1226    result.enableAllAttributeDependencies();
1227    // with NominalToBinary we can also handle nominal attributes, but only
1228    // if the kernel can handle numeric attributes
1229    if (result.handles(Capability.NUMERIC_ATTRIBUTES))
1230      result.enable(Capability.NOMINAL_ATTRIBUTES);
1231    result.enable(Capability.MISSING_VALUES);
1232   
1233    // class
1234    result.disableAllClasses();
1235    result.disableAllClassDependencies();
1236    result.enable(Capability.NOMINAL_CLASS);
1237    result.enable(Capability.MISSING_CLASS_VALUES);
1238   
1239    return result;
1240  }
1241
1242  /**
1243   * Method for building the classifier. Implements a one-against-one
1244   * wrapper for multi-class problems.
1245   *
1246   * @param insts the set of training instances
1247   * @throws Exception if the classifier can't be built successfully
1248   */
1249  public void buildClassifier(Instances insts) throws Exception {
1250
1251    if (!m_checksTurnedOff) {
1252      // can classifier handle the data?
1253      getCapabilities().testWithFail(insts);
1254
1255      // remove instances with missing class
1256      insts = new Instances(insts);
1257      insts.deleteWithMissingClass();
1258     
1259      /* Removes all the instances with weight equal to 0.
1260       MUST be done since condition (8) of Keerthi's paper
1261       is made with the assertion Ci > 0 (See equation (3a). */
1262      Instances data = new Instances(insts, insts.numInstances());
1263      for(int i = 0; i < insts.numInstances(); i++){
1264        if(insts.instance(i).weight() > 0)
1265          data.add(insts.instance(i));
1266      }
1267      if (data.numInstances() == 0) {
1268        throw new Exception("No training instances left after removing " + 
1269        "instances with weight 0!");
1270      }
1271      insts = data;
1272    }
1273
1274    if (!m_checksTurnedOff) {
1275      m_Missing = new ReplaceMissingValues();
1276      m_Missing.setInputFormat(insts);
1277      insts = Filter.useFilter(insts, m_Missing); 
1278    } else {
1279      m_Missing = null;
1280    }
1281
1282    if (getCapabilities().handles(Capability.NUMERIC_ATTRIBUTES)) {
1283      boolean onlyNumeric = true;
1284      if (!m_checksTurnedOff) {
1285        for (int i = 0; i < insts.numAttributes(); i++) {
1286          if (i != insts.classIndex()) {
1287            if (!insts.attribute(i).isNumeric()) {
1288              onlyNumeric = false;
1289              break;
1290            }
1291          }
1292        }
1293      }
1294     
1295      if (!onlyNumeric) {
1296        m_NominalToBinary = new NominalToBinary();
1297        m_NominalToBinary.setInputFormat(insts);
1298        insts = Filter.useFilter(insts, m_NominalToBinary);
1299      } 
1300      else {
1301        m_NominalToBinary = null;
1302      }
1303    }
1304    else {
1305      m_NominalToBinary = null;
1306    }
1307
1308    if (m_filterType == FILTER_STANDARDIZE) {
1309      m_Filter = new Standardize();
1310      m_Filter.setInputFormat(insts);
1311      insts = Filter.useFilter(insts, m_Filter); 
1312    } else if (m_filterType == FILTER_NORMALIZE) {
1313      m_Filter = new Normalize();
1314      m_Filter.setInputFormat(insts);
1315      insts = Filter.useFilter(insts, m_Filter); 
1316    } else {
1317      m_Filter = null;
1318    }
1319
1320    m_classIndex = insts.classIndex();
1321    m_classAttribute = insts.classAttribute();
1322    m_KernelIsLinear = (m_kernel instanceof PolyKernel) && (((PolyKernel) m_kernel).getExponent() == 1.0);
1323   
1324    // Generate subsets representing each class
1325    Instances[] subsets = new Instances[insts.numClasses()];
1326    for (int i = 0; i < insts.numClasses(); i++) {
1327      subsets[i] = new Instances(insts, insts.numInstances());
1328    }
1329    for (int j = 0; j < insts.numInstances(); j++) {
1330      Instance inst = insts.instance(j);
1331      subsets[(int)inst.classValue()].add(inst);
1332    }
1333    for (int i = 0; i < insts.numClasses(); i++) {
1334      subsets[i].compactify();
1335    }
1336
1337    // Build the binary classifiers
1338    Random rand = new Random(m_randomSeed);
1339    m_classifiers = new BinarySMO[insts.numClasses()][insts.numClasses()];
1340    for (int i = 0; i < insts.numClasses(); i++) {
1341      for (int j = i + 1; j < insts.numClasses(); j++) {
1342        m_classifiers[i][j] = new BinarySMO();
1343        m_classifiers[i][j].setKernel(Kernel.makeCopy(getKernel()));
1344        Instances data = new Instances(insts, insts.numInstances());
1345        for (int k = 0; k < subsets[i].numInstances(); k++) {
1346          data.add(subsets[i].instance(k));
1347        }
1348        for (int k = 0; k < subsets[j].numInstances(); k++) {
1349          data.add(subsets[j].instance(k));
1350        }
1351        data.compactify();
1352        data.randomize(rand);
1353        m_classifiers[i][j].buildClassifier(data, i, j, 
1354                                            m_fitLogisticModels,
1355                                            m_numFolds, m_randomSeed);
1356      }
1357    }
1358  }
1359
1360  /**
1361   * Estimates class probabilities for given instance.
1362   *
1363   * @param inst the instance to compute the probabilities for
1364   * @throws Exception in case of an error
1365   */
1366  public double[] distributionForInstance(Instance inst) throws Exception {
1367
1368    // Filter instance
1369    if (!m_checksTurnedOff) {
1370      m_Missing.input(inst);
1371      m_Missing.batchFinished();
1372      inst = m_Missing.output();
1373    }
1374
1375    if (m_NominalToBinary != null) {
1376      m_NominalToBinary.input(inst);
1377      m_NominalToBinary.batchFinished();
1378      inst = m_NominalToBinary.output();
1379    }
1380   
1381    if (m_Filter != null) {
1382      m_Filter.input(inst);
1383      m_Filter.batchFinished();
1384      inst = m_Filter.output();
1385    }
1386   
1387    if (!m_fitLogisticModels) {
1388      double[] result = new double[inst.numClasses()];
1389      for (int i = 0; i < inst.numClasses(); i++) {
1390        for (int j = i + 1; j < inst.numClasses(); j++) {
1391          if ((m_classifiers[i][j].m_alpha != null) || 
1392              (m_classifiers[i][j].m_sparseWeights != null)) {
1393            double output = m_classifiers[i][j].SVMOutput(-1, inst);
1394            if (output > 0) {
1395              result[j] += 1;
1396            } else {
1397              result[i] += 1;
1398            }
1399          }
1400        } 
1401      }
1402      Utils.normalize(result);
1403      return result;
1404    } else {
1405
1406      // We only need to do pairwise coupling if there are more
1407      // then two classes.
1408      if (inst.numClasses() == 2) {
1409        double[] newInst = new double[2];
1410        newInst[0] = m_classifiers[0][1].SVMOutput(-1, inst);
1411        newInst[1] = Utils.missingValue();
1412        return m_classifiers[0][1].m_logistic.
1413          distributionForInstance(new DenseInstance(1, newInst));
1414      }
1415      double[][] r = new double[inst.numClasses()][inst.numClasses()];
1416      double[][] n = new double[inst.numClasses()][inst.numClasses()];
1417      for (int i = 0; i < inst.numClasses(); i++) {
1418        for (int j = i + 1; j < inst.numClasses(); j++) {
1419          if ((m_classifiers[i][j].m_alpha != null) || 
1420              (m_classifiers[i][j].m_sparseWeights != null)) {
1421            double[] newInst = new double[2];
1422            newInst[0] = m_classifiers[i][j].SVMOutput(-1, inst);
1423            newInst[1] = Utils.missingValue();
1424            r[i][j] = m_classifiers[i][j].m_logistic.
1425              distributionForInstance(new DenseInstance(1, newInst))[0];
1426            n[i][j] = m_classifiers[i][j].m_sumOfWeights;
1427          }
1428        }
1429      }
1430      return weka.classifiers.meta.MultiClassClassifier.pairwiseCoupling(n, r);
1431    }
1432  }
1433
1434  /**
1435   * Returns an array of votes for the given instance.
1436   * @param inst the instance
1437   * @return array of votex
1438   * @throws Exception if something goes wrong
1439   */
1440  public int[] obtainVotes(Instance inst) throws Exception {
1441
1442    // Filter instance
1443    if (!m_checksTurnedOff) {
1444      m_Missing.input(inst);
1445      m_Missing.batchFinished();
1446      inst = m_Missing.output();
1447    }
1448
1449    if (m_NominalToBinary != null) {
1450      m_NominalToBinary.input(inst);
1451      m_NominalToBinary.batchFinished();
1452      inst = m_NominalToBinary.output();
1453    }
1454   
1455    if (m_Filter != null) {
1456      m_Filter.input(inst);
1457      m_Filter.batchFinished();
1458      inst = m_Filter.output();
1459    }
1460
1461    int[] votes = new int[inst.numClasses()];
1462    for (int i = 0; i < inst.numClasses(); i++) {
1463      for (int j = i + 1; j < inst.numClasses(); j++) {
1464        double output = m_classifiers[i][j].SVMOutput(-1, inst);
1465        if (output > 0) {
1466          votes[j] += 1;
1467        } else {
1468          votes[i] += 1;
1469        }
1470      }
1471    }
1472    return votes;
1473  }
1474
1475  /**
1476   * Returns the weights in sparse format.
1477   */
1478  public double [][][] sparseWeights() {
1479   
1480    int numValues = m_classAttribute.numValues();
1481    double [][][] sparseWeights = new double[numValues][numValues][];
1482   
1483    for (int i = 0; i < numValues; i++) {
1484      for (int j = i + 1; j < numValues; j++) {
1485        sparseWeights[i][j] = m_classifiers[i][j].m_sparseWeights;
1486      }
1487    }
1488   
1489    return sparseWeights;
1490  }
1491 
1492  /**
1493   * Returns the indices in sparse format.
1494   */
1495  public int [][][] sparseIndices() {
1496   
1497    int numValues = m_classAttribute.numValues();
1498    int [][][] sparseIndices = new int[numValues][numValues][];
1499
1500    for (int i = 0; i < numValues; i++) {
1501      for (int j = i + 1; j < numValues; j++) {
1502        sparseIndices[i][j] = m_classifiers[i][j].m_sparseIndices;
1503      }
1504    }
1505   
1506    return sparseIndices;
1507  }
1508 
1509  /**
1510   * Returns the bias of each binary SMO.
1511   */
1512  public double [][] bias() {
1513   
1514    int numValues = m_classAttribute.numValues();
1515    double [][] bias = new double[numValues][numValues];
1516
1517    for (int i = 0; i < numValues; i++) {
1518      for (int j = i + 1; j < numValues; j++) {
1519        bias[i][j] = m_classifiers[i][j].m_b;
1520      }
1521    }
1522   
1523    return bias;
1524  }
1525 
1526  /*
1527   * Returns the number of values of the class attribute.
1528   */
1529  public int numClassAttributeValues() {
1530
1531    return m_classAttribute.numValues();
1532  }
1533 
1534  /*
1535   * Returns the names of the class attributes.
1536   */
1537  public String [] classAttributeNames() {
1538
1539    int numValues = m_classAttribute.numValues();
1540   
1541    String [] classAttributeNames = new String[numValues];
1542   
1543    for (int i = 0; i < numValues; i++) {
1544      classAttributeNames[i] = m_classAttribute.value(i);
1545    }
1546   
1547    return classAttributeNames;
1548  }
1549 
1550  /**
1551   * Returns the attribute names.
1552   */
1553  public String [][][] attributeNames() {
1554   
1555    int numValues = m_classAttribute.numValues();
1556    String [][][] attributeNames = new String[numValues][numValues][];
1557   
1558    for (int i = 0; i < numValues; i++) {
1559      for (int j = i + 1; j < numValues; j++) {
1560        //      int numAttributes = m_classifiers[i][j].m_data.numAttributes();
1561        int numAttributes = m_classifiers[i][j].m_sparseIndices.length;
1562        String [] attrNames = new String[numAttributes];
1563        for (int k = 0; k < numAttributes; k++) {
1564          attrNames[k] = m_classifiers[i][j].
1565            m_data.attribute(m_classifiers[i][j].m_sparseIndices[k]).name();
1566        }
1567        attributeNames[i][j] = attrNames;         
1568      }
1569    }
1570    return attributeNames;
1571  }
1572 
1573  /**
1574   * Returns an enumeration describing the available options.
1575   *
1576   * @return an enumeration of all the available options.
1577   */
1578  public Enumeration listOptions() {
1579
1580    Vector result = new Vector();
1581
1582    Enumeration enm = super.listOptions();
1583    while (enm.hasMoreElements())
1584      result.addElement(enm.nextElement());
1585
1586    result.addElement(new Option(
1587        "\tTurns off all checks - use with caution!\n"
1588        + "\tTurning them off assumes that data is purely numeric, doesn't\n"
1589        + "\tcontain any missing values, and has a nominal class. Turning them\n"
1590        + "\toff also means that no header information will be stored if the\n"
1591        + "\tmachine is linear. Finally, it also assumes that no instance has\n"
1592        + "\ta weight equal to 0.\n"
1593        + "\t(default: checks on)",
1594        "no-checks", 0, "-no-checks"));
1595
1596    result.addElement(new Option(
1597        "\tThe complexity constant C. (default 1)",
1598        "C", 1, "-C <double>"));
1599   
1600    result.addElement(new Option(
1601        "\tWhether to 0=normalize/1=standardize/2=neither. " +
1602        "(default 0=normalize)",
1603        "N", 1, "-N"));
1604   
1605    result.addElement(new Option(
1606        "\tThe tolerance parameter. " +
1607        "(default 1.0e-3)",
1608        "L", 1, "-L <double>"));
1609   
1610    result.addElement(new Option(
1611        "\tThe epsilon for round-off error. " +
1612        "(default 1.0e-12)",
1613        "P", 1, "-P <double>"));
1614   
1615    result.addElement(new Option(
1616        "\tFit logistic models to SVM outputs. ",
1617        "M", 0, "-M"));
1618   
1619    result.addElement(new Option(
1620        "\tThe number of folds for the internal\n" +
1621        "\tcross-validation. " +
1622        "(default -1, use training data)",
1623        "V", 1, "-V <double>"));
1624   
1625    result.addElement(new Option(
1626        "\tThe random number seed. " +
1627        "(default 1)",
1628        "W", 1, "-W <double>"));
1629   
1630    result.addElement(new Option(
1631        "\tThe Kernel to use.\n"
1632        + "\t(default: weka.classifiers.functions.supportVector.PolyKernel)",
1633        "K", 1, "-K <classname and parameters>"));
1634
1635    result.addElement(new Option(
1636        "",
1637        "", 0, "\nOptions specific to kernel "
1638        + getKernel().getClass().getName() + ":"));
1639   
1640    enm = ((OptionHandler) getKernel()).listOptions();
1641    while (enm.hasMoreElements())
1642      result.addElement(enm.nextElement());
1643
1644    return result.elements();
1645  }
1646
1647  /**
1648   * Parses a given list of options. <p/>
1649   *
1650   <!-- options-start -->
1651   * Valid options are: <p/>
1652   *
1653   * <pre> -D
1654   *  If set, classifier is run in debug mode and
1655   *  may output additional info to the console</pre>
1656   *
1657   * <pre> -no-checks
1658   *  Turns off all checks - use with caution!
1659   *  Turning them off assumes that data is purely numeric, doesn't
1660   *  contain any missing values, and has a nominal class. Turning them
1661   *  off also means that no header information will be stored if the
1662   *  machine is linear. Finally, it also assumes that no instance has
1663   *  a weight equal to 0.
1664   *  (default: checks on)</pre>
1665   *
1666   * <pre> -C &lt;double&gt;
1667   *  The complexity constant C. (default 1)</pre>
1668   *
1669   * <pre> -N
1670   *  Whether to 0=normalize/1=standardize/2=neither. (default 0=normalize)</pre>
1671   *
1672   * <pre> -L &lt;double&gt;
1673   *  The tolerance parameter. (default 1.0e-3)</pre>
1674   *
1675   * <pre> -P &lt;double&gt;
1676   *  The epsilon for round-off error. (default 1.0e-12)</pre>
1677   *
1678   * <pre> -M
1679   *  Fit logistic models to SVM outputs. </pre>
1680   *
1681   * <pre> -V &lt;double&gt;
1682   *  The number of folds for the internal
1683   *  cross-validation. (default -1, use training data)</pre>
1684   *
1685   * <pre> -W &lt;double&gt;
1686   *  The random number seed. (default 1)</pre>
1687   *
1688   * <pre> -K &lt;classname and parameters&gt;
1689   *  The Kernel to use.
1690   *  (default: weka.classifiers.functions.supportVector.PolyKernel)</pre>
1691   *
1692   * <pre>
1693   * Options specific to kernel weka.classifiers.functions.supportVector.PolyKernel:
1694   * </pre>
1695   *
1696   * <pre> -D
1697   *  Enables debugging output (if available) to be printed.
1698   *  (default: off)</pre>
1699   *
1700   * <pre> -no-checks
1701   *  Turns off all checks - use with caution!
1702   *  (default: checks on)</pre>
1703   *
1704   * <pre> -C &lt;num&gt;
1705   *  The size of the cache (a prime number), 0 for full cache and
1706   *  -1 to turn it off.
1707   *  (default: 250007)</pre>
1708   *
1709   * <pre> -E &lt;num&gt;
1710   *  The Exponent to use.
1711   *  (default: 1.0)</pre>
1712   *
1713   * <pre> -L
1714   *  Use lower-order terms.
1715   *  (default: no)</pre>
1716   *
1717   <!-- options-end -->
1718   *
1719   * @param options the list of options as an array of strings
1720   * @throws Exception if an option is not supported
1721   */
1722  public void setOptions(String[] options) throws Exception {
1723    String      tmpStr;
1724    String[]    tmpOptions;
1725   
1726    setChecksTurnedOff(Utils.getFlag("no-checks", options));
1727
1728    tmpStr = Utils.getOption('C', options);
1729    if (tmpStr.length() != 0)
1730      setC(Double.parseDouble(tmpStr));
1731    else
1732      setC(1.0);
1733
1734    tmpStr = Utils.getOption('L', options);
1735    if (tmpStr.length() != 0)
1736      setToleranceParameter(Double.parseDouble(tmpStr));
1737    else
1738      setToleranceParameter(1.0e-3);
1739   
1740    tmpStr = Utils.getOption('P', options);
1741    if (tmpStr.length() != 0)
1742      setEpsilon(Double.parseDouble(tmpStr));
1743    else
1744      setEpsilon(1.0e-12);
1745   
1746    tmpStr = Utils.getOption('N', options);
1747    if (tmpStr.length() != 0)
1748      setFilterType(new SelectedTag(Integer.parseInt(tmpStr), TAGS_FILTER));
1749    else
1750      setFilterType(new SelectedTag(FILTER_NORMALIZE, TAGS_FILTER));
1751   
1752    setBuildLogisticModels(Utils.getFlag('M', options));
1753   
1754    tmpStr = Utils.getOption('V', options);
1755    if (tmpStr.length() != 0)
1756      setNumFolds(Integer.parseInt(tmpStr));
1757    else
1758      setNumFolds(-1);
1759   
1760    tmpStr = Utils.getOption('W', options);
1761    if (tmpStr.length() != 0)
1762      setRandomSeed(Integer.parseInt(tmpStr));
1763    else
1764      setRandomSeed(1);
1765
1766    tmpStr     = Utils.getOption('K', options);
1767    tmpOptions = Utils.splitOptions(tmpStr);
1768    if (tmpOptions.length != 0) {
1769      tmpStr        = tmpOptions[0];
1770      tmpOptions[0] = "";
1771      setKernel(Kernel.forName(tmpStr, tmpOptions));
1772    }
1773   
1774    super.setOptions(options);
1775  }
1776
1777  /**
1778   * Gets the current settings of the classifier.
1779   *
1780   * @return an array of strings suitable for passing to setOptions
1781   */
1782  public String[] getOptions() {
1783    int       i;
1784    Vector    result;
1785    String[]  options;
1786
1787    result = new Vector();
1788    options = super.getOptions();
1789    for (i = 0; i < options.length; i++)
1790      result.add(options[i]);
1791
1792    if (getChecksTurnedOff())
1793      result.add("-no-checks");
1794
1795    result.add("-C");
1796    result.add("" + getC());
1797   
1798    result.add("-L");
1799    result.add("" + getToleranceParameter());
1800   
1801    result.add("-P");
1802    result.add("" + getEpsilon());
1803   
1804    result.add("-N");
1805    result.add("" + m_filterType);
1806   
1807    if (getBuildLogisticModels())
1808      result.add("-M");
1809   
1810    result.add("-V");
1811    result.add("" + getNumFolds());
1812   
1813    result.add("-W");
1814    result.add("" + getRandomSeed());
1815
1816    result.add("-K");
1817    result.add("" + getKernel().getClass().getName() + " " + Utils.joinOptions(getKernel().getOptions()));
1818   
1819    return (String[]) result.toArray(new String[result.size()]);         
1820  }
1821
1822  /**
1823   * Disables or enables the checks (which could be time-consuming). Use with
1824   * caution!
1825   *
1826   * @param value       if true turns off all checks
1827   */
1828  public void setChecksTurnedOff(boolean value) {
1829    if (value)
1830      turnChecksOff();
1831    else
1832      turnChecksOn();
1833  }
1834 
1835  /**
1836   * Returns whether the checks are turned off or not.
1837   *
1838   * @return            true if the checks are turned off
1839   */
1840  public boolean getChecksTurnedOff() {
1841    return m_checksTurnedOff;
1842  }
1843
1844  /**
1845   * Returns the tip text for this property
1846   *
1847   * @return            tip text for this property suitable for
1848   *                    displaying in the explorer/experimenter gui
1849   */
1850  public String checksTurnedOffTipText() {
1851    return "Turns time-consuming checks off - use with caution.";
1852  }
1853 
1854  /**
1855   * Returns the tip text for this property
1856   *
1857   * @return            tip text for this property suitable for
1858   *                    displaying in the explorer/experimenter gui
1859   */
1860  public String kernelTipText() {
1861    return "The kernel to use.";
1862  }
1863 
1864  /**
1865   * sets the kernel to use
1866   *
1867   * @param value       the kernel to use
1868   */
1869  public void setKernel(Kernel value) {
1870    m_kernel = value;
1871  }
1872 
1873  /**
1874   * Returns the kernel to use
1875   *
1876   * @return            the current kernel
1877   */
1878  public Kernel getKernel() {
1879    return m_kernel;
1880  }
1881     
1882  /**
1883   * Returns the tip text for this property
1884   * @return tip text for this property suitable for
1885   * displaying in the explorer/experimenter gui
1886   */
1887  public String cTipText() {
1888    return "The complexity parameter C.";
1889  }
1890 
1891  /**
1892   * Get the value of C.
1893   *
1894   * @return Value of C.
1895   */
1896  public double getC() {
1897   
1898    return m_C;
1899  }
1900 
1901  /**
1902   * Set the value of C.
1903   *
1904   * @param v  Value to assign to C.
1905   */
1906  public void setC(double v) {
1907   
1908    m_C = v;
1909  }
1910     
1911  /**
1912   * Returns the tip text for this property
1913   * @return tip text for this property suitable for
1914   * displaying in the explorer/experimenter gui
1915   */
1916  public String toleranceParameterTipText() {
1917    return "The tolerance parameter (shouldn't be changed).";
1918  }
1919 
1920  /**
1921   * Get the value of tolerance parameter.
1922   * @return Value of tolerance parameter.
1923   */
1924  public double getToleranceParameter() {
1925   
1926    return m_tol;
1927  }
1928 
1929  /**
1930   * Set the value of tolerance parameter.
1931   * @param v  Value to assign to tolerance parameter.
1932   */
1933  public void setToleranceParameter(double v) {
1934   
1935    m_tol = v;
1936  }
1937     
1938  /**
1939   * Returns the tip text for this property
1940   * @return tip text for this property suitable for
1941   * displaying in the explorer/experimenter gui
1942   */
1943  public String epsilonTipText() {
1944    return "The epsilon for round-off error (shouldn't be changed).";
1945  }
1946 
1947  /**
1948   * Get the value of epsilon.
1949   * @return Value of epsilon.
1950   */
1951  public double getEpsilon() {
1952   
1953    return m_eps;
1954  }
1955 
1956  /**
1957   * Set the value of epsilon.
1958   * @param v  Value to assign to epsilon.
1959   */
1960  public void setEpsilon(double v) {
1961   
1962    m_eps = v;
1963  }
1964     
1965  /**
1966   * Returns the tip text for this property
1967   * @return tip text for this property suitable for
1968   * displaying in the explorer/experimenter gui
1969   */
1970  public String filterTypeTipText() {
1971    return "Determines how/if the data will be transformed.";
1972  }
1973 
1974  /**
1975   * Gets how the training data will be transformed. Will be one of
1976   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
1977   *
1978   * @return the filtering mode
1979   */
1980  public SelectedTag getFilterType() {
1981
1982    return new SelectedTag(m_filterType, TAGS_FILTER);
1983  }
1984 
1985  /**
1986   * Sets how the training data will be transformed. Should be one of
1987   * FILTER_NORMALIZE, FILTER_STANDARDIZE, FILTER_NONE.
1988   *
1989   * @param newType the new filtering mode
1990   */
1991  public void setFilterType(SelectedTag newType) {
1992   
1993    if (newType.getTags() == TAGS_FILTER) {
1994      m_filterType = newType.getSelectedTag().getID();
1995    }
1996  }
1997     
1998  /**
1999   * Returns the tip text for this property
2000   * @return tip text for this property suitable for
2001   * displaying in the explorer/experimenter gui
2002   */
2003  public String buildLogisticModelsTipText() {
2004    return "Whether to fit logistic models to the outputs (for proper "
2005      + "probability estimates).";
2006  }
2007
2008  /**
2009   * Get the value of buildLogisticModels.
2010   *
2011   * @return Value of buildLogisticModels.
2012   */
2013  public boolean getBuildLogisticModels() {
2014   
2015    return m_fitLogisticModels;
2016  }
2017 
2018  /**
2019   * Set the value of buildLogisticModels.
2020   *
2021   * @param newbuildLogisticModels Value to assign to buildLogisticModels.
2022   */
2023  public void setBuildLogisticModels(boolean newbuildLogisticModels) {
2024   
2025    m_fitLogisticModels = newbuildLogisticModels;
2026  }
2027     
2028  /**
2029   * Returns the tip text for this property
2030   * @return tip text for this property suitable for
2031   * displaying in the explorer/experimenter gui
2032   */
2033  public String numFoldsTipText() {
2034    return "The number of folds for cross-validation used to generate "
2035      + "training data for logistic models (-1 means use training data).";
2036  }
2037 
2038  /**
2039   * Get the value of numFolds.
2040   *
2041   * @return Value of numFolds.
2042   */
2043  public int getNumFolds() {
2044   
2045    return m_numFolds;
2046  }
2047 
2048  /**
2049   * Set the value of numFolds.
2050   *
2051   * @param newnumFolds Value to assign to numFolds.
2052   */
2053  public void setNumFolds(int newnumFolds) {
2054   
2055    m_numFolds = newnumFolds;
2056  }
2057     
2058  /**
2059   * Returns the tip text for this property
2060   * @return tip text for this property suitable for
2061   * displaying in the explorer/experimenter gui
2062   */
2063  public String randomSeedTipText() {
2064    return "Random number seed for the cross-validation.";
2065  }
2066 
2067  /**
2068   * Get the value of randomSeed.
2069   *
2070   * @return Value of randomSeed.
2071   */
2072  public int getRandomSeed() {
2073   
2074    return m_randomSeed;
2075  }
2076 
2077  /**
2078   * Set the value of randomSeed.
2079   *
2080   * @param newrandomSeed Value to assign to randomSeed.
2081   */
2082  public void setRandomSeed(int newrandomSeed) {
2083   
2084    m_randomSeed = newrandomSeed;
2085  }
2086 
2087  /**
2088   * Prints out the classifier.
2089   *
2090   * @return a description of the classifier as a string
2091   */
2092  public String toString() {
2093   
2094    StringBuffer text = new StringBuffer();
2095   
2096    if ((m_classAttribute == null)) {
2097      return "SMO: No model built yet.";
2098    }
2099    try {
2100      text.append("SMO\n\n");
2101      text.append("Kernel used:\n  " + m_kernel.toString() + "\n\n");
2102     
2103      for (int i = 0; i < m_classAttribute.numValues(); i++) {
2104        for (int j = i + 1; j < m_classAttribute.numValues(); j++) {
2105          text.append("Classifier for classes: " + 
2106                      m_classAttribute.value(i) + ", " +
2107                      m_classAttribute.value(j) + "\n\n");
2108          text.append(m_classifiers[i][j]);
2109          if (m_fitLogisticModels) {
2110            text.append("\n\n");
2111            if ( m_classifiers[i][j].m_logistic == null) {
2112              text.append("No logistic model has been fit.\n");
2113            } else {
2114              text.append(m_classifiers[i][j].m_logistic);
2115            }
2116          }
2117          text.append("\n\n");
2118        }
2119      }
2120    } catch (Exception e) {
2121      return "Can't print SMO classifier.";
2122    }
2123   
2124    return text.toString();
2125  }
2126 
2127  /**
2128   * Returns the revision string.
2129   *
2130   * @return            the revision
2131   */
2132  public String getRevision() {
2133    return RevisionUtils.extract("$Revision: 6024 $");
2134  }
2135 
2136  /**
2137   * Main method for testing this class.
2138   */
2139  public static void main(String[] argv) {
2140    runClassifier(new SMO(), argv);
2141  }
2142}
2143
Note: See TracBrowser for help on using the repository browser.