KStar.java
Upload User: rhdiban
Upload Date: 2013-08-09
Package Size: 15085k
Code Size: 17k
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.  *    KS.java
  18.  *    Copyright (c) 1995-97 by Len Trigg (trigg@cs.waikato.ac.nz).
  19.  *    Java port to Weka by Abdelaziz Mahoui (am14@cs.waikato.ac.nz).
  20.  *
  21.  */
  22. package weka.classifiers.kstar;
  23. import java.io.*;
  24. import java.util.*;
  25. import weka.core.*;
  26. import weka.classifiers.*;
  27. //import java.text.NumberFormat;
  28. /**
  29.  * K* is an instance-based classifier, that is the class of a test
  30.  * instance is based upon the class of those training instances
  31.  * similar to it, as determined by some similarity function.  The
  32.  * underlying assumption of instance-based classifiers such as K*,
  33.  * IB1, PEBLS, etc, is that similar instances will have similar
  34.  * classes.
  35.  *
  36.  * For more information on K*, see <p>
  37.  * 
  38.  * John, G. Cleary and Leonard, E. Trigg (1995) "K*: An Instance-
  39.  * based Learner Using an Entropic Distance Measure",
  40.  * <i>Proceedings of the 12th International Conference on Machine
  41.  * learning</i>, pp. 108-114.<p>
  42.  *
  43.  * @author Len Trigg (len@intelligenesis.net)
  44.  * @author Abdelaziz Mahoui (am14@cs.waikato.ac.nz)
  45.  * @version $Revision 1.0 $
  46.  */
  47. public class KStar extends DistributionClassifier
  48.   implements KStarConstants, OptionHandler, UpdateableClassifier, WeightedInstancesHandler {
  49.   /** The training instances used for classification. */
  50.   protected Instances m_Train; 
  51.   /** The number of instances in the dataset */
  52.   protected int m_NumInstances;
  53.   /** The number of class values */
  54.   protected int m_NumClasses;
  55.   /** The number of attributes */
  56.   protected int m_NumAttributes;
  57.   /** The class attribute type */
  58.   protected int m_ClassType;
  59.   /** Table of random class value colomns */
  60.   protected int [][] m_RandClassCols;
  61.   /** Flag turning on and off the computation of random class colomns */
  62.   protected int m_ComputeRandomCols = ON;
  63.   /** Flag turning on and off the initialisation of config variables */
  64.   protected int m_InitFlag = ON;
  65.   /**
  66.    * A custom data structure for caching distinct attribute values
  67.    * and their scale factor or stop parameter.
  68.    */
  69.   protected KStarCache [] m_Cache;
  70.   /** missing value treatment */
  71.   protected int m_MissingMode = M_AVERAGE;
  72.   /** 0 = use specified blend, 1 = entropic blend setting */
  73.   protected int m_BlendMethod = B_SPHERE;
  74.   /** default sphere of influence blend setting */
  75.   protected int m_GlobalBlend = 20;
  76.   /** Define possible missing value handling methods */
  77.   public static final Tag [] TAGS_MISSING = {
  78.     new Tag(M_DELETE, "Ignore the instance with the missing value"),
  79.     new Tag(M_MAXDIFF, "Treat missing values as maximally different"),
  80.     new Tag(M_NORMAL, "Normilize over the attributes"),
  81.     new Tag(M_AVERAGE, "Average column entropy curves")
  82.       };
  83.   /**
  84.    * Generates the classifier.
  85.    *
  86.    * @param instances set of instances serving as training data 
  87.    * @exception Exception if the classifier has not been generated successfully
  88.    */
  89.   public void buildClassifier(Instances instances) throws Exception {
  90.     String debug = "(KStar.buildClassifier) ";
  91.     if (instances.classIndex() < 0)
  92.       throw new Exception ("No class attribute assigned to instances");
  93.     if (instances.checkForStringAttributes())
  94.       throw new Exception("Can't handle string attributes!");
  95.     m_Train = new Instances(instances, 0, instances.numInstances());
  96.     // Throw away training instances with missing class
  97.     m_Train.deleteWithMissingClass();
  98.     // initializes class attributes ** java-speaking! :-) **
  99.     init_m_Attributes();
  100.   }
  101.   
  102.   /**
  103.    * Adds the supplied instance to the training set
  104.    *
  105.    * @param instance the instance to add
  106.    * @exception Exception if instance could not be incorporated successfully
  107.    */
  108.   public void updateClassifier(Instance instance) throws Exception {
  109.     String debug = "(KStar.updateClassifier) ";
  110.     if (m_Train.equalHeaders(instance.dataset()) == false)
  111.       throw new Exception("Incompatible instance types");
  112.     if ( instance.classIsMissing() )
  113.       return;
  114.     m_Train.add(instance);
  115.     // update relevant attributes ...
  116.     update_m_Attributes();
  117.   }
  118.   /**
  119.    * Calculates the class membership probabilities for the given test instance.
  120.    *
  121.    * @param instance the instance to be classified
  122.    * @return predicted class probability distribution
  123.    * @exception Exception if an error occurred during the prediction
  124.    */
  125.   public double [] distributionForInstance(Instance instance) throws Exception {
  126.     String debug = "(KStar.distributionForInstance) ";
  127.     double transProb = 0.0, temp = 0.0;
  128.     double [] classProbability = new double[m_NumClasses];
  129.     double [] predictedValue = new double[1];
  130.     // initialization ...
  131.     for (int i=0; i<classProbability.length; i++) {
  132.       classProbability[i] = 0.0;
  133.     }
  134.     predictedValue[0] = 0.0;
  135.     if (m_InitFlag == ON) {
  136. // need to compute them only once and will be used for all instances.
  137. // We are doing this because the evaluation module controls the calls. 
  138.       if (m_BlendMethod == B_ENTROPY) {
  139. generateRandomClassColomns();
  140.       }
  141.       m_Cache = new KStarCache[m_NumAttributes];
  142.       for (int i=0; i<m_NumAttributes;i++) {
  143. m_Cache[i] = new KStarCache();
  144.       }
  145.       m_InitFlag = OFF;
  146.       //      System.out.println("Computing...");
  147.     }
  148.     // init done.
  149.     Instance trainInstance;
  150.     Enumeration enum = m_Train.enumerateInstances();
  151.     while ( enum.hasMoreElements() ) {
  152.       trainInstance = (Instance)enum.nextElement();
  153.       transProb = instanceTransformationProbability(instance, trainInstance);      
  154.       switch ( m_ClassType )
  155. {
  156. case Attribute.NOMINAL:
  157.   classProbability[(int)trainInstance.classValue()] += transProb;
  158.   break;
  159. case Attribute.NUMERIC:
  160.   predictedValue[0] += transProb * trainInstance.classValue();
  161.   temp += transProb;
  162.   break;
  163. }
  164.     }
  165.     if (m_ClassType == Attribute.NOMINAL) {
  166.       double sum = Utils.sum(classProbability);
  167.       if (sum <= 0.0)
  168. for (int i=0; i<classProbability.length; i++)
  169.   classProbability[i] = 1/m_NumClasses;
  170.       else Utils.normalize(classProbability, sum);
  171.       return classProbability;
  172.     }
  173.     else {
  174.       predictedValue[0] = (temp != 0) ? predictedValue[0] / temp : 0.0;
  175.       return predictedValue;
  176.     }
  177.   }
  178.   /**
  179.    * Calculate the probability of the first instance transforming into the 
  180.    * second instance:
  181.    * the probability is the product of the transformation probabilities of 
  182.    * the attributes normilized over the number of instances used.
  183.    * 
  184.    * @param first the test instance
  185.    * @param second the train instance
  186.    * @return transformation probability value
  187.    */
  188.   private double instanceTransformationProbability(Instance first, 
  189.    Instance second) {
  190.     String debug = "(KStar.instanceTransformationProbability) ";
  191.     double transProb = 1.0;
  192.     int numMissAttr = 0;
  193.     for (int i = 0; i < m_NumAttributes; i++) {
  194.       if (i == m_Train.classIndex()) {
  195. continue; // ignore class attribute
  196.       }
  197.       if (first.isMissing(i)) { // test instance attribute value is missing
  198. numMissAttr++;
  199. continue;
  200.       }
  201.       transProb *= attrTransProb(first, second, i);
  202.       // normilize for missing values
  203.       if (numMissAttr != m_NumAttributes) {
  204. transProb = Math.pow(transProb, (double)m_NumAttributes / 
  205.      (m_NumAttributes - numMissAttr));
  206.       }
  207.       else { // weird case!
  208. transProb = 0.0;
  209.       }
  210.     }
  211.     // normilize for the train dataset
  212.      return transProb / m_NumInstances;
  213.   }
  214.   /**
  215.    * Calculates the transformation probability of the indexed test attribute 
  216.    * to the indexed train attribute.
  217.    *
  218.    * @param first the test instance.
  219.    * @param second the train instance.
  220.    * @param col the index of the attribute in the instance.
  221.    * @return the value of the transformation probability.
  222.    */
  223.   private double attrTransProb(Instance first, Instance second, int col) {
  224.     String debug = "(KStar.attrTransProb)";
  225.     double transProb = 0.0;
  226.     KStarNominalAttribute ksNominalAttr;
  227.     KStarNumericAttribute ksNumericAttr;
  228.     switch ( m_Train.attribute(col).type() )
  229.       {
  230.       case Attribute.NOMINAL:
  231. ksNominalAttr = new KStarNominalAttribute(first, second, col, m_Train, 
  232.   m_RandClassCols, 
  233.   m_Cache[col]);
  234. ksNominalAttr.setOptions(m_MissingMode, m_BlendMethod, m_GlobalBlend);
  235. transProb = ksNominalAttr.transProb();
  236. ksNominalAttr = null;
  237. break;
  238.       case Attribute.NUMERIC:
  239. ksNumericAttr = new KStarNumericAttribute(first, second, col, 
  240.   m_Train, m_RandClassCols, 
  241.   m_Cache[col]);
  242. ksNumericAttr.setOptions(m_MissingMode, m_BlendMethod, m_GlobalBlend);
  243. transProb = ksNumericAttr.transProb();
  244. ksNumericAttr = null;
  245. break;
  246.       }
  247.     return transProb;
  248.   }
  249.   /**
  250.    * Gets the method to use for handling missing values. Will be one of
  251.    * M_NORMAL, M_AVERAGE, M_MAXDIFF or M_DELETE.
  252.    *
  253.    * @return the method used for handling missing values.
  254.    */
  255.   public SelectedTag getMissingMode() {
  256.     return new SelectedTag(m_MissingMode, TAGS_MISSING);
  257.   }
  258.   
  259.   /**
  260.    * Sets the method to use for handling missing values. Values other than
  261.    * M_NORMAL, M_AVERAGE, M_MAXDIFF and M_DELETE will be ignored.
  262.    *
  263.    * @param newMode the method to use for handling missing values.
  264.    */
  265.   public void setMissingMode(SelectedTag newMode) {
  266.     
  267.     if (newMode.getTags() == TAGS_MISSING) {
  268.       m_MissingMode = newMode.getSelectedTag().getID();
  269.     }
  270.   }
  271.   /**
  272.    * Returns an enumeration describing the available options
  273.    *
  274.    * @return an enumeration of all the available options
  275.    */
  276.   public Enumeration listOptions() {
  277.     Vector optVector = new Vector( 3 );
  278.     optVector.addElement(new Option(
  279.       "tManual blend setting (default 20%)n",
  280.       "B", 1, "-B <num>"));
  281.     optVector.addElement(new Option(
  282.       "tEnable entropic auto-blend setting (symbolic class only)n",
  283.       "E", 0, "-E"));
  284.     optVector.addElement(new Option(
  285.       "tSpecify the missing value treatment mode (default a)n"
  286.       +"tValid options are: a(verage), d(elete), m(axdiff), n(ormal)n",
  287.       "M", 1,"-M <char>"));
  288.     return optVector.elements();
  289.   }
  290.   /**
  291.    * Set the global blend parameter
  292.    * @param b the value for global blending
  293.    */
  294.   public void setGlobalBlend(int b) {
  295.      m_GlobalBlend = b;
  296.       if ( m_GlobalBlend > 100 ) {
  297. m_GlobalBlend = 100;
  298.       }
  299.       if ( m_GlobalBlend < 0 ) {
  300. m_GlobalBlend = 0;
  301.       }
  302.   }
  303.   /**
  304.    * Get the value of the global blend parameter
  305.    * @return the value of the global blend parameter
  306.    */
  307.   public int getGlobalBlend() {
  308.     return m_GlobalBlend;
  309.   }
  310.   /**
  311.    * Set whether entropic blending is to be used.
  312.    * @param e true if entropic blending is to be used
  313.    */
  314.   public void setEntropicAutoBlend(boolean e) {
  315.     if (e) {
  316.       m_BlendMethod = B_ENTROPY;
  317.     } else {
  318.       m_BlendMethod = B_SPHERE;
  319.     }
  320.   }
  321.   /**
  322.    * Get whether entropic blending being used
  323.    * @return true if entropic blending is used
  324.    */
  325.   public boolean getEntropicAutoBlend() {
  326.     if (m_BlendMethod == B_ENTROPY) {
  327.       return true;
  328.     }
  329.     return false;
  330.   }
  331.   /**
  332.    * Parses a given list of options. Valid options are:
  333.    * ...
  334.    *
  335.    * @param options the list of options as an array of strings
  336.    * @exception Exception if an option is not supported
  337.    */
  338.   public void setOptions(String[] options) throws Exception {
  339.     String debug = "(KStar.setOptions)";
  340.     String blendStr = Utils.getOption('B', options);
  341.     if (blendStr.length() != 0) {
  342.       setGlobalBlend(Integer.parseInt(blendStr));
  343.     }
  344.     setEntropicAutoBlend(Utils.getFlag('E', options));
  345.     
  346.     String missingModeStr = Utils.getOption('M', options);
  347.     if (missingModeStr.length() != 0) {
  348.       switch ( missingModeStr.charAt(0) ) {
  349.       case 'a':
  350. setMissingMode(new SelectedTag(M_AVERAGE, TAGS_MISSING));
  351. break;
  352.       case 'd':
  353. setMissingMode(new SelectedTag(M_DELETE, TAGS_MISSING));
  354. break;
  355.       case 'm':
  356. setMissingMode(new SelectedTag(M_MAXDIFF, TAGS_MISSING));
  357. break;
  358.       case 'n':
  359. setMissingMode(new SelectedTag(M_NORMAL, TAGS_MISSING));
  360. break;
  361.       default:
  362. setMissingMode(new SelectedTag(M_AVERAGE, TAGS_MISSING));
  363.       }
  364.     }
  365.     Utils.checkForRemainingOptions(options);
  366.   }
  367.   /**
  368.    * Gets the current settings of K*.
  369.    *
  370.    * @return an array of strings suitable for passing to setOptions()
  371.    */
  372.   public String [] getOptions() {
  373.     // -B <num> -E -M <char>
  374.     String [] options = new String [ 5 ];
  375.     int itr = 0;
  376.     options[itr++] = "-B";
  377.     options[itr++] = "" + m_GlobalBlend;
  378.     if (getEntropicAutoBlend()) {
  379.       options[itr++] = "-E";
  380.     }
  381.     options[itr++] = "-M";
  382.     if (m_MissingMode == M_AVERAGE) {
  383.       options[itr++] = "" + "a";
  384.     }
  385.     else if (m_MissingMode == M_DELETE) {
  386.       options[itr++] = "" + "d";
  387.     }
  388.     else if (m_MissingMode == M_MAXDIFF) {
  389.       options[itr++] = "" + "m";
  390.     }
  391.     else if (m_MissingMode == M_NORMAL) {
  392.       options[itr++] = "" + "n";
  393.     }
  394.     while (itr < options.length) {
  395.       options[itr++] = "";
  396.     }
  397.     return options;
  398.   }
  399.   /**
  400.    * Returns a description of this classifier.
  401.    *
  402.    * @return a description of this classifier as a string.
  403.    */
  404.   public String toString() {
  405.     StringBuffer st = new StringBuffer();
  406.     st.append("KStar Beta Verion (0.1b).n"
  407.       +"Copyright (c) 1995-97 by Len Trigg (trigg@cs.waikato.ac.nz).n"
  408.       +"Java port to Weka by Abdelaziz Mahoui "
  409.       +"(am14@cs.waikato.ac.nz).nnKStar options : ");
  410.     String [] ops = getOptions();
  411.     for (int i=0;i<ops.length;i++) {
  412.       st.append(ops[i]+' ');
  413.     }
  414.     return st.toString();
  415.   }
  416.   
  417.   /**
  418.    * Main method for testing this class.
  419.    *
  420.    * @param argv should contain command line options (see setOptions)
  421.    */
  422.   public static void main(String [] argv) {
  423.     try {
  424.       System.out.println(Evaluation.evaluateModel(new KStar(), argv));
  425.     } catch (Exception e) {
  426.       // System.err.println(e.getMessage());
  427.       e.printStackTrace();
  428.     }
  429.   }
  430.   /**
  431.    * Initializes the m_Attributes of the class.
  432.    */
  433.   private void init_m_Attributes() {
  434.     try {
  435.       m_NumInstances = m_Train.numInstances();
  436.       m_NumClasses = m_Train.numClasses();
  437.       m_NumAttributes = m_Train.numAttributes();
  438.       m_ClassType = m_Train.classAttribute().type();
  439.     } catch(Exception e) {
  440.       e.printStackTrace();
  441.     }
  442.   }
  443.   /**
  444.    * Updates the m_attributes of the class.
  445.    */
  446.   private void update_m_Attributes() {
  447.     m_NumInstances = m_Train.numInstances();
  448.     m_InitFlag = ON;
  449.   }
  450.   /**
  451.    * Note: for Nominal Class Only!
  452.    * Generates a set of random versions of the class colomn.
  453.    */
  454.   private void generateRandomClassColomns() {
  455.     String debug = "(KStar.generateRandomClassColomns)";
  456.     Random generator = new Random(42);
  457.     //    Random generator = new Random();
  458.     m_RandClassCols = new int [NUM_RAND_COLS+1][];
  459.     int [] classvals = classValues();
  460.     for (int i=0; i < NUM_RAND_COLS; i++) {
  461.       // generate a randomized version of the class colomn
  462.       m_RandClassCols[i] = randomize(classvals, generator);
  463.     }
  464.     // original colomn is preserved in colomn NUM_RAND_COLS
  465.     m_RandClassCols[NUM_RAND_COLS] = classvals;
  466.   }
  467.   /**
  468.    * Note: for Nominal Class Only!
  469.    * Returns an array of the class values
  470.    *
  471.    * @return an array of class values
  472.    */
  473.   private int [] classValues() {
  474.     String debug = "(KStar.classValues)";
  475.     int [] classval = new int[m_NumInstances];
  476.     for (int i=0; i < m_NumInstances; i++) {
  477.       try {
  478. classval[i] = (int)m_Train.instance(i).classValue();
  479.       } catch (Exception ex) {
  480. ex.printStackTrace();
  481.       }
  482.     }
  483.     return classval;
  484.   }
  485.   /**
  486.    * Returns a copy of the array with its elements randomly redistributed.
  487.    *
  488.    * @param array the array to randomize.
  489.    * @return a copy of the array with its elements randomly redistributed.
  490.    */
  491.   private int [] randomize(int [] array, Random generator) {
  492.     String debug = "(KStar.randomize)";
  493.     int index;
  494.     int temp;
  495.     int [] newArray = new int[array.length];
  496.     System.arraycopy(array, 0, newArray, 0, array.length);
  497.     for (int j = newArray.length - 1; j > 0; j--) {
  498.       index = (int) ( generator.nextDouble() * (double)j );
  499.       temp = newArray[j];
  500.       newArray[j] = newArray[index];
  501.       newArray[index] = temp;
  502.     }
  503.     return newArray;
  504.   }
  505. } // class end