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

weka.classifiers.trees.m5.Rule 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 .
 */

/*
 *    Rule.java
 *    Copyright (C) 2000-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.classifiers.trees.m5;

import java.io.Serializable;

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

/**
 * Generates a single m5 tree or rule
 * 
 * @author Mark Hall
 * @version $Revision: 15357 $
 */
public class Rule implements Serializable, RevisionHandler {

  /** for serialization */
  private static final long serialVersionUID = -4458627451682483204L;

  protected static int LEFT = 0;
  protected static int RIGHT = 1;

  /**
   * the instances covered by this rule
   */
  private Instances m_instances;

  /**
   * the class index
   */
  private int m_classIndex;

  /**
   * the number of instances in the dataset
   */
  private int m_numInstances;

  /**
   * the indexes of the attributes used to split on for this rule
   */
  private int[] m_splitAtts;

  /**
   * the corresponding values of the split points
   */
  private double[] m_splitVals;

  /**
   * the corresponding internal nodes. Used for smoothing rules.
   */
  private RuleNode[] m_internalNodes;

  /**
   * the corresponding relational operators (0 = "<=", 1 = ">")
   */
  private int[] m_relOps;

  /**
   * the leaf encapsulating the linear model for this rule
   */
  private RuleNode m_ruleModel;

  /**
   * the top of the m5 tree for this rule
   */
  protected RuleNode m_topOfTree;

  /**
   * the standard deviation of the class for all the instances
   */
  private double m_globalStdDev;

  /**
   * the absolute deviation of the class for all the instances
   */
  private double m_globalAbsDev;

  /**
   * the instances covered by this rule
   */
  private Instances m_covered;

  /**
   * the number of instances covered by this rule
   */
  private int m_numCovered;

  /**
   * the instances not covered by this rule
   */
  private Instances m_notCovered;

  /**
   * use a pruned m5 tree rather than make a rule
   */
  private boolean m_useTree;

  /**
   * use the original m5 smoothing procedure
   */
  private boolean m_smoothPredictions;

  /**
   * Save instances at each node in an M5 tree for visualization purposes.
   */
  private boolean m_saveInstances;

  /**
   * Make a regression tree instead of a model tree
   */
  private boolean m_regressionTree;

  /**
   * Build unpruned tree/rule
   */
  private boolean m_useUnpruned;

  /**
   * The minimum number of instances to allow at a leaf node
   */
  private double m_minNumInstances;

  /**
   * The number of decimal places used for printing this rule.
   */
  private int m_numDecimalPlaces = 4;

  /**
   * Constructor declaration
   * 
   */
  public Rule() {
    m_useTree = false;
    m_smoothPredictions = false;
    m_useUnpruned = false;
    m_minNumInstances = 4;
  }

  /**
   * Generates a single rule or m5 model tree.
   * 
   * @param data set of instances serving as training data
   * @exception Exception if the rule has not been generated successfully
   */
  public void buildClassifier(Instances data) throws Exception {
    m_instances = null;
    m_topOfTree = null;
    m_covered = null;
    m_notCovered = null;
    m_ruleModel = null;
    m_splitAtts = null;
    m_splitVals = null;
    m_relOps = null;
    m_internalNodes = null;
    m_instances = data;
    m_classIndex = m_instances.classIndex();
    m_numInstances = m_instances.numInstances();

    // first calculate global deviation of class attribute
    m_globalStdDev = Rule.stdDev(m_classIndex, m_instances);
    m_globalAbsDev = Rule.absDev(m_classIndex, m_instances);

    m_topOfTree = new RuleNode(m_globalStdDev, m_globalAbsDev, null);
    m_topOfTree.setSaveInstances(m_saveInstances);
    m_topOfTree.setRegressionTree(m_regressionTree);
    m_topOfTree.setMinNumInstances(m_minNumInstances);
    m_topOfTree.setNumDecimalPlaces(getNumDecimalPlaces());
    m_topOfTree.buildClassifier(m_instances);

    if (!m_useUnpruned) {
      m_topOfTree.prune();
    } else {
      m_topOfTree.installLinearModels();
    }

    if (m_smoothPredictions) {
      m_topOfTree.installSmoothedModels();
    }
    // m_topOfTree.printAllModels();
    m_topOfTree.numLeaves(0);

    if (!m_useTree) {
      makeRule();
      // save space
      // m_topOfTree = null;
    }

    // save space
    m_instances = new Instances(m_instances, 0);

  }

  /**
   * Calculates a prediction for an instance using this rule or M5 model tree
   * 
   * @param instance the instance whos class value is to be predicted
   * @return the prediction
   * @exception Exception if a prediction can't be made.
   */
  public double classifyInstance(Instance instance) throws Exception {
    if (m_useTree) {
      return m_topOfTree.classifyInstance(instance);
    }

    // does the instance pass the rule's conditions?
    if (m_splitAtts.length > 0) {
      for (int i = 0; i < m_relOps.length; i++) {
        if (m_relOps[i] == LEFT) // left
        {
          if (instance.value(m_splitAtts[i]) > m_splitVals[i]) {
            throw new Exception("Rule does not classify instance");
          }
        } else {
          if (instance.value(m_splitAtts[i]) <= m_splitVals[i]) {
            throw new Exception("Rule does not classify instance");
          }
        }
      }
    }

    // the linear model's prediction for this rule
    return m_ruleModel.classifyInstance(instance);
  }

