All Downloads are FREE. Search and download functionalities are using the official Maven repository.

weka.classifiers.lazy.kstar.KStarNominalAttribute Maven / Gradle / Ivy

Go to download

The Waikato Environment for Knowledge Analysis (WEKA), a machine learning workbench. This version represents the developer version, the "bleeding edge" of development, you could say. New functionality gets added to this version.

There is a newer version: 3.9.6
Show newest version
/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see .
 */

/**
 *    KStarNominalAttribute.java
 *    Copyright (C) 1995-2012 Univeristy of Waikato
 *    Java port to Weka by Abdelaziz Mahoui ([email protected]).
 *
 */

package weka.classifiers.lazy.kstar;

import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;

/**
 * A custom class which provides the environment for computing the
 * transformation probability of a specified test instance nominal attribute to
 * a specified train instance nominal attribute.
 * 
 * @author Len Trigg ([email protected])
 * @author Abdelaziz Mahoui ([email protected])
 * @version $Revision 1.0 $
 */
public class KStarNominalAttribute implements KStarConstants, RevisionHandler {

  /** The training instances used for classification. */
  protected Instances m_TrainSet;

  /** The test instance */
  protected Instance m_Test;

  /** The train instance */
  protected Instance m_Train;

  /** The index of the nominal attribute in the test and train instances */
  protected int m_AttrIndex;

  /** The stop parameter */
  protected double m_Stop = 1.0;

  /**
   * Probability of test attribute transforming into train attribute with
   * missing value
   */
  protected double m_MissingProb = 1.0;

  /**
   * Average probability of test attribute transforming into train attribute
   */
  protected double m_AverageProb = 1.0;

  /**
   * Smallest probability of test attribute transforming into train attribute
   */
  protected double m_SmallestProb = 1.0;

  /** Number of trai instances with no missing attribute values */
  protected int m_TotalCount;

  /** Distribution of the attribute value in the train dataset */
  protected int[] m_Distribution;

  /**
   * Set of colomns: each colomn representing a randomised version of the train
   * dataset class colomn
   */
  protected int[][] m_RandClassCols;

  /**
   * A cache for storing attribute values and their corresponding stop
   * parameters
   */
  protected KStarCache m_Cache;

  // KStar Global settings

  /** The number of instances in the dataset */
  protected int m_NumInstances;

  /** The number of class values */
  protected int m_NumClasses;

  /** The number of attributes */
  protected int m_NumAttributes;

  /** The class attribute type */
  protected int m_ClassType;

  /** missing value treatment */
  protected int m_MissingMode = M_AVERAGE;

  /** B_SPHERE = use specified blend, B_ENTROPY = entropic blend setting */
  protected int m_BlendMethod = B_SPHERE;

  /** default sphere of influence blend setting */
  protected int m_BlendFactor = 20;

  /**
   * Constructor
   */
  public KStarNominalAttribute(Instance test, Instance train, int attrIndex,
    Instances trainSet, int[][] randClassCol, KStarCache cache) {
    m_Test = test;
    m_Train = train;
    m_AttrIndex = attrIndex;
    m_TrainSet = trainSet;
    m_RandClassCols = randClassCol;
    m_Cache = cache;
    init();
  }

