weka.classifiers.trees.m5.Rule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of weka-dev Show documentation
Show all versions of weka-dev Show documentation
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.
/*
* 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