/* * RapidMiner * * Copyright (C) 2001-2008 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.operator.learner.rules; import java.util.Collection; import java.util.Iterator; import java.util.Vector; import com.rapidminer.example.Attribute; import com.rapidminer.example.Example; import com.rapidminer.example.ExampleSet; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.learner.SimplePredictionModel; import com.rapidminer.tools.Tools; /** * Each object of this class represents a conjunctive rule with boolean target * and nominal attributes. It cannot be changed after construction. This * simplifies applications that maintain counts separately. Each attribute may * be tested at most once. For two rules it can be tested whether one subsumes * the other. A method for refinement allows to create each rule just once. * * This model may be used to query for the prediction of a single example, as * well as to predict complete ExampleSets. * * @author Martin Scholz * @version $Id: ConjunctiveRuleModel.java,v 1.8 2006/04/12 18:04:24 ingomierswa * Exp $ */ public class ConjunctiveRuleModel extends SimplePredictionModel { private static final long serialVersionUID = 9088700646188411002L; /** * Helper class for maintaining attribute-value tests. Objects cannot be * changed after construction. */ private static class Literal { private final Attribute myAttribute; private final double myValue; public Literal(Attribute attribute, double testedValue) { this.myAttribute = attribute; this.myValue = testedValue; } public Attribute getAttribute() { return this.myAttribute; } public double getValue() { return this.myValue; } public boolean testExample(Example example) { return (example.getValue(this.getAttribute()) == this.getValue()); } } // ------------------------------------ // the label to be predicted if all literals evaluate to true private final int predictedLabel; // the literals (body) of the rule to be combined conjunctively private final Vector<Literal> myLiterals = new Vector<Literal>(); /** * Constructor to create an empty rule that makes a default prediction * * @param exampleSet * the example set used for training * @param predictedLabel * specifies the head of the rule, i.e. which label to predict */ public ConjunctiveRuleModel(ExampleSet exampleSet, int predictedLabel) { super(exampleSet); this.predictedLabel = predictedLabel; } /** * Constructor to create an empty rule that makes a default prediction * * @param exampleSet * the example set used for training * @param predictedLabel * specifies the head of the rule, i.e. which label to predict */ public ConjunctiveRuleModel(ExampleSet exampleSet, int predictedLabel, int positives, int negatives) { super(exampleSet); this.predictedLabel = predictedLabel; } /** Constructor to clone a rule, but to change the head (prediction) */ public ConjunctiveRuleModel(ConjunctiveRuleModel ruleToClone, int predictedLabel) { super(ruleToClone.getTrainingHeader()); this.predictedLabel = predictedLabel; this.myLiterals.addAll(ruleToClone.myLiterals); } /** Constructor to create an empty rule that makes a default prediction */ public ConjunctiveRuleModel(ConjunctiveRuleModel ruleToExtend, Attribute attribute, double testValue) throws OperatorException { super(ruleToExtend.getTrainingHeader()); if (ruleToExtend.getPositionOfAttributeInRule(attribute) != -1) { throw new OperatorException("ConjunctiveRuleModels may not contain the same attribute twice!"); } this.predictedLabel = ruleToExtend.predictedLabel; this.myLiterals.addAll(ruleToExtend.myLiterals); Literal literalToAdd = new Literal(attribute, testValue); this.myLiterals.add(literalToAdd); } /** @return a <code>String</code> representation of this rule model. */ public String toString() { StringBuffer result = new StringBuffer(super.toString() + Tools.getLineSeparator() + " (" + this.getLabel().getName() + "=" + (this.getLabel().getMapping().mapIndex(this.predictedLabel)) + ") <-- "); for (int i = 0; i < this.getRuleLength(); i++) { Attribute att = this.getAttributeOfLiteral(i); String val = att.getMapping().mapIndex((int) this.getTestedValueAtLiteral(i)); result.append((i > 0 ? ", " : "") + ("(" + att.getName() + "=" + val + ")")); } return result.toString(); } /** * Works only for boolean labels and needs to be improved in case of * changing RapidMiner core classes. */ private double flipLabel(double nonPredictedLabel) { return (1.0d - nonPredictedLabel); } public double predict(Example example) throws OperatorException { Iterator it = this.myLiterals.iterator(); while (it.hasNext()) { Literal nextLiteral = (Literal) it.next(); if (nextLiteral.testExample(example) == false) { return this.flipLabel(this.predictedLabel); } } return this.predictedLabel; } /** @return the number of literals */ public int getRuleLength() { return this.myLiterals.size(); } /** @return the label this rule predicts */ public int getConclusion() { return this.predictedLabel; } /** * @param literalNumber * the number of the literal in the rule * @return the attribute tested in the specified literal */ public Attribute getAttributeOfLiteral(int literalNumber) { return this.myLiterals.get(literalNumber).getAttribute(); } /** * @param literalNumber * the number of the literal in the rule * @return the value an attribute needs to have in order to pass the test of * the specified literal */ public double getTestedValueAtLiteral(int literalNumber) { return this.myLiterals.get(literalNumber).getValue(); } /** * @param attribute * to look for in the conjunctive rule * @return the position (which is unique) of the attribute in the (ordered) * conjunctive rule, or -1, if the attribute has not been found */ public int getPositionOfAttributeInRule(Attribute attribute) { int ruleLength = this.getRuleLength(); int index = 0; while ((index < ruleLength) && (!attribute.equals(this.getAttributeOfLiteral(index)))) { index++; } return ((index == ruleLength) ? -1 : index); } /** * @param model * another ConjuctiveRuleModel * @return true, if this rule is a refinement of the specified rule, or if * both rules are equal. A rule refines another one, if it conatains * all of its lietrals and predicts the same label. */ public boolean isRefinementOf(ConjunctiveRuleModel model) { if (this == model) { return true; } int numLiterals = model.getRuleLength(); if (this.getRuleLength() < numLiterals || this.getConclusion() != model.getConclusion()) { return false; } for (int i = 0; i < numLiterals; i++) { Attribute attribute = model.getAttributeOfLiteral(i); int pos; if ((pos = this.getPositionOfAttributeInRule(attribute)) == -1) { return false; } // Any attribute is tested at most once, which simplifies // comparisons: if (model.getTestedValueAtLiteral(i) != this.getTestedValueAtLiteral(pos)) { return false; } } return true; } /** * Two rules are equal, if they are both permutations of the same set of * literals and predict the same label. */ public boolean equals(Object object) { if (object == null) return false; if (this == object) return true; if (!(object instanceof ConjunctiveRuleModel)) return false; ConjunctiveRuleModel rule = (ConjunctiveRuleModel) object; if (this.getRuleLength() != rule.getRuleLength() || this.getConclusion() != rule.getConclusion()) return false; for (int i = 0; i < this.getRuleLength(); i++) { Attribute att = this.getAttributeOfLiteral(i); int pos; if ((pos = rule.getPositionOfAttributeInRule(att)) == -1 || this.getTestedValueAtLiteral(i) != rule.getTestedValueAtLiteral(pos)) return false; } return true; } public int hashCode() { return Integer.valueOf(this.getRuleLength()).hashCode() ^ Integer.valueOf(this.getConclusion()).hashCode() ^ myLiterals.hashCode(); } /** * Helper method of <code>getAllRefinedRules</code>. Iterates through the * Attributes of an ExampleSet and compares them to those part of the rule. * * @param exampleSet * @return the index of the first attribute of the exampleSet that is not * used in the rule, and for which no later Attribute is found in * the rule, either. */ protected int getFirstUnusedAttribute(ExampleSet exampleSet, Attribute[] allAttributes) { int numAttributes = allAttributes.length; int firstUnusedAttribute = numAttributes; for (int i = numAttributes - 1; i >= 0; i--) { Attribute exampleSetAttribute = allAttributes[i]; if (this.getPositionOfAttributeInRule(exampleSetAttribute) != -1) { return firstUnusedAttribute; } else firstUnusedAttribute = i; } return firstUnusedAttribute; } /** * A refinement method that - when applied sytematically during learning - * generates all rules for nominal attributes and a boolean target exactly * once. The top-down refinement is compatible with pruning, as long as * scores decrerase monotonically in support in the typical sense known from * subgroup discovery. Attributes are added in the same order in which they * occur in the provided exampleSet. * * @param exampleSet * used to identify attributes and their values for refinement * @return all refined ConjunctiveRuleModel objects */ public Collection<ConjunctiveRuleModel> getAllRefinedRules(ExampleSet exampleSet) throws OperatorException { Attribute[] allAttributes = exampleSet.getAttributes().createRegularAttributeArray(); int numAttributes = allAttributes.length; int firstUnused = this.getFirstUnusedAttribute(exampleSet, allAttributes); Vector<ConjunctiveRuleModel> theRefinements = new Vector<ConjunctiveRuleModel>(); for (int i = firstUnused; i < numAttributes; i++) { Attribute nextAttribute = allAttributes[i]; Iterator it = nextAttribute.getMapping().getValues().iterator(); while (it.hasNext()) { String valueS = (String) it.next(); int value = nextAttribute.getMapping().getIndex(valueS); ConjunctiveRuleModel refinedRule = new ConjunctiveRuleModel(this, nextAttribute, value); theRefinements.add(refinedRule); } } return theRefinements; } }