  /**
   * Initializes the m_Attributes of the class.
   */
  private void init() {
    try {
      m_NumInstances = m_TrainSet.numInstances();
      m_NumClasses = m_TrainSet.numClasses();
      m_NumAttributes = m_TrainSet.numAttributes();
      m_ClassType = m_TrainSet.classAttribute().type();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * Calculates the probability of the indexed nominal attribute of the test
   * instance transforming into the indexed nominal attribute of the training
   * instance.
   * 
   * @return the value of the transformation probability.
   */
  public double transProb() {
    double transProb = 0.0;
    // check if the attribute value has been encountred before
    // in which case it should be in the nominal cache
    if (m_Cache.containsKey(m_Test.value(m_AttrIndex))) {
      KStarCache.TableEntry te = m_Cache.getCacheValues(m_Test
        .value(m_AttrIndex));
      m_Stop = te.value;
      m_MissingProb = te.pmiss;
    } else {
      generateAttrDistribution();
      // we have to compute the parameters
      if (m_BlendMethod == B_ENTROPY) {
        m_Stop = stopProbUsingEntropy();
      } else { // default is B_SPHERE
        m_Stop = stopProbUsingBlend();
      }
      // store the values in cache
      m_Cache.store(m_Test.value(m_AttrIndex), m_Stop, m_MissingProb);
    }
    // we've got our m_Stop, then what?
    if (m_Train.isMissing(m_AttrIndex)) {
      transProb = m_MissingProb;
    } else {
      try {
        transProb = (1.0 - m_Stop) / m_Test.attribute(m_AttrIndex).numValues();
        if ((int) m_Test.value(m_AttrIndex) == (int) m_Train.value(m_AttrIndex)) {
          transProb += m_Stop;
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    return transProb;
  }

  /**
   * Calculates the "stop parameter" for this attribute using the entropy
   * method: the value is computed using a root finder algorithm. The method
   * takes advantage of the calculation to compute the smallest and average
   * transformation probabilities once the stop factor is obtained. It also sets
   * the transformation probability to an attribute with a missing value.
   * 
   * @return the value of the stop parameter.
   * 
   */
  private double stopProbUsingEntropy() {
    String debug = "(KStarNominalAttribute.stopProbUsingEntropy)";
    if (m_ClassType != Attribute.NOMINAL) {
      System.err.println("Error: " + debug
        + " attribute class must be nominal!");
      System.exit(1);
    }
    int itcount = 0;
    double stopProb;
    double lower, upper, pstop;
    double bestminprob = 0.0, bestpsum = 0.0;
    double bestdiff = 0.0, bestpstop = 0.0;
    double currentdiff, lastdiff, stepsize, delta;

    KStarWrapper botvals = new KStarWrapper();
    KStarWrapper upvals = new KStarWrapper();
    KStarWrapper vals = new KStarWrapper();

    // Initial values for root finder
    lower = 0.0 + ROOT_FINDER_ACCURACY / 2.0;
    upper = 1.0 - ROOT_FINDER_ACCURACY / 2.0;

    // Find (approx) entropy ranges
    calculateEntropy(upper, upvals);
    calculateEntropy(lower, botvals);

    if (upvals.avgProb == 0) {
      // When there are no training instances with the test value:
      // doesn't matter what exact value we use for pstop, just acts as
      // a constant scale factor in this case.
      calculateEntropy(lower, vals);
    } else {
      // Optimise the scale factor
      if ((upvals.randEntropy - upvals.actEntropy < botvals.randEntropy
        - botvals.actEntropy)
        && (botvals.randEntropy - botvals.actEntropy > FLOOR)) {
        bestpstop = pstop = lower;
        stepsize = INITIAL_STEP;
        bestminprob = botvals.minProb;
        bestpsum = botvals.avgProb;
      } else {
        bestpstop = pstop = upper;
        stepsize = -INITIAL_STEP;
        bestminprob = upvals.minProb;
        bestpsum = upvals.avgProb;
      }
      bestdiff = currentdiff = FLOOR;
      itcount = 0;
      /* Enter the root finder */
      while (true) {
        itcount++;
        lastdiff = currentdiff;
        pstop += stepsize;
        if (pstop <= lower) {
          pstop = lower;
          currentdiff = 0.0;
          delta = -1.0;
        } else if (pstop >= upper) {
          pstop = upper;
          currentdiff = 0.0;
          delta = -1.0;
        } else {
          calculateEntropy(pstop, vals);
          currentdiff = vals.randEntropy - vals.actEntropy;

          if (currentdiff < FLOOR) {
            currentdiff = FLOOR;
            if ((Math.abs(stepsize) < INITIAL_STEP) && (bestdiff == FLOOR)) {
              bestpstop = lower;
              bestminprob = botvals.minProb;
              bestpsum = botvals.avgProb;
              break;
            }
          }
          delta = currentdiff - lastdiff;
        }
        if (currentdiff > bestdiff) {
          bestdiff = currentdiff;
          bestpstop = pstop;
          bestminprob = vals.minProb;
          bestpsum = vals.avgProb;
        }
        if (delta < 0) {
          if (Math.abs(stepsize) < ROOT_FINDER_ACCURACY) {
            break;
          } else {
            stepsize /= -2.0;
          }
        }
        if (itcount > ROOT_FINDER_MAX_ITER) {
          break;
        }
      }
    }

    m_SmallestProb = bestminprob;
    m_AverageProb = bestpsum;
    // Set the probability of transforming to a missing value
    switch (m_MissingMode) {
    case M_DELETE:
      m_MissingProb = 0.0;
      break;
    case M_NORMAL:
      m_MissingProb = 1.0;
      break;
    case M_MAXDIFF:
      m_MissingProb = m_SmallestProb;
      break;
    case M_AVERAGE:
      m_MissingProb = m_AverageProb;
      break;
    }

    if (Math.abs(bestpsum - m_TotalCount) < EPSILON) {
      // No difference in the values
      stopProb = 1.0;
    } else {
      stopProb = bestpstop;
    }
    return stopProb;
  }

  /**
   * Calculates the entropy of the actual class prediction and the entropy for
   * random class prediction. It also calculates the smallest and average
   * transformation probabilities.
   * 
   * @param stop the stop parameter
   * @param params the object wrapper for the parameters: actual entropy, random
   *          entropy, average probability and smallest probability.
   * @return the values are returned in the object "params".
   * 
   */
  private void calculateEntropy(double stop, KStarWrapper params) {
    int i, j, k;
    Instance train;
    double actent = 0.0, randent = 0.0;
    double pstar, tprob, psum = 0.0, minprob = 1.0;
    double actClassProb, randClassProb;
    double[][] pseudoClassProb = new double[NUM_RAND_COLS + 1][m_NumClasses];
    // init ...
    for (j = 0; j <= NUM_RAND_COLS; j++) {
      for (i = 0; i < m_NumClasses; i++) {
        pseudoClassProb[j][i] = 0.0;
      }
    }
    for (i = 0; i < m_NumInstances; i++) {
      train = m_TrainSet.instance(i);
      if (!train.isMissing(m_AttrIndex)) {
        pstar = PStar(m_Test, train, m_AttrIndex, stop);
        tprob = pstar / m_TotalCount;
        if (pstar < minprob) {
          minprob = pstar;
        }
        psum += tprob;
        // filter instances with same class value
        for (k = 0; k <= NUM_RAND_COLS; k++) {
          // instance i is assigned a random class value in colomn k;
          // colomn k = NUM_RAND_COLS contains the original mapping:
          // instance -> class vlaue
          pseudoClassProb[k][m_RandClassCols[k][i]] += tprob;
        }
      }
    }
    // compute the actual entropy using the class probs
    // with the original class value mapping (colomn NUM_RAND_COLS)
    for (j = m_NumClasses - 1; j >= 0; j--) {
      actClassProb = pseudoClassProb[NUM_RAND_COLS][j] / psum;
      if (actClassProb > 0) {
        actent -= actClassProb * Math.log(actClassProb) / LOG2;
      }
    }
    // compute a random entropy using the pseudo class probs
    // excluding the colomn NUM_RAND_COLS
    for (k = 0; k < NUM_RAND_COLS; k++) {
      for (i = m_NumClasses - 1; i >= 0; i--) {
        randClassProb = pseudoClassProb[k][i] / psum;
        if (randClassProb > 0) {
          randent -= randClassProb * Math.log(randClassProb) / LOG2;
        }
      }
    }
    randent /= NUM_RAND_COLS;
    // return the results ... Yuk !!!
    params.actEntropy = actent;
    params.randEntropy = randent;
    params.avgProb = psum;
    params.minProb = minprob;
  }

  /**
   * Calculates the "stop parameter" for this attribute using the blend method:
   * the value is computed using a root finder algorithm. The method takes
   * advantage of this calculation to compute the smallest and average
   * transformation probabilities once the stop factor is obtained. It also sets
   * the transformation probability to an attribute with a missing value.
   * 
   * @return the value of the stop parameter.
   * 
   */
  private double stopProbUsingBlend() {
    int itcount = 0;
    double stopProb, aimfor;
    double lower, upper, tstop;

    KStarWrapper botvals = new KStarWrapper();
    KStarWrapper upvals = new KStarWrapper();
    KStarWrapper vals = new KStarWrapper();

    int testvalue = (int) m_Test.value(m_AttrIndex);
    aimfor = (m_TotalCount - m_Distribution[testvalue])
      * (double) m_BlendFactor / 100.0 + m_Distribution[testvalue];

    // Initial values for root finder
    tstop = 1.0 - m_BlendFactor / 100.0;
    lower = 0.0 + ROOT_FINDER_ACCURACY / 2.0;
    upper = 1.0 - ROOT_FINDER_ACCURACY / 2.0;

    // Find out function border values
    calculateSphereSize(testvalue, lower, botvals);
    botvals.sphere -= aimfor;
    calculateSphereSize(testvalue, upper, upvals);
    upvals.sphere -= aimfor;

    if (upvals.avgProb == 0) {
      // When there are no training instances with the test value:
      // doesn't matter what exact value we use for tstop, just acts as
      // a constant scale factor in this case.
      calculateSphereSize(testvalue, tstop, vals);
    } else if (upvals.sphere > 0) {
      // Can't include aimfor instances, going for min possible
      tstop = upper;
      vals.avgProb = upvals.avgProb;
    } else {
      // Enter the root finder
      for (;;) {
        itcount++;
        calculateSphereSize(testvalue, tstop, vals);
        vals.sphere -= aimfor;
        if (Math.abs(vals.sphere) <= ROOT_FINDER_ACCURACY
          || itcount >= ROOT_FINDER_MAX_ITER) {
          break;
        }
        if (vals.sphere > 0.0) {
          lower = tstop;
          tstop = (upper + lower) / 2.0;
        } else {
          upper = tstop;
          tstop = (upper + lower) / 2.0;
        }
      }
    }

    m_SmallestProb = vals.minProb;
    m_AverageProb = vals.avgProb;
    // Set the probability of transforming to a missing value
    switch (m_MissingMode) {
    case M_DELETE:
      m_MissingProb = 0.0;
      break;
    case M_NORMAL:
      m_MissingProb = 1.0;
      break;
    case M_MAXDIFF:
      m_MissingProb = m_SmallestProb;
      break;
    case M_AVERAGE:
      m_MissingProb = m_AverageProb;
      break;
    }

    if (Math.abs(vals.avgProb - m_TotalCount) < EPSILON) {
      // No difference in the values
      stopProb = 1.0;
    } else {
      stopProb = tstop;
    }
    return stopProb;
  }

  /**
   * Calculates the size of the "sphere of influence" defined as: sphere =
   * sum(P^2)/sum(P)^2 P(i|j) = (1-tstop)*P(i) + ((i==j)?tstop:0). This method
   * takes advantage of the calculation to compute the values of the "smallest"
   * and "average" transformation probabilities when using the specified stop
   * parameter.
   * 
   * @param testValue the value of the test instance
   * @param stop the stop parameter
   * @param params a wrapper of the parameters to be computed: "sphere" the
   *          sphere size "avgprob" the average transformation probability
   *          "minProb" the smallest transformation probability
   * @return the values are returned in "params" object.
   * 
   */
  private void calculateSphereSize(int testvalue, double stop,
    KStarWrapper params) {
    int i, thiscount;
    double tprob, tval = 0.0, t1 = 0.0;
    double sphere, minprob = 1.0, transprob = 0.0;

    for (i = 0; i < m_Distribution.length; i++) {
      thiscount = m_Distribution[i];
      if (thiscount != 0) {
        if (testvalue == i) {
          tprob = (stop + (1 - stop) / m_Distribution.length) / m_TotalCount;
          tval += tprob * thiscount;
          t1 += tprob * tprob * thiscount;
        } else {
          tprob = ((1 - stop) / m_Distribution.length) / m_TotalCount;
          tval += tprob * thiscount;
          t1 += tprob * tprob * thiscount;
        }
        if (minprob > tprob * m_TotalCount) {
          minprob = tprob * m_TotalCount;
        }
      }
    }
    transprob = tval;
    sphere = (t1 == 0) ? 0 : ((tval * tval) / t1);
    // return values ... Yck!!!
    params.sphere = sphere;
    params.avgProb = transprob;
    params.minProb = minprob;
  }

  /**
   * Calculates the nominal probability function defined as: P(i|j) = (1-stop) *
   * P(i) + ((i==j) ? stop : 0) In this case, it calculates the transformation
   * probability of the indexed test attribute to the indexed train attribute.
   * 
   * @param test the test instance
   * @param train the train instance
   * @param col the attribute index
   * @return the value of the tranformation probability.
   * 
   */
  private double PStar(Instance test, Instance train, int col, double stop) {
    double pstar;
    int numvalues = 0;
    try {
      numvalues = test.attribute(col).numValues();
    } catch (Exception ex) {
      ex.printStackTrace();
    }
    if ((int) test.value(col) == (int) train.value(col)) {
      pstar = stop + (1 - stop) / numvalues;
    } else {
      pstar = (1 - stop) / numvalues;
    }
    return pstar;
  }

  /**
   * Calculates the distribution, in the dataset, of the indexed nominal
   * attribute values. It also counts the actual number of training instances
   * that contributed (those with non-missing values) to calculate the
   * distribution.
   */
  private void generateAttrDistribution() {
    m_Distribution = new int[m_TrainSet.attribute(m_AttrIndex).numValues()];
    int i;
    Instance train;
    for (i = 0; i < m_NumInstances; i++) {
      train = m_TrainSet.instance(i);
      if (!train.isMissing(m_AttrIndex)) {
        m_TotalCount++;
        m_Distribution[(int) train.value(m_AttrIndex)]++;
      }
    }
  }

  /**
   * Sets the options.
   * 
   */
  public void setOptions(int missingmode, int blendmethod, int blendfactor) {
    m_MissingMode = missingmode;
    m_BlendMethod = blendmethod;
    m_BlendFactor = blendfactor;
  }

  /**
   * Returns the revision string.
   * 
   * @return the revision
   */
  @Override
  public String getRevision() {
    return RevisionUtils.extract("$Revision: 10153 $");
  }
} // class




© 2015 - 2024 Weber Informatics LLC | Privacy Policy