  /**
   * Returns the top of the tree.
   */
  public RuleNode topOfTree() {

    return m_topOfTree;
  }

  /**
   * Make the single best rule from a pruned m5 model tree
   * 
   * @exception Exception if something goes wrong.
   */
  private void makeRule() throws Exception {
    RuleNode[] best_leaf = new RuleNode[1];
    double[] best_cov = new double[1];
    RuleNode temp;

    m_notCovered = new Instances(m_instances, 0);
    m_covered = new Instances(m_instances, 0);
    best_cov[0] = -1;
    best_leaf[0] = null;

    m_topOfTree.findBestLeaf(best_cov, best_leaf);

    temp = best_leaf[0];

    if (temp == null) {
      throw new Exception("Unable to generate rule!");
    }

    // save the linear model for this rule
    m_ruleModel = temp;

    int count = 0;

    while (temp.parentNode() != null) {
      count++;
      temp = temp.parentNode();
    }

    temp = best_leaf[0];
    m_relOps = new int[count];
    m_splitAtts = new int[count];
    m_splitVals = new double[count];
    if (m_smoothPredictions) {
      m_internalNodes = new RuleNode[count];
    }

    // trace back to the root
    int i = 0;

    while (temp.parentNode() != null) {
      m_splitAtts[i] = temp.parentNode().splitAtt();
      m_splitVals[i] = temp.parentNode().splitVal();

      if (temp.parentNode().leftNode() == temp) {
        m_relOps[i] = LEFT;
        temp.parentNode().m_right = null;
      } else {
        m_relOps[i] = RIGHT;
        temp.parentNode().m_left = null;
      }

      if (m_smoothPredictions) {
        m_internalNodes[i] = temp.parentNode();
      }

      temp = temp.parentNode();
      i++;
    }

    // now assemble the covered and uncovered instances
    boolean ok;

    for (i = 0; i < m_numInstances; i++) {
      ok = true;

      for (int j = 0; j < m_relOps.length; j++) {
        if (m_relOps[j] == LEFT) {
          if (m_instances.instance(i).value(m_splitAtts[j]) > m_splitVals[j]) {
            m_notCovered.add(m_instances.instance(i));
            ok = false;
            break;
          }
        } else {
          if (m_instances.instance(i).value(m_splitAtts[j]) <= m_splitVals[j]) {
            m_notCovered.add(m_instances.instance(i));
            ok = false;
            break;
          }
        }
      }

      if (ok) {
        m_numCovered++;
        // m_covered.add(m_instances.instance(i));
      }
    }
  }

  /**
   * Return a description of the m5 tree or rule
   * 
   * @return a description of the m5 tree or rule as a String
   */
  @Override
  public String toString() {
    if (m_useTree) {
      return treeToString();
    } else {
      return ruleToString();
    }
  }

  /**
   * Return a description of the m5 tree
   * 
   * @return a description of the m5 tree as a String
   */
  private String treeToString() {
    StringBuffer text = new StringBuffer();

    if (m_topOfTree == null) {
      return "Tree/Rule has not been built yet!";
    }

    text.append("M5 " + ((m_useUnpruned) ? "unpruned " : "pruned ")
      + ((m_regressionTree) ? "regression " : "model ") + "tree:\n");

    if (m_smoothPredictions == true) {
      text.append("(using smoothed linear models)\n");
    }

    text.append(m_topOfTree.treeToString(0));
    text.append(m_topOfTree.printLeafModels());
    text.append("\nNumber of Rules : " + m_topOfTree.numberOfLinearModels());

    return text.toString();
  }

  /**
   * Return a description of the rule
   * 
   * @return a description of the rule as a String
   */
  private String ruleToString() {
    StringBuffer text = new StringBuffer();

    if (m_splitAtts.length > 0) {
      text.append("IF\n");

      for (int i = m_splitAtts.length - 1; i >= 0; i--) {
        text.append("\t" + m_covered.attribute(m_splitAtts[i]).name() + " ");

        if (m_relOps[i] == 0) {
          text.append("<= ");
        } else {
          text.append("> ");
        }

        text.append(Utils.doubleToString(m_splitVals[i], 1, getNumDecimalPlaces() - 1) + "\n");
      }

      text.append("THEN\n");
    }

    if (m_ruleModel != null) {
      try {
        text.append(m_ruleModel.printNodeLinearModel());
        text.append(" [" + m_numCovered/* m_covered.numInstances() */);

        if (m_globalAbsDev > 0.0) {
          text
            .append("/"
              + Utils.doubleToString(
                (100 * m_ruleModel.rootMeanSquaredError() / m_globalStdDev), 1,
                getNumDecimalPlaces() - 1) + "%]\n\n");
        } else {
          text.append("]\n\n");
        }
      } catch (Exception e) {
        return "Can't print rule";
      }
    }

    // System.out.println(m_instances);
    return text.toString();
  }

