/* * RapidMiner * * Copyright (C) 2001-2014 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.meta; import java.util.List; import org.jfree.util.Log; import com.rapidminer.example.ExampleSet; import com.rapidminer.operator.IOContainer; import com.rapidminer.operator.OperatorDescription; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.ProcessSetupError.Severity; import com.rapidminer.operator.SimpleProcessSetupError; import com.rapidminer.operator.performance.PerformanceVector; import com.rapidminer.operator.ports.InputPort; import com.rapidminer.operator.ports.metadata.MetaData; import com.rapidminer.operator.ports.metadata.SimplePrecondition; import com.rapidminer.parameter.ParameterType; import com.rapidminer.parameter.ParameterTypeBoolean; import com.rapidminer.parameter.ParameterTypeCategory; import com.rapidminer.parameter.ParameterTypeDouble; import com.rapidminer.parameter.ParameterTypeInt; import com.rapidminer.parameter.UndefinedParameterError; import com.rapidminer.parameter.conditions.BooleanParameterCondition; /** * Performs its inner operators until all given criteria are met or a timeout * occurs. * * @author Stefan Rueping */ public class RepeatUntilOperatorChain extends AbstractIteratingOperatorChain { public static final String PARAMETER_CONDITION_EXAMPLES = "condition_on_data"; /** The parameter name for "Minimal number of attributes in first example set" */ public static final String PARAMETER_MIN_ATTRIBUTES = "min_attributes"; /** The parameter name for "Maximal number of attributes in first example set" */ public static final String PARAMETER_MAX_ATTRIBUTES = "max_attributes"; /** The parameter name for "Minimal number of examples in first example set" */ public static final String PARAMETER_MIN_EXAMPLES = "min_examples"; /** The parameter name for "Maximal number of examples in first example set" */ public static final String PARAMETER_MAX_EXAMPLES = "max_examples"; /** The parameter name for "Minimal main criterion in first performance vector" */ public static final String PARAMETER_MIN_CRITERION = "min_criterion"; public static final String PARAMETER_CONDITION_PERFORMANCE = "condition_on_performance"; /** The parameter name for "Maximal main criterion in first performance vector" */ public static final String PARAMETER_MAX_CRITERION = "max_criterion"; /** The parameter name for "Maximum number of iterations" */ public static final String PARAMETER_MAX_ITERATIONS = "max_iterations"; public static final String PARAMETER_LIMIT_TIME = "limit_time"; /** The parameter name for "Timeout in minutes (-1 = no timeout)" */ public static final String PARAMETER_TIMEOUT = "timeout"; /** The parameter name for "Stop when performance of inner chain behaves like this." */ public static final String PARAMETER_PERFORMANCE_CHANGE = "performance_change"; /** The parameter name for "Evaluate condition before inner chain is applied (true) or after?" */ public static final String PARAMETER_CONDITION_BEFORE = "condition_before"; public static final String[] COMPARISONS = { "none", "decreasing", "non-increasing" }; public static final int NONE = 0; public static final int DECREASING = 1; public static final int NONINCREASING = 2; private long stoptime; private double fitness; private final InputPort performanceConditionInput = getSubprocess(0).getInnerSinks().createPort("performance"); private final InputPort exampleSetConditionInput = getSubprocess(0).getInnerSinks().createPort("example set"); public RepeatUntilOperatorChain(OperatorDescription description) { super(description); performanceConditionInput.addPrecondition(new SimplePrecondition(performanceConditionInput, new MetaData(PerformanceVector.class)) { @Override protected boolean isMandatory() { return getParameterAsBoolean(PARAMETER_CONDITION_PERFORMANCE); } }); exampleSetConditionInput.addPrecondition(new SimplePrecondition(exampleSetConditionInput, new MetaData(ExampleSet.class)) { @Override protected boolean isMandatory() { return getParameterAsBoolean(PARAMETER_CONDITION_EXAMPLES); } }); } @Override public void performAdditionalChecks() { if (!getParameterAsBoolean(PARAMETER_CONDITION_PERFORMANCE) && !getParameterAsBoolean(PARAMETER_CONDITION_EXAMPLES)) { this.addError(new SimpleProcessSetupError(Severity.ERROR, getPortOwner(), "need_one_parameter", PARAMETER_CONDITION_EXAMPLES, PARAMETER_CONDITION_PERFORMANCE)); } try { if (getParameterAsBoolean(PARAMETER_CONDITION_PERFORMANCE)) { double maxCrit = getParameterAsDouble(PARAMETER_MAX_CRITERION); double minCrit = getParameterAsDouble(PARAMETER_MIN_CRITERION); if ((maxCrit < Double.POSITIVE_INFINITY) || (minCrit > Double.NEGATIVE_INFINITY)) { if (maxCrit < minCrit) { this.addError(new SimpleProcessSetupError(Severity.ERROR, getPortOwner(), "parameter_combination_forbidden_range", PARAMETER_MIN_CRITERION, PARAMETER_MAX_CRITERION)); } } } if (getParameterAsBoolean(PARAMETER_CONDITION_EXAMPLES)) { int maxAtts = getParameterAsInt(PARAMETER_MAX_ATTRIBUTES); int minAtts = getParameterAsInt(PARAMETER_MIN_ATTRIBUTES); if ((maxAtts < Double.POSITIVE_INFINITY) || (minAtts > Double.NEGATIVE_INFINITY)) { if (maxAtts < minAtts) { this.addError(new SimpleProcessSetupError(Severity.ERROR, getPortOwner(), "parameter_combination_forbidden_range", PARAMETER_MIN_ATTRIBUTES, PARAMETER_MAX_ATTRIBUTES)); } } int maxEx = getParameterAsInt(PARAMETER_MAX_EXAMPLES); int minEx = getParameterAsInt(PARAMETER_MIN_EXAMPLES); if ((maxEx < Double.POSITIVE_INFINITY) || (minEx > Double.NEGATIVE_INFINITY)) { if (maxEx < minEx) { this.addError(new SimpleProcessSetupError(Severity.ERROR, getPortOwner(), "parameter_combination_forbidden_range", PARAMETER_MIN_EXAMPLES, PARAMETER_MAX_EXAMPLES)); } } } } catch (UndefinedParameterError e) { Log.error("parameter undefined", e); } } @Override public void doWork() throws OperatorException { stoptime = Long.MAX_VALUE; if (getParameterAsBoolean(PARAMETER_LIMIT_TIME)) stoptime = System.currentTimeMillis() + 60L * 1000 * getParameterAsInt(PARAMETER_TIMEOUT); fitness = Double.NEGATIVE_INFINITY; super.doWork(); } /** Evaluates whether the stopping condition is met * @throws OperatorException */ private boolean evaluateCondition(IOContainer input) throws OperatorException { if ((getIteration() == 0) && (!getParameterAsBoolean(PARAMETER_CONDITION_BEFORE))) { return false; } int maxit = getParameterAsInt(PARAMETER_MAX_ITERATIONS); if (getIteration() >= maxit) { getLogger().fine("Maximum number of iterations met."); return true; }; if (java.lang.System.currentTimeMillis() > stoptime) { getLogger().fine("Runtime exceeded."); return true; }; // NOTE: This is not optional if (getParameterAsBoolean(PARAMETER_CONDITION_PERFORMANCE)) { PerformanceVector performanceVector = performanceConditionInput.getData(PerformanceVector.class); int changeType = getParameterAsInt(PARAMETER_PERFORMANCE_CHANGE); if (changeType != NONE) { if (getIteration() > 0) { double currentFitness = performanceVector.getMainCriterion().getFitness(); if ((changeType == DECREASING) && (currentFitness < fitness)) { return true; } else if ((changeType == NONINCREASING) && (currentFitness <= fitness)) { return true; } fitness = currentFitness; return false; } } double maxCrit = getParameterAsDouble(PARAMETER_MAX_CRITERION); double minCrit = getParameterAsDouble(PARAMETER_MIN_CRITERION); if ((maxCrit < Double.POSITIVE_INFINITY) || (minCrit > Double.NEGATIVE_INFINITY)) { double crit = performanceVector.getMainCriterion().getAverage(); if ((crit > maxCrit) || (crit < minCrit)) return false; } } if (getParameterAsBoolean(PARAMETER_CONDITION_EXAMPLES)) { ExampleSet exampleSet = exampleSetConditionInput.getData(ExampleSet.class); int maxAtts = getParameterAsInt(PARAMETER_MAX_ATTRIBUTES); int minAtts = getParameterAsInt(PARAMETER_MIN_ATTRIBUTES); if ((maxAtts < Integer.MAX_VALUE) || (minAtts > 0)) { int nrAtts = exampleSet.getAttributes().size(); if ((nrAtts > maxAtts) || (nrAtts < minAtts)) return false; } int maxEx = getParameterAsInt(PARAMETER_MAX_EXAMPLES); int minEx = getParameterAsInt(PARAMETER_MIN_EXAMPLES); if ((maxEx < Integer.MAX_VALUE) || (minEx > 0)) { int nrEx = exampleSet.size(); if ((nrEx > maxEx) || (nrEx < minEx)) return false; } } getLogger().fine("All criteria met."); return true; } @Override boolean shouldStop(IOContainer iterationResults) throws OperatorException { return evaluateCondition(iterationResults); } @Override public List<ParameterType> getParameterTypes() { List<ParameterType> types = super.getParameterTypes(); ParameterType type = new ParameterTypeBoolean(PARAMETER_CONDITION_EXAMPLES, "If checked, conditions for the loop abortion based on the example set can be defined.", true); //type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_PERFORMANCE, true, false)); types.add(type); type = new ParameterTypeInt(PARAMETER_MIN_ATTRIBUTES, "Minimal number of attributes in first example set", 0, Integer.MAX_VALUE, 0); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_EXAMPLES, false, true)); type.setExpert(false); types.add(type); type = new ParameterTypeInt(PARAMETER_MAX_ATTRIBUTES, "Maximal number of attributes in first example set", 0, Integer.MAX_VALUE, 0); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_EXAMPLES, false, true)); type.setExpert(false); types.add(type); type = new ParameterTypeInt(PARAMETER_MIN_EXAMPLES, "Minimal number of examples in first example set", 0, Integer.MAX_VALUE, 0); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_EXAMPLES, false, true)); type.setExpert(false); types.add(type); type = new ParameterTypeInt(PARAMETER_MAX_EXAMPLES, "Maximal number of examples in first example set", 0, Integer.MAX_VALUE, Integer.MAX_VALUE); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_EXAMPLES, false, true)); type.setExpert(false); types.add(type); type = new ParameterTypeBoolean(PARAMETER_CONDITION_PERFORMANCE, "If checked, conditions for the loop abortion based on the performance can be defined.", true); types.add(type); type = new ParameterTypeDouble(PARAMETER_MIN_CRITERION, "Minimal main criterion in first performance vector", Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_PERFORMANCE, false, true)); type.setExpert(false); types.add(type); type = new ParameterTypeDouble(PARAMETER_MAX_CRITERION, "Maximal main criterion in first performance vector", Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_PERFORMANCE, false, true)); type.setExpert(false); types.add(type); type = new ParameterTypeCategory(PARAMETER_PERFORMANCE_CHANGE, "Stop when performance of inner chain behaves like this. 'none' means to ignore any performance changes.", COMPARISONS, NONE); type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_CONDITION_PERFORMANCE, false, true)); type.setExpert(false); types.add(type); type = new ParameterTypeInt(PARAMETER_MAX_ITERATIONS, "Maximum number of iterations", 0, Integer.MAX_VALUE, Integer.MAX_VALUE); type.setExpert(true); types.add(type); type = new ParameterTypeBoolean(PARAMETER_LIMIT_TIME, "If checked, the loop will be aborted at last after a specified time.", false); types.add(type); type = new ParameterTypeInt(PARAMETER_TIMEOUT, "Timeout in minutes", 1, Integer.MAX_VALUE, 1); type.setExpert(true); types.add(type); type = new ParameterTypeBoolean(PARAMETER_CONDITION_BEFORE, "Evaluate condition before inner chain is applied (true) or after?", false); type.setExpert(true); types.add(type); return types; } }