/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.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.igss.hypothesis; import com.rapidminer.example.Attribute; import com.rapidminer.example.Example; import java.util.Arrays; import java.util.LinkedList; /** * Objects of this class represent a conjunctive rule. All abstract methods of the superclass * Hypothesis are implemented. * * @author Dirk Dach */ public class Rule extends Hypothesis { private static final long serialVersionUID = -1121263970366784202L; /** All literals possible with the given attributeset. */ private Literal[][] allLiterals; /** The premise of the rule. */ private Literal[] literals; /** The index of the label class this rule predicts aka Y+ or Y-. */ private int prediction; /** Creates a new rule,initializes the regularAttributes and the literals attribute. */ public Rule(Attribute[] regularAttributes, Attribute label, boolean rejectionSampling, boolean createAll) { super(regularAttributes, label, rejectionSampling, createAll); allLiterals = new Literal[regularAttributes.length][]; for (int attributeIndex = 0; attributeIndex < regularAttributes.length; attributeIndex++) { allLiterals[attributeIndex] = new Literal[regularAttributes[attributeIndex].getMapping().size()]; for (int valueIndex = 0; valueIndex < regularAttributes[attributeIndex].getMapping().size(); valueIndex++) { allLiterals[attributeIndex][valueIndex] = new Literal(regularAttributes[attributeIndex], valueIndex, attributeIndex); } } } /** Clones the rule with covered and positive weight. */ @Override public Hypothesis clone() { Rule clone = new Rule(this.literals, this.prediction); clone.setCoveredWeight(this.getCoveredWeight()); clone.setPositiveWeight(this.getPositiveWeight()); return clone; } /** Construct a new rule with one literal. */ public Rule(Literal literal, int prediction) { super(); this.literals = new Literal[1]; this.literals[0] = literal; this.prediction = prediction; } /** Construct a new rule with the given literals. */ public Rule(Literal[] literals, int prediction) { super(); this.literals = new Literal[literals.length]; for (int i = 0; i < literals.length; i++) { this.literals[i] = literals[i]; } this.prediction = prediction; } /** Applies the rule to the given examples. */ @Override public void apply(Example e) { if (this.applicable(e)) { if (rejectionSampling) { coveredWeight++; if ((int) e.getLabel() == this.prediction) { positiveWeight++; } } else { coveredWeight += e.getWeight(); if ((int) e.getLabel() == this.prediction) { positiveWeight += e.getWeight(); } } } } /** * Test if the rule is applicable to the given examples without updating the corresponding * value. */ @Override public boolean applicable(Example e) { boolean success = true; for (int i = 0; i < literals.length; i++) { int exampleValue = (int) e.getValue(literals[i].getAttribute()); int ruleValue = literals[i].getValue(); if (exampleValue != ruleValue) { success = false; break; } } return success; } /** Creates all rules with length<=minComplexity. */ @Override public LinkedList<Hypothesis> init(int minComplexity) { LinkedList<Hypothesis> border = new LinkedList<Hypothesis>(); LinkedList<Hypothesis> result = new LinkedList<Hypothesis>(); // Add all hypothesis of lenght 1 to border. for (int attributeIndex = 0; attributeIndex < allLiterals.length; attributeIndex++) { for (int valueIndex = 0; valueIndex < allLiterals[attributeIndex].length; valueIndex++) { border.addLast(new Rule(allLiterals[attributeIndex][valueIndex], POSITIVE_CLASS)); // Create // h->Y+ // first. } } while (!border.isEmpty()) { Rule rule = (Rule) border.removeFirst(); result.addLast(rule); // Add h->Y+ to result. if (createAllHypothesis) { result.addLast(new Rule(rule.getLiterals(), NEGATIVE_CLASS)); // Add h->Y- to // result. } // No need to refine anymore if rule length already is equal to minComplexity. if (rule.getComplexity() < minComplexity) { border.addAll(rule.refine()); // Add h->Y+ only } } return result; } /** Creates all successors of the rule that have one more literal. */ @Override public LinkedList<Hypothesis> refine() { LinkedList<Hypothesis> result = new LinkedList<Hypothesis>(); Literal[] lits = new Literal[literals.length + 1]; // New rule contains all literals of the old rule for (int i = 0; i < literals.length; i++) { lits[i] = literals[i]; } // Create new Rules with the remainig literals with higher indices. // The literals testing the same attribute as the last literal of this rule are excluded. int lastLiteralIndex = literals[literals.length - 1].getIndex(); for (int literalIndex = lastLiteralIndex + 1; literalIndex < allLiterals.length; literalIndex++) { for (int valueIndex = 0; valueIndex < allLiterals[literalIndex].length; valueIndex++) { lits[lits.length - 1] = allLiterals[literalIndex][valueIndex]; result.addLast(new Rule(lits, this.prediction)); } } return result; } /** Returns true only if this hypothesis can still be refined. */ @Override public boolean canBeRefined() { // No literals can be appended if the last literal tests the last attribute. if ((literals[literals.length - 1].getIndex() == allLiterals.length - 1)) { return false; } else { return true; } } /** Returns the index of prediction of this rule */ @Override public int getPrediction() { return this.prediction; } /** Returns the lenght of the premise of the rule. */ @Override public int getComplexity() { return this.literals.length; } /** Returns true if the two rules have the same premise and make the same perdiction. */ @Override public boolean equals(Object o) { if (!(o instanceof Rule)) { return false; } Rule otherRule = (Rule) o; if (otherRule.literals.length != this.literals.length) { return false; } if (otherRule.prediction != this.prediction) { return false; } boolean result = true; for (int i = 0; i < this.literals.length; i++) { if (!(this.literals[i].equals(otherRule.literals[i]))) { result = false; break; } } return result; } @Override public int hashCode() { return Arrays.hashCode(this.literals) ^ Integer.valueOf(this.prediction).hashCode(); } /** Returns a String representation of the rule. */ @Override public String toString() { StringBuffer result = new StringBuffer("IF "); for (int i = 0; i < literals.length - 1; i++) { result.append(literals[i].toString() + " AND "); } result.append(literals[literals.length - 1].toString()); result.append(" THEN (" + getLabel().getName() + "=" + getLabel().getMapping().mapIndex(this.getPrediction()) + ")"); return result.toString(); } /** Returns the literals in the premise of this rule. */ public Literal[] getLiterals() { return literals; } }