  /**
   * Use unpruned tree/rules
   * 
   * @param unpruned true if unpruned tree/rules are to be generated
   */
  public void setUnpruned(boolean unpruned) {
    m_useUnpruned = unpruned;
  }

  /**
   * Get whether unpruned tree/rules are being generated
   * 
   * @return true if unpruned tree/rules are to be generated
   */
  public boolean getUnpruned() {
    return m_useUnpruned;
  }

  /**
   * Use an m5 tree rather than generate rules
   * 
   * @param u true if m5 tree is to be used
   */
  public void setUseTree(boolean u) {
    m_useTree = u;
  }

  /**
   * get whether an m5 tree is being used rather than rules
   * 
   * @return true if an m5 tree is being used.
   */
  public boolean getUseTree() {
    return m_useTree;
  }

  /**
   * Smooth predictions
   * 
   * @param s true if smoothing is to be used
   */
  public void setSmoothing(boolean s) {
    m_smoothPredictions = s;
  }

  /**
   * Get whether or not smoothing has been turned on
   * 
   * @return true if smoothing is being used
   */
  public boolean getSmoothing() {
    return m_smoothPredictions;
  }

  /**
   * Get the instances not covered by this rule
   * 
   * @return the instances not covered
   */
  public Instances notCoveredInstances() {
    return m_notCovered;
  }

  /**
   * Free up memory consumed by the set of instances not covered by this rule.
   */
  public void freeNotCoveredInstances() {
    m_notCovered = null;
  }

  // /**
  // * Get the instances covered by this rule
  // *
  // * @return the instances covered by this rule
  // */
  // public Instances coveredInstances() {
  // return m_covered;
  // }

  /**
   * Returns the standard deviation value of the supplied attribute index.
   * 
   * @param attr an attribute index
   * @param inst the instances
   * @return the standard deviation value
   */
  protected static final double stdDev(int attr, Instances inst) {
    int i, count = 0;
    double sd, va, sum = 0.0, sqrSum = 0.0, value;

    for (i = 0; i <= inst.numInstances() - 1; i++) {
      count++;
      value = inst.instance(i).value(attr);
      sum += value;
      sqrSum += value * value;
    }

    if (count > 1) {
      va = (sqrSum - sum * sum / count) / count;
      va = Math.abs(va);
      sd = Math.sqrt(va);
    } else {
      sd = 0.0;
    }

    return sd;
  }

  /**
   * Returns the absolute deviation value of the supplied attribute index.
   * 
   * @param attr an attribute index
   * @param inst the instances
   * @return the absolute deviation value
   */
  protected static final double absDev(int attr, Instances inst) {
    int i;
    double average = 0.0, absdiff = 0.0, absDev;

    for (i = 0; i <= inst.numInstances() - 1; i++) {
      average += inst.instance(i).value(attr);
    }
    if (inst.numInstances() > 1) {
      average /= inst.numInstances();
      for (i = 0; i <= inst.numInstances() - 1; i++) {
        absdiff += Math.abs(inst.instance(i).value(attr) - average);
      }
      absDev = absdiff / inst.numInstances();
    } else {
      absDev = 0.0;
    }

    return absDev;
  }

  /**
   * Sets whether instances at each node in an M5 tree should be saved for
   * visualization purposes. Default is to save memory.
   * 
   * @param save a boolean value
   */
  protected void setSaveInstances(boolean save) {
    m_saveInstances = save;
  }

  /**
   * Get the value of regressionTree.
   * 
   * @return Value of regressionTree.
   */
  public boolean getRegressionTree() {

    return m_regressionTree;
  }

  /**
   * Set the value of regressionTree.
   * 
   * @param newregressionTree Value to assign to regressionTree.
   */
  public void setRegressionTree(boolean newregressionTree) {

    m_regressionTree = newregressionTree;
  }

  /**
   * Set the minumum number of instances to allow at a leaf node
   * 
   * @param minNum the minimum number of instances
   */
  public void setMinNumInstances(double minNum) {
    m_minNumInstances = minNum;
  }

  /**
   * Get the minimum number of instances to allow at a leaf node
   * 
   * @return a double value
   */
  public double getMinNumInstances() {
    return m_minNumInstances;
  }

  /**
   * Get the number of decimal places.
   */
  public int getNumDecimalPlaces() {
    return m_numDecimalPlaces;
  }

  /**
   * Set the number of decimal places.
   */
  public void setNumDecimalPlaces(int num) {
    m_numDecimalPlaces = num;
  }

  public RuleNode getM5RootNode() {
    return m_topOfTree;
  }

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy