SMO.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 44k
Category:

Windows Develop

Development Platform:

Java

  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.  *    SMO.java
  18.  *    Copyright (C) 1999 Eibe Frank
  19.  *
  20.  */
  21. package weka.classifiers.functions;
  22. import weka.classifiers.Classifier;
  23. import weka.classifiers.Evaluation;
  24. import weka.filters.unsupervised.attribute.NominalToBinary;
  25. import weka.filters.unsupervised.attribute.ReplaceMissingValues;
  26. import weka.filters.unsupervised.attribute.Normalize;
  27. import weka.filters.Filter;
  28. import java.util.*;
  29. import java.io.*;
  30. import weka.core.*;
  31. /**
  32.  * Implements John C. Platt's sequential minimal optimization
  33.  * algorithm for training a support vector classifier using polynomial
  34.  * or RBF kernels. 
  35.  *
  36.  * This implementation globally replaces all missing values and
  37.  * transforms nominal attributes into binary ones. For more
  38.  * information on the SMO algorithm, see<p>
  39.  *
  40.  * J. Platt (1998). <i>Fast Training of Support Vector
  41.  * Machines using Sequential Minimal Optimization</i>. Advances in Kernel
  42.  * Methods - Support Vector Learning, B. Sch鰈kopf, C. Burges, and
  43.  * A. Smola, eds., MIT Press. <p>
  44.  *
  45.  * S.S. Keerthi, S.K. Shevade, C. Bhattacharyya, K.R.K. Murthy (1999).
  46.  * <i> Improvements to Platt's SMO Algorithm for SVM Classifier Design</i>.
  47.  * Technical Report CD-99-14. Control Division, Dept of Mechanical and
  48.  * Production Engineering, National University of Singapore. <p>
  49.  *
  50.  * Note: for improved speed normalization should be turned off when
  51.  * operating on SparseInstances.<p>
  52.  *
  53.  * Valid options are:<p>
  54.  *
  55.  * -C num <br>
  56.  * The complexity constant C. (default 1)<p>
  57.  *
  58.  * -E num <br>
  59.  * The exponent for the polynomial kernel. (default 1)<p>
  60.  *
  61.  * -G num <br>
  62.  * Gamma for the RBF kernel. (default 0.01)<p>
  63.  *
  64.  * -N <br>
  65.  * Don't normalize the training instances. <p>
  66.  *
  67.  * -L <br>
  68.  * Rescale kernel (only for non-linear polynomial kernels). <p>
  69.  *
  70.  * -O <br>
  71.  * Use lower-order terms (only for non-linear polynomial kernels). <p>
  72.  *
  73.  * -R <br>
  74.  * Use the RBF kernel. (default poly)<p>
  75.  *
  76.  * -A num <br>
  77.  * Sets the size of the kernel cache. Should be a prime number. 
  78.  * (default 1000003) <p>
  79.  *
  80.  * -T num <br>
  81.  * Sets the tolerance parameter. (default 1.0e-3)<p>
  82.  *
  83.  * -P num <br>
  84.  * Sets the epsilon for round-off error. (default 1.0e-12)<p>
  85.  *
  86.  * @author Eibe Frank (eibe@cs.waikato.ac.nz)
  87.  * @author Shane Legg (shane@intelligenesis.net) (sparse vector code)
  88.  * @author Stuart Inglis (stuart@intelligenesis.net) (sparse vector code)
  89.  * @author J. Lindgren (jtlindgr{at}cs.helsinki.fi) (RBF kernel)
  90.  * @version $Revision: 1.34 $ 
  91.  */
  92. public class SMO extends Classifier implements OptionHandler {
  93.   /**
  94.    * Class for building a binary support vector machine.
  95.    */
  96.   private class BinarySMO implements Serializable {
  97.     /**
  98.      * Calculates a dot product between two instances
  99.      *
  100.      */
  101.     private double dotProd(Instance inst1, Instance inst2) throws Exception {
  102.       double result=0;
  103.     
  104.       // we can do a fast dot product
  105.       int n1 = inst1.numValues(); int n2 = inst2.numValues();
  106.       int classIndex = m_data.classIndex();
  107.       for (int p1 = 0, p2 = 0; p1 < n1 && p2 < n2;) {
  108.         int ind1 = inst1.index(p1); 
  109.         int ind2 = inst2.index(p2);
  110.         if (ind1 == ind2) {
  111.   if (ind1 != classIndex) {
  112.     result += inst1.valueSparse(p1) * inst2.valueSparse(p2);
  113.   }
  114.   p1++; p2++;
  115. } else if (ind1 > ind2) {
  116.   p2++;
  117.         } else {
  118.           p1++;
  119. }
  120.       }
  121.       return(result);
  122.     }
  123.     /**
  124.      * Abstract kernel. 
  125.      *
  126.      */
  127.     private abstract class Kernel {
  128.     
  129.       /**
  130.        * Computes the result of the kernel function for two instances.
  131.        *
  132.        * @param id1 the index of the first instance
  133.        * @param id2 the index of the second instance
  134.        * @param inst the instance corresponding to id1
  135.        * @return the result of the kernel function
  136.        */
  137.       public abstract double eval(int id1, int id2, Instance inst1) throws Exception;
  138.     }
  139.  
  140.     /**
  141.      * The polynomial kernel.
  142.      *
  143.      */
  144.     private class PolyKernel extends Kernel {
  145.       public double eval(int id1, int id2, Instance inst1) throws Exception {
  146.       
  147. double result = 0;
  148. long key = -1;
  149. int location = -1;
  150. // we can only cache if we know the indexes
  151. if (id1 >= 0) {
  152.   if (id1 > id2) {
  153.     key = (long)id1 * m_alpha.length + id2;
  154.   } else {
  155.     key = (long)id2 * m_alpha.length + id1;
  156.   }
  157.   if (key < 0) {
  158.     throw new Exception("Cache overflow detected!");
  159.   }
  160.   location = (int)(key % m_keys.length);
  161.   if (m_keys[location] == (key + 1)) {
  162.     return m_storage[location];
  163.   }
  164.         }
  165.         result=dotProd(inst1, m_data.instance(id2));
  166.     
  167.         // Use lower order terms?
  168.         if (m_lowerOrder) {
  169.     result += 1.0;
  170.         }
  171.         // Rescale kernel?
  172.         if (m_rescale) {
  173.   result /= (double)m_data.numAttributes() - 1;
  174.         }      
  175.     
  176.         if (m_exponent != 1.0) {
  177.   result = Math.pow(result, m_exponent);
  178.         }
  179.         m_kernelEvals++;
  180.     
  181.         // store result in cache 
  182.         if (key != -1){
  183.   m_storage[location] = result;
  184.   m_keys[location] = (key + 1);
  185.         }
  186.         return result;
  187. //
  188.       }
  189.     }
  190.     
  191.     /**
  192.      * The RBF kernel.
  193.      *
  194.      */
  195.     private class RBFKernel extends Kernel {
  196.     
  197.       /** The precalculated dotproducts of <inst_i,inst_i> */
  198.       private double m_kernelPrecalc[];
  199.       /**
  200.        * Constructor. Initializes m_kernelPrecalc[].
  201.        *
  202.        */
  203.       public RBFKernel(Instances data) throws Exception {
  204.         
  205. m_kernelPrecalc=new double[data.numInstances()];
  206. for(int i=0;i<data.numInstances();i++)
  207.   m_kernelPrecalc[i]=dotProd(data.instance(i),data.instance(i));
  208.       
  209.       }
  210.       public double eval(int id1, int id2, Instance inst1) throws Exception {
  211.   
  212. double result = 0;
  213. long key = -1;
  214. int location = -1;
  215.       
  216. // we can only cache if we know the indexes
  217. if (id1 >= 0) {
  218.   if (id1 > id2) {
  219.        key = (long)id1 * m_alpha.length + id2;
  220.   } else {
  221.     key = (long)id2 * m_alpha.length + id1;
  222.   }
  223.   if (key < 0) {
  224.     throw new Exception("Cache overflow detected!");
  225.   }
  226.   location = (int)(key % m_keys.length);
  227.   if (m_keys[location] == (key + 1)) {
  228.     return m_storage[location];
  229.   }
  230.         }
  231. Instance inst2 = m_data.instance(id2);
  232. double precalc1;
  233. if(id1==-1)
  234.   precalc1=dotProd(inst1,inst1);
  235. else
  236.           precalc1=m_kernelPrecalc[id1];
  237.         result=Math.exp(m_gamma*(2.*dotProd(inst1, inst2)-precalc1-m_kernelPrecalc[id2]));
  238.   
  239.         m_kernelEvals++;
  240.     
  241.         // store result in cache 
  242.         if (key != -1){
  243.           m_storage[location] = result;
  244.           m_keys[location] = (key + 1);
  245.         }
  246.         return result;
  247.       }
  248.     }
  249.     
  250.     /**
  251.      * Stores a set of a given size.
  252.      */
  253.     private class SMOset implements Serializable {
  254.       /** The current number of elements in the set */
  255.       private int m_number;
  256.       /** The first element in the set */
  257.       private int m_first;
  258.       /** Indicators */
  259.       private boolean[] m_indicators;
  260.       /** The next element for each element */
  261.       private int[] m_next;
  262.       /** The previous element for each element */
  263.       private int[] m_previous;
  264.       /**
  265.        * Creates a new set of the given size.
  266.        */
  267.       private SMOset(int size) {
  268.       
  269. m_indicators = new boolean[size];
  270. m_next = new int[size];
  271. m_previous = new int[size];
  272. m_number = 0;
  273. m_first = -1;
  274.       }
  275.  
  276.       /**
  277.        * Checks whether an element is in the set.
  278.        */
  279.       private boolean contains(int index) {
  280. return m_indicators[index];
  281.       }
  282.       /**
  283.        * Deletes an element from the set.
  284.        */
  285.       private void delete(int index) {
  286. if (m_indicators[index]) {
  287.   if (m_first == index) {
  288.     m_first = m_next[index];
  289.   } else {
  290.     m_next[m_previous[index]] = m_next[index];
  291.   }
  292.   if (m_next[index] != -1) {
  293.     m_previous[m_next[index]] = m_previous[index];
  294.   }
  295.   m_indicators[index] = false;
  296.   m_number--;
  297. }
  298.       }
  299.       /**
  300.        * Inserts an element into the set.
  301.        */
  302.       private void insert(int index) {
  303. if (!m_indicators[index]) {
  304.   if (m_number == 0) {
  305.     m_first = index;
  306.     m_next[index] = -1;
  307.     m_previous[index] = -1;
  308.   } else {
  309.     m_previous[m_first] = index;
  310.     m_next[index] = m_first;
  311.     m_previous[index] = -1;
  312.     m_first = index;
  313.   }
  314.   m_indicators[index] = true;
  315.   m_number++;
  316. }
  317.       }
  318.       /** 
  319.        * Gets the next element in the set. -1 gets the first one.
  320.        */
  321.       private int getNext(int index) {
  322. if (index == -1) {
  323.   return m_first;
  324. } else {
  325.   return m_next[index];
  326. }
  327.       }
  328.       /**
  329.        * Prints all the current elements in the set.
  330.        */
  331.       private void printElements() {
  332. for (int i = getNext(-1); i != -1; i = getNext(i)) {
  333.   System.err.print(i + " ");
  334. }
  335. System.err.println();
  336. for (int i = 0; i < m_indicators.length; i++) {
  337.   if (m_indicators[i]) {
  338.     System.err.print(i + " ");
  339.   }
  340. }
  341. System.err.println();
  342. System.err.println(m_number);
  343.       }
  344.       /** 
  345.        * Returns the number of elements in the set.
  346.        */
  347.       private int numElements() {
  348.       
  349. return m_number;
  350.       }
  351.     }
  352.     /** The Lagrange multipliers. */
  353.     private double[] m_alpha;
  354.     /** The thresholds. */
  355.     private double m_b, m_bLow, m_bUp;
  356.     /** The indices for m_bLow and m_bUp */
  357.     private int m_iLow, m_iUp;
  358.     /** The training data. */
  359.     private Instances m_data;
  360.     /** Weight vector for linear machine. */
  361.     private double[] m_weights;
  362.     /** Variables to hold weight vector in sparse form.
  363. (To reduce storage requirements.) */
  364.     private double[] m_sparseWeights;
  365.     private int[] m_sparseIndices;
  366.     /** Kernel to use **/
  367.     private Kernel m_kernel;
  368.     /** Kernel function cache */
  369.     private double[] m_storage;
  370.     private long[] m_keys;
  371.     /** The transformed class values. */
  372.     private double[] m_class;
  373.     /** The current set of errors for all non-bound examples. */
  374.     private double[] m_errors;
  375.     /** The five different sets used by the algorithm. */
  376.     private SMOset m_I0; // {i: 0 < m_alpha[i] < C}
  377.     private SMOset m_I1; // {i: m_class[i] = 1, m_alpha[i] = 0}
  378.     private SMOset m_I2; // {i: m_class[i] = -1, m_alpha[i] =C}
  379.     private SMOset m_I3; // {i: m_class[i] = 1, m_alpha[i] = C}
  380.     private SMOset m_I4; // {i: m_class[i] = -1, m_alpha[i] = 0}
  381.     /** The set of support vectors */
  382.     private SMOset m_supportVectors; // {i: 0 < m_alpha[i]}
  383.     /** Counts the number of kernel evaluations. */
  384.     private int m_kernelEvals;
  385.     /**
  386.      * Method for building the binary classifier.
  387.      *
  388.      * @param insts the set of training instances
  389.      * @param cl1 the first class' index
  390.      * @param cl2 the second class' index
  391.      * @exception Exception if the classifier can't be built successfully
  392.      */
  393.     private void buildClassifier(Instances insts, int cl1, int cl2) throws Exception {
  394.       
  395.       // Initialize the number of kernel evaluations
  396.       int m_kernelEvals = 0;
  397.       
  398.       // Initialize thresholds
  399.       m_bUp = -1; m_bLow = 1; m_b = 0;
  400.       
  401.       // Set class values
  402.       m_class = new double[insts.numInstances()];
  403.       m_iUp = -1; m_iLow = -1;
  404.       for (int i = 0; i < m_class.length; i++) {
  405. if ((int) insts.instance(i).classValue() == cl1) {
  406.   m_class[i] = -1; m_iLow = i;
  407. } else if ((int) insts.instance(i).classValue() == cl2) {
  408.   m_class[i] = 1; m_iUp = i;
  409. } else {
  410.   throw new Exception ("This should never happen!");
  411. }
  412.       }
  413.       if ((m_iUp == -1) || (m_iLow == -1)) {
  414. if (m_iUp == -1) {
  415.   m_b = 1;
  416. } else {
  417.   m_b = -1;
  418. }
  419. if (!m_useRBF && m_exponent == 1.0) {
  420.   m_sparseWeights = new double[0];
  421.   m_sparseIndices = new int[0];
  422. }
  423. m_class = null;
  424. return;
  425.       }
  426.       
  427.       // Set the reference to the data
  428.       m_data = insts;
  429.       // If machine is linear, reserve space for weights
  430.       if (!m_useRBF && m_exponent == 1.0) {
  431. m_weights = new double[m_data.numAttributes()];
  432.       } else {
  433. m_weights = null;
  434.       }
  435.       
  436.       // Initialize alpha array to zero
  437.       m_alpha = new double[m_data.numInstances()];
  438.       
  439.       // Initialize sets
  440.       m_supportVectors = new SMOset(m_data.numInstances());
  441.       m_I0 = new SMOset(m_data.numInstances());
  442.       m_I1 = new SMOset(m_data.numInstances());
  443.       m_I2 = new SMOset(m_data.numInstances());
  444.       m_I3 = new SMOset(m_data.numInstances());
  445.       m_I4 = new SMOset(m_data.numInstances());
  446.       // Clean out some instance variables
  447.       m_sparseWeights = null;
  448.       m_sparseIndices = null;
  449.       
  450.       // Initialize error cache
  451.       m_errors = new double[m_data.numInstances()];
  452.       m_errors[m_iLow] = 1; m_errors[m_iUp] = -1;
  453.      
  454.       // Initialize kernel
  455.       if(m_useRBF)
  456.        m_kernel=new RBFKernel(m_data);
  457.       else
  458.         m_kernel=new PolyKernel();
  459.      
  460.       // The kernel calculations are cached
  461.       m_storage = new double[m_cacheSize];
  462.       m_keys = new long[m_cacheSize];
  463.       
  464.       // Build up I1 and I4
  465.       for (int i = 0; i < m_class.length; i++ ) {
  466. if (m_class[i] == 1) {
  467.   m_I1.insert(i);
  468. } else {
  469.   m_I4.insert(i);
  470. }
  471.       }
  472.       
  473.       // Loop to find all the support vectors
  474.       int numChanged = 0;
  475.       boolean examineAll = true;
  476.       while ((numChanged > 0) || examineAll) {
  477. numChanged = 0;
  478. if (examineAll) {
  479.   for (int i = 0; i < m_alpha.length; i++) {
  480.     if (examineExample(i)) {
  481.       numChanged++;
  482.     }
  483.   }
  484. } else {
  485.   
  486.   // This code implements Modification 1 from Keerthi et al.'s paper
  487.   for (int i = 0; i < m_alpha.length; i++) {
  488.     if ((m_alpha[i] > 0) &&  (m_alpha[i] < m_C)) {
  489.       if (examineExample(i)) {
  490. numChanged++;
  491.       }
  492.       
  493.       // Is optimality on unbound vectors obtained?
  494.       if (m_bUp > m_bLow - 2 * m_tol) {
  495. numChanged = 0;
  496. break;
  497.       }
  498.     }
  499.   }
  500.   
  501.   //This is the code for Modification 2 from Keerthi et al.'s paper
  502.   /*boolean innerLoopSuccess = true; 
  503.     numChanged = 0;
  504.     while ((m_bUp < m_bLow - 2 * m_tol) && (innerLoopSuccess == true)) {
  505.     innerLoopSuccess = takeStep(m_iUp, m_iLow, m_errors[m_iLow]);
  506.     }*/
  507. }
  508. if (examineAll) {
  509.   examineAll = false;
  510. } else if (numChanged == 0) {
  511.   examineAll = true;
  512. }
  513.       }
  514.       
  515.       // Set threshold
  516.       m_b = (m_bLow + m_bUp) / 2.0;
  517.       
  518.       // Save memory
  519.       m_storage = null; m_keys = null; m_errors = null;
  520.       m_I0 = m_I1 = m_I2 = m_I3 = m_I4 = null;
  521.       
  522.       // If machine is linear, delete training data
  523.       // and store weight vector in sparse format
  524.       if (!m_useRBF && m_exponent == 1.0) {
  525. // We don't need to store the set of support vectors
  526. m_supportVectors = null;
  527. // We don't need to store the class values either
  528. m_class = null;
  529. // Clean out training data
  530. if (!m_checksTurnedOff) {
  531.   m_data = new Instances(m_data, 0);
  532. } else {
  533.   m_data = null;
  534. }
  535. // Convert weight vector
  536. double[] sparseWeights = new double[m_weights.length];
  537. int[] sparseIndices = new int[m_weights.length];
  538. int counter = 0;
  539. for (int i = 0; i < m_weights.length; i++) {
  540.   if (m_weights[i] != 0.0) {
  541.     sparseWeights[counter] = m_weights[i];
  542.     sparseIndices[counter] = i;
  543.     counter++;
  544.   }
  545. }
  546. m_sparseWeights = new double[counter];
  547. m_sparseIndices = new int[counter];
  548. System.arraycopy(sparseWeights, 0, m_sparseWeights, 0, counter);
  549. System.arraycopy(sparseIndices, 0, m_sparseIndices, 0, counter);
  550. // Clean out weight vector
  551. m_weights = null;
  552. // We don't need the alphas in the linear case
  553. m_alpha = null;
  554.       }
  555.     }
  556.     
  557.     /**
  558.      * Computes SVM output for given instance.
  559.      *
  560.      * @param index the instance for which output is to be computed
  561.      * @param inst the instance 
  562.      * @return the output of the SVM for the given instance
  563.      */
  564.     private double SVMOutput(int index, Instance inst) throws Exception {
  565.       
  566.       double result = 0;
  567.       
  568.       // Is the machine linear?
  569.       if (!m_useRBF && m_exponent == 1.0) {
  570. // Is weight vector stored in sparse format?
  571. if (m_sparseWeights == null) {
  572.   int n1 = inst.numValues(); 
  573.   for (int p = 0; p < n1; p++) {
  574.     if (inst.index(p) != m_classIndex) {
  575.       result += m_weights[inst.index(p)] * inst.valueSparse(p);
  576.     }
  577.   }
  578. } else {
  579.   int n1 = inst.numValues(); int n2 = m_sparseWeights.length;
  580.   for (int p1 = 0, p2 = 0; p1 < n1 && p2 < n2;) {
  581.     int ind1 = inst.index(p1); 
  582.     int ind2 = m_sparseIndices[p2];
  583.     if (ind1 == ind2) {
  584.       if (ind1 != m_classIndex) {
  585. result += inst.valueSparse(p1) * m_sparseWeights[p2];
  586.       }
  587.       p1++; p2++;
  588.     } else if (ind1 > ind2) {
  589.       p2++;
  590.     } else { 
  591.       p1++;
  592.     }
  593.   }
  594. }
  595.       } else {
  596. for (int i = m_supportVectors.getNext(-1); i != -1; 
  597.      i = m_supportVectors.getNext(i)) {
  598.   result += m_class[i] * m_alpha[i] * m_kernel.eval(index, i, inst);
  599. }
  600.       }
  601.       result -= m_b;
  602.       
  603.       return result;
  604.     }
  605.     /**
  606.      * Prints out the classifier.
  607.      *
  608.      * @return a description of the classifier as a string
  609.      */
  610.     public String toString() {
  611.       StringBuffer text = new StringBuffer();
  612.       int printed = 0;
  613.       if ((m_alpha == null) && (m_sparseWeights == null)) {
  614. return "BinarySMO: No model built yet.";
  615.       }
  616.       try {
  617. text.append("BinarySMOnn");
  618. // If machine linear, print weight vector
  619. if (!m_useRBF && m_exponent == 1.0) {
  620.   text.append("Machine linear: showing attribute weights, ");
  621.   text.append("not support vectors.nn");
  622.   // We can assume that the weight vector is stored in sparse
  623.   // format because the classifier has been built
  624.   for (int i = 0; i < m_sparseWeights.length; i++) {
  625.     if (i != (int)m_classIndex) {
  626.       if (printed > 0) {
  627. text.append(" + ");
  628.       } else {
  629. text.append("   ");
  630.       }
  631.       if (!m_checksTurnedOff) {
  632. text.append(m_sparseWeights[i] + " * " + 
  633.     m_data.attribute(m_sparseIndices[i]).name()+"n");
  634.       } else {
  635. text.append(m_sparseWeights[i] + " * " + "attribute with index " + 
  636.     m_sparseIndices[i] +"n");
  637.       }
  638.       printed++;
  639.     }
  640.   }
  641. } else {
  642.   for (int i = 0; i < m_alpha.length; i++) {
  643.     if (m_supportVectors.contains(i)) {
  644.       if (printed > 0) {
  645. text.append(" + ");
  646.       } else {
  647. text.append("   ");
  648.       }
  649.       text.append(((int)m_class[i]) + " * " +
  650.   m_alpha[i] + " * K[X(" + i + ") * X]n");
  651.       printed++;
  652.     }
  653.   }
  654. }
  655. text.append(" - " + m_b);
  656. if (m_useRBF || m_exponent != 1.0) {
  657.   text.append("nnNumber of support vectors: " + m_supportVectors.numElements());
  658. }
  659. text.append("nnNumber of kernel evaluations: " + m_kernelEvals);
  660.       } catch (Exception e) {
  661. return "Can't print BinarySMO classifier.";
  662.       }
  663.     
  664.       return text.toString();
  665.     }
  666.     /**
  667.      * Examines instance.
  668.      *
  669.      * @param i2 index of instance to examine
  670.      * @return true if examination was successfull
  671.      * @exception Exception if something goes wrong
  672.      */
  673.     private boolean examineExample(int i2) throws Exception {
  674.     
  675.       double y2, alph2, F2;
  676.       int i1 = -1;
  677.     
  678.       y2 = m_class[i2];
  679.       alph2 = m_alpha[i2];
  680.       if (m_I0.contains(i2)) {
  681. F2 = m_errors[i2];
  682.       } else {
  683. F2 = SVMOutput(i2, m_data.instance(i2)) + m_b - y2;
  684. m_errors[i2] = F2;
  685.       
  686. // Update thresholds
  687. if ((m_I1.contains(i2) || m_I2.contains(i2)) && (F2 < m_bUp)) {
  688.   m_bUp = F2; m_iUp = i2;
  689. } else if ((m_I3.contains(i2) || m_I4.contains(i2)) && (F2 > m_bLow)) {
  690.   m_bLow = F2; m_iLow = i2;
  691. }
  692.       }
  693.       // Check optimality using current bLow and bUp and, if
  694.       // violated, find an index i1 to do joint optimization
  695.       // with i2...
  696.       boolean optimal = true;
  697.       if (m_I0.contains(i2) || m_I1.contains(i2) || m_I2.contains(i2)) {
  698. if (m_bLow - F2 > 2 * m_tol) {
  699.   optimal = false; i1 = m_iLow;
  700. }
  701.       }
  702.       if (m_I0.contains(i2) || m_I3.contains(i2) || m_I4.contains(i2)) {
  703. if (F2 - m_bUp > 2 * m_tol) {
  704.   optimal = false; i1 = m_iUp;
  705. }
  706.       }
  707.       if (optimal) {
  708. return false;
  709.       }
  710.       // For i2 unbound choose the better i1...
  711.       if (m_I0.contains(i2)) {
  712. if (m_bLow - F2 > F2 - m_bUp) {
  713.   i1 = m_iLow;
  714. } else {
  715.   i1 = m_iUp;
  716. }
  717.       }
  718.       if (i1 == -1) {
  719. throw new Exception("This should never happen!");
  720.       }
  721.       return takeStep(i1, i2, F2);
  722.     }
  723.     /**
  724.      * Method solving for the Lagrange multipliers for
  725.      * two instances.
  726.      *
  727.      * @param i1 index of the first instance
  728.      * @param i2 index of the second instance
  729.      * @return true if multipliers could be found
  730.      * @exception Exception if something goes wrong
  731.      */
  732.     private boolean takeStep(int i1, int i2, double F2) throws Exception {
  733.       double alph1, alph2, y1, y2, F1, s, L, H, k11, k12, k22, eta,
  734. a1, a2, f1, f2, v1, v2, Lobj, Hobj, b1, b2, bOld;
  735.       // Don't do anything if the two instances are the same
  736.       if (i1 == i2) {
  737. return false;
  738.       }
  739.       // Initialize variables
  740.       alph1 = m_alpha[i1]; alph2 = m_alpha[i2];
  741.       y1 = m_class[i1]; y2 = m_class[i2];
  742.       F1 = m_errors[i1];
  743.       s = y1 * y2;
  744.       // Find the constraints on a2
  745.       if (y1 != y2) {
  746. L = Math.max(0, alph2 - alph1); 
  747. H = Math.min(m_C, m_C + alph2 - alph1);
  748.       } else {
  749. L = Math.max(0, alph1 + alph2 - m_C);
  750. H = Math.min(m_C, alph1 + alph2);
  751.       }
  752.       if (L >= H) {
  753. return false;
  754.       }
  755.       // Compute second derivative of objective function
  756.       k11 = m_kernel.eval(i1, i1, m_data.instance(i1));
  757.       k12 = m_kernel.eval(i1, i2, m_data.instance(i1));
  758.       k22 = m_kernel.eval(i2, i2, m_data.instance(i2));
  759.       eta = 2 * k12 - k11 - k22;
  760.       // Check if second derivative is negative
  761.       if (eta < 0) {
  762. // Compute unconstrained maximum
  763. a2 = alph2 - y2 * (F1 - F2) / eta;
  764. // Compute constrained maximum
  765. if (a2 < L) {
  766.   a2 = L;
  767. } else if (a2 > H) {
  768.   a2 = H;
  769. }
  770.       } else {
  771. // Look at endpoints of diagonal
  772. f1 = SVMOutput(i1, m_data.instance(i1));
  773. f2 = SVMOutput(i2, m_data.instance(i2));
  774. v1 = f1 + m_b - y1 * alph1 * k11 - y2 * alph2 * k12; 
  775. v2 = f2 + m_b - y1 * alph1 * k12 - y2 * alph2 * k22; 
  776. double gamma = alph1 + s * alph2;
  777. Lobj = (gamma - s * L) + L - 0.5 * k11 * (gamma - s * L) * (gamma - s * L) - 
  778.   0.5 * k22 * L * L - s * k12 * (gamma - s * L) * L - 
  779.   y1 * (gamma - s * L) * v1 - y2 * L * v2;
  780. Hobj = (gamma - s * H) + H - 0.5 * k11 * (gamma - s * H) * (gamma - s * H) - 
  781.   0.5 * k22 * H * H - s * k12 * (gamma - s * H) * H - 
  782.   y1 * (gamma - s * H) * v1 - y2 * H * v2;
  783. if (Lobj > Hobj + m_eps) {
  784.   a2 = L;
  785. } else if (Lobj < Hobj - m_eps) {
  786.   a2 = H;
  787. } else {
  788.   a2 = alph2;
  789. }
  790.       }
  791.       if (Math.abs(a2 - alph2) < m_eps * (a2 + alph2 + m_eps)) {
  792. return false;
  793.       }
  794.       
  795.       // To prevent precision problems
  796.       if (a2 > m_C - m_Del * m_C) {
  797. a2 = m_C;
  798.       } else if (a2 <= m_Del * m_C) {
  799. a2 = 0;
  800.       }
  801.       
  802.       // Recompute a1
  803.       a1 = alph1 + s * (alph2 - a2);
  804.       
  805.       // To prevent precision problems
  806.       if (a1 > m_C - m_Del * m_C) {
  807. a1 = m_C;
  808.       } else if (a1 <= m_Del * m_C) {
  809. a1 = 0;
  810.       }
  811.       
  812.       // Update sets
  813.       if (a1 > 0) {
  814. m_supportVectors.insert(i1);
  815.       } else {
  816. m_supportVectors.delete(i1);
  817.       }
  818.       if ((a1 > 0) && (a1 < m_C)) {
  819. m_I0.insert(i1);
  820.       } else {
  821. m_I0.delete(i1);
  822.       }
  823.       if ((y1 == 1) && (a1 == 0)) {
  824. m_I1.insert(i1);
  825.       } else {
  826. m_I1.delete(i1);
  827.       }
  828.       if ((y1 == -1) && (a1 == m_C)) {
  829. m_I2.insert(i1);
  830.       } else {
  831. m_I2.delete(i1);
  832.       }
  833.       if ((y1 == 1) && (a1 == m_C)) {
  834. m_I3.insert(i1);
  835.       } else {
  836. m_I3.delete(i1);
  837.       }
  838.       if ((y1 == -1) && (a1 == 0)) {
  839. m_I4.insert(i1);
  840.       } else {
  841. m_I4.delete(i1);
  842.       }
  843.       if (a2 > 0) {
  844. m_supportVectors.insert(i2);
  845.       } else {
  846. m_supportVectors.delete(i2);
  847.       }
  848.       if ((a2 > 0) && (a2 < m_C)) {
  849. m_I0.insert(i2);
  850.       } else {
  851. m_I0.delete(i2);
  852.       }
  853.       if ((y2 == 1) && (a2 == 0)) {
  854. m_I1.insert(i2);
  855.       } else {
  856. m_I1.delete(i2);
  857.       }
  858.       if ((y2 == -1) && (a2 == m_C)) {
  859. m_I2.insert(i2);
  860.       } else {
  861. m_I2.delete(i2);
  862.       }
  863.       if ((y2 == 1) && (a2 == m_C)) {
  864. m_I3.insert(i2);
  865.       } else {
  866. m_I3.delete(i2);
  867.       }
  868.       if ((y2 == -1) && (a2 == 0)) {
  869. m_I4.insert(i2);
  870.       } else {
  871. m_I4.delete(i2);
  872.       }
  873.       
  874.       // Update weight vector to reflect change a1 and a2, if linear SVM
  875.       if (!m_useRBF && m_exponent == 1.0) {
  876. Instance inst1 = m_data.instance(i1);
  877. for (int p1 = 0; p1 < inst1.numValues(); p1++) {
  878.   if (inst1.index(p1) != m_data.classIndex()) {
  879.     m_weights[inst1.index(p1)] += 
  880.       y1 * (a1 - alph1) * inst1.valueSparse(p1);
  881.   }
  882. }
  883. Instance inst2 = m_data.instance(i2);
  884. for (int p2 = 0; p2 < inst2.numValues(); p2++) {
  885.   if (inst2.index(p2) != m_data.classIndex()) {
  886.     m_weights[inst2.index(p2)] += 
  887.       y2 * (a2 - alph2) * inst2.valueSparse(p2);
  888.   }
  889. }
  890.       }
  891.       
  892.       // Update error cache using new Lagrange multipliers
  893.       for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
  894. if ((j != i1) && (j != i2)) {
  895.   m_errors[j] += 
  896.     y1 * (a1 - alph1) * m_kernel.eval(i1, j, m_data.instance(i1)) + 
  897.     y2 * (a2 - alph2) * m_kernel.eval(i2, j, m_data.instance(i2));
  898. }
  899.       }
  900.       
  901.       // Update error cache for i1 and i2
  902.       m_errors[i1] += y1 * (a1 - alph1) * k11 + y2 * (a2 - alph2) * k12;
  903.       m_errors[i2] += y1 * (a1 - alph1) * k12 + y2 * (a2 - alph2) * k22;
  904.       
  905.       // Update array with Lagrange multipliers
  906.       m_alpha[i1] = a1;
  907.       m_alpha[i2] = a2;
  908.       
  909.       // Update thresholds
  910.       m_bLow = -Double.MAX_VALUE; m_bUp = Double.MAX_VALUE;
  911.       m_iLow = -1; m_iUp = -1;
  912.       for (int j = m_I0.getNext(-1); j != -1; j = m_I0.getNext(j)) {
  913. if (m_errors[j] < m_bUp) {
  914.   m_bUp = m_errors[j]; m_iUp = j;
  915. }
  916. if (m_errors[j] > m_bLow) {
  917.   m_bLow = m_errors[j]; m_iLow = j;
  918. }
  919.       }
  920.       if (!m_I0.contains(i1)) {
  921. if (m_I3.contains(i1) || m_I4.contains(i1)) {
  922.   if (m_errors[i1] > m_bLow) {
  923.     m_bLow = m_errors[i1]; m_iLow = i1;
  924.   } 
  925. } else {
  926.   if (m_errors[i1] < m_bUp) {
  927.     m_bUp = m_errors[i1]; m_iUp = i1;
  928.   }
  929. }
  930.       }
  931.       if (!m_I0.contains(i2)) {
  932. if (m_I3.contains(i2) || m_I4.contains(i2)) {
  933.   if (m_errors[i2] > m_bLow) {
  934.     m_bLow = m_errors[i2]; m_iLow = i2;
  935.   }
  936. } else {
  937.   if (m_errors[i2] < m_bUp) {
  938.     m_bUp = m_errors[i2]; m_iUp = i2;
  939.   }
  940. }
  941.       }
  942.       if ((m_iLow == -1) || (m_iUp == -1)) {
  943. throw new Exception("This should never happen!");
  944.       }
  945.       // Made some progress.
  946.       return true;
  947.     }
  948.   
  949.     /**
  950.      * Quick and dirty check whether the quadratic programming problem is solved.
  951.      */
  952.     private void checkClassifier() throws Exception {
  953.       double sum = 0;
  954.       for (int i = 0; i < m_alpha.length; i++) {
  955. if (m_alpha[i] > 0) {
  956.   sum += m_class[i] * m_alpha[i];
  957. }
  958.       }
  959.       System.err.println("Sum of y(i) * alpha(i): " + sum);
  960.       for (int i = 0; i < m_alpha.length; i++) {
  961. double output = SVMOutput(i, m_data.instance(i));
  962. if (Utils.eq(m_alpha[i], 0)) {
  963.   if (Utils.sm(m_class[i] * output, 1)) {
  964.     System.err.println("KKT condition 1 violated: " + m_class[i] * output);
  965.   }
  966. if (Utils.gr(m_alpha[i], 0) && Utils.sm(m_alpha[i], m_C)) {
  967.   if (!Utils.eq(m_class[i] * output, 1)) {
  968.     System.err.println("KKT condition 2 violated: " + m_class[i] * output);
  969.   }
  970. if (Utils.eq(m_alpha[i], m_C)) {
  971.   if (Utils.gr(m_class[i] * output, 1)) {
  972.     System.err.println("KKT condition 3 violated: " + m_class[i] * output);
  973.   }
  974.       }
  975.     }  
  976.   }
  977.   /** The binary classifier(s) */
  978.   private BinarySMO[][] m_classifiers = null;
  979.   /** The exponent for the polynomial kernel. */
  980.   private double m_exponent = 1.0;
  981.  
  982.   /** Gamma for the RBF kernel. */
  983.   private double m_gamma = 0.01;
  984.   
  985.   /** The complexity parameter. */
  986.   private double m_C = 1.0;
  987.   
  988.   /** Epsilon for rounding. */
  989.   private double m_eps = 1.0e-12;
  990.   
  991.   /** Tolerance for accuracy of result. */
  992.   private double m_tol = 1.0e-3;
  993.   /** True if we don't want to normalize */
  994.   private boolean m_Normalize = true;
  995.   
  996.   /** Rescale? */
  997.   private boolean m_rescale = false;
  998.   
  999.   /** Use lower-order terms? */
  1000.   private boolean m_lowerOrder = false;
  1001.   /** Use RBF kernel? (default: poly) */
  1002.   private boolean m_useRBF = false;
  1003.   
  1004.   /** The size of the cache (a prime number) */
  1005.   private int m_cacheSize = 1000003;
  1006.   /** The filter used to make attributes numeric. */
  1007.   private NominalToBinary m_NominalToBinary;
  1008.   /** The filter used to normalize all values. */
  1009.   private Normalize m_Normalization;
  1010.   /** The filter used to get rid of missing values. */
  1011.   private ReplaceMissingValues m_Missing;
  1012.   /** Only numeric attributes in the dataset? */
  1013.   private boolean m_onlyNumeric;
  1014.   /** The class index from the training data */
  1015.   private int m_classIndex = -1;
  1016.   /** The class attribute */
  1017.   private Attribute m_classAttribute;
  1018.   /** Turn off all checks and conversions? Turning them off assumes
  1019.       that data is purely numeric, doesn't contain any missing values,
  1020.       and has a nominal class. Turning them off also means that
  1021.       no header information will be stored if the machine is linear. */
  1022.   private boolean m_checksTurnedOff;
  1023.   /** Precision constant for updating sets */
  1024.   private static double m_Del = 1000 * Double.MIN_VALUE;
  1025.   /**
  1026.    * Turns off checks for missing values, etc. Use with caution.
  1027.    */
  1028.   public void turnChecksOff() {
  1029.     m_checksTurnedOff = true;
  1030.   }
  1031.   /**
  1032.    * Turns on checks for missing values, etc.
  1033.    */
  1034.   public void turnChecksOn() {
  1035.     m_checksTurnedOff = false;
  1036.   }
  1037.   /**
  1038.    * Method for building the classifier. Implements a one-against-one
  1039.    * wrapper for multi-class problems.
  1040.    *
  1041.    * @param insts the set of training instances
  1042.    * @exception Exception if the classifier can't be built successfully
  1043.    */
  1044.   public void buildClassifier(Instances insts) throws Exception {
  1045.     if (!m_checksTurnedOff) {
  1046.       if (insts.checkForStringAttributes()) {
  1047. throw new UnsupportedAttributeTypeException("Cannot handle string attributes!");
  1048.       }
  1049.       if (insts.classAttribute().isNumeric()) {
  1050. throw new UnsupportedClassTypeException("SMO can't handle a numeric class!");
  1051.       }
  1052.       insts = new Instances(insts);
  1053.       insts.deleteWithMissingClass();
  1054.       if (insts.numInstances() == 0) {
  1055. throw new Exception("No training instances without a missing class!");
  1056.       }
  1057.     }
  1058.     m_onlyNumeric = true;
  1059.     if (!m_checksTurnedOff) {
  1060.       for (int i = 0; i < insts.numAttributes(); i++) {
  1061. if (i != insts.classIndex()) {
  1062.   if (!insts.attribute(i).isNumeric()) {
  1063.     m_onlyNumeric = false;
  1064.     break;
  1065.   }
  1066. }
  1067.       }
  1068.     }
  1069.     if (!m_checksTurnedOff) {
  1070.       m_Missing = new ReplaceMissingValues();
  1071.       m_Missing.setInputFormat(insts);
  1072.       insts = Filter.useFilter(insts, m_Missing); 
  1073.     } else {
  1074.       m_Missing = null;
  1075.     }
  1076.     if (m_Normalize) {
  1077.       m_Normalization = new Normalize();
  1078.       m_Normalization.setInputFormat(insts);
  1079.       insts = Filter.useFilter(insts, m_Normalization); 
  1080.     } else {
  1081.       m_Normalization = null;
  1082.     }
  1083.     if (!m_onlyNumeric) {
  1084.       m_NominalToBinary = new NominalToBinary();
  1085.       m_NominalToBinary.setInputFormat(insts);
  1086.       insts = Filter.useFilter(insts, m_NominalToBinary);
  1087.     } else {
  1088.       m_NominalToBinary = null;
  1089.     }
  1090.     m_classIndex = insts.classIndex();
  1091.     m_classAttribute = insts.classAttribute();
  1092.     // Generate subsets representing each class
  1093.     Instances[] subsets = new Instances[insts.numClasses()];
  1094.     for (int i = 0; i < insts.numClasses(); i++) {
  1095.       subsets[i] = new Instances(insts, insts.numInstances());
  1096.     }
  1097.     for (int j = 0; j < insts.numInstances(); j++) {
  1098.       Instance inst = insts.instance(j);
  1099.       subsets[(int)inst.classValue()].add(inst);
  1100.     }
  1101.     for (int i = 0; i < insts.numClasses(); i++) {
  1102.       subsets[i].compactify();
  1103.     }
  1104.     // Build the binary classifiers
  1105.     m_classifiers = new BinarySMO[insts.numClasses()][insts.numClasses()];
  1106.     for (int i = 0; i < insts.numClasses(); i++) {
  1107.       for (int j = i + 1; j < insts.numClasses(); j++) {
  1108. m_classifiers[i][j] = new BinarySMO();
  1109. Instances data = new Instances(insts, insts.numInstances());
  1110. for (int k = 0; k < subsets[i].numInstances(); k++) {
  1111.   data.add(subsets[i].instance(k));
  1112. }
  1113. for (int k = 0; k < subsets[j].numInstances(); k++) {
  1114.   data.add(subsets[j].instance(k));
  1115. }
  1116. data.compactify();
  1117. m_classifiers[i][j].buildClassifier(data, i, j);
  1118.       }
  1119.     }
  1120.   }
  1121.   /**
  1122.    * Returns an array of votes for the given instance.
  1123.    * @param inst the instance
  1124.    * @return array of votex
  1125.    * @exception Exception if something goes wrong
  1126.    */
  1127.   public int[] obtainVotes(Instance inst) throws Exception {
  1128.     // Filter instance
  1129.     if (!m_checksTurnedOff) {
  1130.       m_Missing.input(inst);
  1131.       m_Missing.batchFinished();
  1132.       inst = m_Missing.output();
  1133.     }
  1134.     
  1135.     if (m_Normalize) {
  1136.       m_Normalization.input(inst);
  1137.       m_Normalization.batchFinished();
  1138.       inst = m_Normalization.output();
  1139.     }
  1140.     if (!m_onlyNumeric) {
  1141.       m_NominalToBinary.input(inst);
  1142.       m_NominalToBinary.batchFinished();
  1143.       inst = m_NominalToBinary.output();
  1144.     }
  1145.     int[] votes = new int[inst.numClasses()];
  1146.     for (int i = 0; i < inst.numClasses(); i++) {
  1147.       for (int j = i + 1; j < inst.numClasses(); j++) {
  1148. double output = m_classifiers[i][j].SVMOutput(-1, inst);
  1149. if (output > 0) {
  1150.   votes[j] += 1;
  1151. } else {
  1152.   votes[i] += 1;
  1153. }
  1154.       }
  1155.     }
  1156.     return votes;
  1157.   }
  1158.   /**
  1159.    * Returns the coefficients in sparse format.  Throws an exception
  1160.    * if there is more than one machine or if the machine is not
  1161.    * linear.  
  1162.    */
  1163.   public FastVector weights() throws Exception {
  1164.     
  1165.     if (m_classifiers.length > 2) {
  1166.       throw new Exception("More than one machine has been built.");
  1167.     }
  1168.     if (m_classifiers[0][1].m_sparseWeights == null) {
  1169.       throw new Exception("No weight vector available.");
  1170.     }
  1171.     FastVector vec = new FastVector(2);
  1172.     vec.addElement(m_classifiers[0][1].m_sparseWeights);
  1173.     vec.addElement(m_classifiers[0][1].m_sparseIndices);
  1174.     return vec;
  1175.   }
  1176.   /**
  1177.    * Classifies a given instance
  1178.    * @param inst the instance
  1179.    * @return the classification in internal format
  1180.    * @exception Exception if something goes wrong
  1181.    */
  1182.   public double classifyInstance(Instance inst) throws Exception {
  1183.     return (double)Utils.maxIndex(obtainVotes(inst));
  1184.   }
  1185.   /**
  1186.    * Returns an enumeration describing the available options.
  1187.    *
  1188.    * @return an enumeration of all the available options.
  1189.    */
  1190.   public Enumeration listOptions() {
  1191.     Vector newVector = new Vector(10);
  1192.     newVector.addElement(new Option("tThe complexity constant C. (default 1)",
  1193.     "C", 1, "-C <double>"));
  1194.     newVector.addElement(new Option("tThe exponent for the "
  1195.     + "polynomial kernel. (default 1)",
  1196.     "E", 1, "-E <double>"));
  1197.     newVector.addElement(new Option("tGamma for the "
  1198.     + "RBF kernel. (default 0.01)",
  1199.     "G", 1, "-G <double>"));
  1200.     newVector.addElement(new Option("tDon't normalize the data.",
  1201.     "N", 0, "-N"));
  1202.     newVector.addElement(new Option("tRescale the kernel (only for non-linear polynomial kernels).",
  1203.     "L", 0, "-L"));
  1204.     newVector.addElement(new Option("tUse lower-order terms (only for non-linear polynomial kernels).",
  1205.     "O", 0, "-O"));
  1206.     newVector.addElement(new Option("tUse RBF kernel. " +
  1207.          "(default poly)",
  1208.     "R", 0, "-R"));
  1209.     newVector.addElement(new Option("tThe size of the kernel cache. " +
  1210.     "(default 1000003)",
  1211.     "A", 1, "-A <int>"));
  1212.     newVector.addElement(new Option("tThe tolerance parameter. " +
  1213.     "(default 1.0e-3)",
  1214.     "T", 1, "-T <double>"));
  1215.     newVector.addElement(new Option("tThe epsilon for round-off error. " +
  1216.     "(default 1.0e-12)",
  1217.     "P", 1, "-P <double>"));
  1218.     
  1219.     return newVector.elements();
  1220.   }
  1221.   /**
  1222.    * Parses a given list of options. Valid options are:<p>
  1223.    *
  1224.    * -C num <br>
  1225.    * The complexity constant C. (default 1)<p>
  1226.    *
  1227.    * -E num <br>
  1228.    * The exponent for the polynomial kernel. (default 1) <p>
  1229.    *
  1230.    * -G num <br>
  1231.    * Gamma for the RBF kernel. (default 0.01) <p>
  1232.    *
  1233.    * -N <br>
  1234.    * Don't normalize the training instances. <p>
  1235.    *
  1236.    * -L <br>
  1237.    * Rescale kernel (only for non-linear polynomial kernels). <p>
  1238.    *
  1239.    * -O <br>
  1240.    * Use lower-order terms (only for non-linear polynomial kernels). <p>
  1241.    *
  1242.    * -R <br>
  1243.    * Use RBF kernel (default poly). <p>
  1244.    * 
  1245.    * -A num <br>
  1246.    * Sets the size of the kernel cache. Should be a prime number. (default 1000003) <p>
  1247.    *
  1248.    * -T num <br>
  1249.    * Sets the tolerance parameter. (default 1.0e-3)<p>
  1250.    *
  1251.    * -P num <br>
  1252.    * Sets the epsilon for round-off error. (default 1.0e-12)<p>
  1253.    *
  1254.    * @param options the list of options as an array of strings
  1255.    * @exception Exception if an option is not supported
  1256.    */
  1257.   public void setOptions(String[] options) throws Exception {
  1258.     
  1259.     String complexityString = Utils.getOption('C', options);
  1260.     if (complexityString.length() != 0) {
  1261.       m_C = (new Double(complexityString)).doubleValue();
  1262.     } else {
  1263.       m_C = 1.0;
  1264.     }
  1265.     String exponentsString = Utils.getOption('E', options);
  1266.     if (exponentsString.length() != 0) {
  1267.       m_exponent = (new Double(exponentsString)).doubleValue();
  1268.     } else {
  1269.       m_exponent = 1.0;
  1270.     }
  1271.     String gammaString = Utils.getOption('G', options);
  1272.     if (gammaString.length() != 0) {
  1273.       m_gamma = (new Double(gammaString)).doubleValue();
  1274.     } else {
  1275.       m_gamma = 0.01;
  1276.     }
  1277.     String cacheString = Utils.getOption('A', options);
  1278.     if (cacheString.length() != 0) {
  1279.       m_cacheSize = Integer.parseInt(cacheString);
  1280.     } else {
  1281.       m_cacheSize = 1000003;
  1282.     }
  1283.     String toleranceString = Utils.getOption('T', options);
  1284.     if (toleranceString.length() != 0) {
  1285.       m_tol = (new Double(toleranceString)).doubleValue();
  1286.     } else {
  1287.       m_tol = 1.0e-3;
  1288.     }
  1289.     String epsilonString = Utils.getOption('P', options);
  1290.     if (epsilonString.length() != 0) {
  1291.       m_eps = (new Double(epsilonString)).doubleValue();
  1292.     } else {
  1293.       m_eps = 1.0e-12;
  1294.     }
  1295.     m_useRBF = Utils.getFlag('R', options);
  1296.     m_Normalize = !Utils.getFlag('N', options);
  1297.     m_rescale = Utils.getFlag('L', options);
  1298.     if ((m_useRBF) && (m_rescale)) {
  1299.       throw new Exception("Can't use rescaling with RBF machine.");
  1300.     }
  1301.     if ((m_exponent == 1.0) && (m_rescale)) {
  1302.       throw new Exception("Can't use rescaling with linear machine.");
  1303.     }
  1304.     m_lowerOrder = Utils.getFlag('O', options);
  1305.     if ((m_useRBF) && (m_lowerOrder)) {
  1306.       throw new Exception("Can't use lower-order terms with RBF machine.");
  1307.     }
  1308.     if ((m_exponent == 1.0) && (m_lowerOrder)) {
  1309.       throw new Exception("Can't use lower-order terms with linear machine.");
  1310.     }
  1311.   }
  1312.   /**
  1313.    * Gets the current settings of the classifier.
  1314.    *
  1315.    * @return an array of strings suitable for passing to setOptions
  1316.    */
  1317.   public String [] getOptions() {
  1318.     String [] options = new String [16];
  1319.     int current = 0;
  1320.     options[current++] = "-C"; options[current++] = "" + m_C;
  1321.     options[current++] = "-E"; options[current++] = "" + m_exponent;
  1322.     options[current++] = "-G"; options[current++] = "" + m_gamma;
  1323.     options[current++] = "-A"; options[current++] = "" + m_cacheSize;
  1324.     options[current++] = "-T"; options[current++] = "" + m_tol;
  1325.     options[current++] = "-P"; options[current++] = "" + m_eps;
  1326.     if (!m_Normalize) {
  1327.       options[current++] = "-N";
  1328.     }
  1329.     if (m_rescale) {
  1330.       options[current++] = "-L";
  1331.     }
  1332.     if (m_lowerOrder) {
  1333.       options[current++] = "-O";
  1334.     }
  1335.     if (m_useRBF) {
  1336.       options[current++] = "-R";
  1337.     }
  1338.     while (current < options.length) {
  1339.       options[current++] = "";
  1340.     }
  1341.     return options;
  1342.   }
  1343.   
  1344.   /**
  1345.    * Get the value of exponent. 
  1346.    *
  1347.    * @return Value of exponent.
  1348.    */
  1349.   public double getExponent() {
  1350.     
  1351.     return m_exponent;
  1352.   }
  1353.   
  1354.   /**
  1355.    * Set the value of exponent. If linear kernel
  1356.    * is used, rescaling and lower-order terms are
  1357.    * turned off.
  1358.    *
  1359.    * @param v  Value to assign to exponent.
  1360.    */
  1361.   public void setExponent(double v) {
  1362.     
  1363.     if (v == 1.0) {
  1364.       m_rescale = false;
  1365.       m_lowerOrder = false;
  1366.     }
  1367.     m_exponent = v;
  1368.   }
  1369.   
  1370.   /**
  1371.    * Get the value of gamma. 
  1372.    *
  1373.    * @return Value of gamma.
  1374.    */
  1375.   public double getGamma() {
  1376.     
  1377.     return m_gamma;
  1378.   }
  1379.   
  1380.   /**
  1381.    * Set the value of gamma. 
  1382.    *
  1383.    * @param v  Value to assign to gamma.
  1384.    */
  1385.   public void setGamma(double v) {
  1386.     
  1387.     m_gamma = v;
  1388.   }
  1389.   
  1390.   /**
  1391.    * Get the value of C.
  1392.    *
  1393.    * @return Value of C.
  1394.    */
  1395.   public double getC() {
  1396.     
  1397.     return m_C;
  1398.   }
  1399.   
  1400.   /**
  1401.    * Set the value of C.
  1402.    *
  1403.    * @param v  Value to assign to C.
  1404.    */
  1405.   public void setC(double v) {
  1406.     
  1407.     m_C = v;
  1408.   }
  1409.   
  1410.   /**
  1411.    * Get the value of tolerance parameter.
  1412.    * @return Value of tolerance parameter.
  1413.    */
  1414.   public double getToleranceParameter() {
  1415.     
  1416.     return m_tol;
  1417.   }
  1418.   
  1419.   /**
  1420.    * Set the value of tolerance parameter.
  1421.    * @param v  Value to assign to tolerance parameter.
  1422.    */
  1423.   public void setToleranceParameter(double v) {
  1424.     
  1425.     m_tol = v;
  1426.   }
  1427.   
  1428.   /**
  1429.    * Get the value of epsilon.
  1430.    * @return Value of epsilon.
  1431.    */
  1432.   public double getEpsilon() {
  1433.     
  1434.     return m_eps;
  1435.   }
  1436.   
  1437.   /**
  1438.    * Set the value of epsilon.
  1439.    * @param v  Value to assign to epsilon.
  1440.    */
  1441.   public void setEpsilon(double v) {
  1442.     
  1443.     m_eps = v;
  1444.   }
  1445.   
  1446.   /**
  1447.    * Get the size of the kernel cache
  1448.    * @return Size of kernel cache.
  1449.    */
  1450.   public int getCacheSize() {
  1451.     
  1452.     return m_cacheSize;
  1453.   }
  1454.   
  1455.   /**
  1456.    * Set the value of the kernel cache.
  1457.    * @param v  Size of kernel cache.
  1458.    */
  1459.   public void setCacheSize(int v) {
  1460.     
  1461.     m_cacheSize = v;
  1462.   }
  1463.   
  1464.   /**
  1465.    * Check whether data is to be normalized.
  1466.    * @return true if data is to be normalized
  1467.    */
  1468.   public boolean getNormalizeData() {
  1469.     
  1470.     return m_Normalize;
  1471.   }
  1472.   
  1473.   /**
  1474.    * Set whether data is to be normalized.
  1475.    * @param v  true if data is to be normalized
  1476.    */
  1477.   public void setNormalizeData(boolean v) {
  1478.     
  1479.     m_Normalize = v;
  1480.   }
  1481.   
  1482.   /**
  1483.    * Check if the RBF kernel is to be used.
  1484.    * @return true if RBF
  1485.    */
  1486.   public boolean getUseRBF() {
  1487.     
  1488.     return m_useRBF;
  1489.   }
  1490.   
  1491.   /**
  1492.    * Set if the RBF kernel is to be used.
  1493.    * @param v  true if RBF
  1494.    */
  1495.   public void setUseRBF(boolean v) {
  1496.     if (v) {
  1497.       m_rescale = false;
  1498.       m_lowerOrder = false;
  1499.     }
  1500.     m_useRBF = v;
  1501.   }
  1502.   
  1503.   /**
  1504.    * Check whether kernel is being rescaled.
  1505.    * @return Value of rescale.
  1506.    */
  1507.   public boolean getRescaleKernel() throws Exception {
  1508.     return m_rescale;
  1509.   }
  1510.   
  1511.   /**
  1512.    * Set whether kernel is to be rescaled. Defaults
  1513.    * to false if a linear machine is built.
  1514.    * @param v  Value to assign to rescale.
  1515.    */
  1516.   public void setRescaleKernel(boolean v) throws Exception {
  1517.     
  1518.     if (m_exponent == 1.0 || m_useRBF) {
  1519.       m_rescale = false;
  1520.     } else {
  1521.       m_rescale = v;
  1522.     }
  1523.   }
  1524.   
  1525.   /**
  1526.    * Check whether lower-order terms are being used.
  1527.    * @return Value of lowerOrder.
  1528.    */
  1529.   public boolean getLowerOrderTerms() {
  1530.     
  1531.     return m_lowerOrder;
  1532.   }
  1533.   
  1534.   /**
  1535.    * Set whether lower-order terms are to be used. Defaults
  1536.    * to false if a linear machine is built.
  1537.    * @param v  Value to assign to lowerOrder.
  1538.    */
  1539.   public void setLowerOrderTerms(boolean v) {
  1540.     
  1541.     if (m_exponent == 1.0 || m_useRBF) {
  1542.       m_lowerOrder = false;
  1543.     } else {
  1544.       m_lowerOrder = v;
  1545.     }
  1546.   }
  1547.     /**
  1548.      * Prints out the classifier.
  1549.      *
  1550.      * @return a description of the classifier as a string
  1551.      */
  1552.     public String toString() {
  1553.       StringBuffer text = new StringBuffer();
  1554.       int printed = 0;
  1555.       if ((m_classAttribute == null)) {
  1556. return "SMO: No model built yet.";
  1557.       }
  1558.       try {
  1559. text.append("SMOnn");
  1560. for (int i = 0; i < m_classAttribute.numValues(); i++) {
  1561.   for (int j = i + 1; j < m_classAttribute.numValues(); j++) {
  1562.     text.append("Classifier for classes: " + 
  1563. m_classAttribute.value(i) + ", " +
  1564. m_classAttribute.value(j) + "nn");
  1565.     text.append(m_classifiers[i][j] + "nn");
  1566.   }
  1567. }
  1568.       } catch (Exception e) {
  1569. return "Can't print SMO classifier.";
  1570.       }
  1571.     
  1572.       return text.toString();
  1573.     }
  1574.   /**
  1575.    * Main method for testing this class.
  1576.    */
  1577.   public static void main(String[] argv) {
  1578.     Classifier scheme;
  1579.     try {
  1580.       scheme = new SMO();
  1581.       System.out.println(Evaluation.evaluateModel(scheme, argv));
  1582.     } catch (Exception e) {
  1583.       System.err.println(e.getMessage());
  1584.     }
  1585.   }
  1586.