/** * 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.meta; import java.util.Iterator; import java.util.List; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorDescription; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.UserError; import com.rapidminer.operator.performance.PerformanceVector; import com.rapidminer.parameter.value.ParameterValueRange; import com.rapidminer.parameter.value.ParameterValues; /** * <p> * This operator finds the optimal values for a set of parameters using a grid search. The parameter * <var>parameters</var> is a list of key value pairs where the keys are of the form * <code>operator_name.parameter_name</code> and the value is either a comma separated list of * values (e.g. 10,15,20,25) or an interval definition in the format [start;end;stepsize] (e.g. * [10;25;5]). Alternatively a value grid pattern may be used by [e.g. [start;end;no_steps;scale], * where scale identifies the type of the pattern. * </p> * * <p> * The operator returns an optimal {@link ParameterSet} which can as well be written to a file with * a {@link com.rapidminer.extension.legacy.operator.io.ParameterSetWriter}. This parameter set can be read in * another process using a {@link com.rapidminer.extension.legacy.operator.io.ParameterSetLoader}. * </p> * * <p> * The file format of the parameter set file is straightforward and can easily be generated by * external applications. Each line is of the form <center> * <code>operator_name.parameter_name = value</code></center> * </p> * * <p> * Please refer to section {@rapidminer.ref sec:parameter_optimization|Advanced Processes/Parameter * and performance analysis} for an example application. Another parameter optimization schems like * the {@link EvolutionaryParameterOptimizationOperator} might also be useful if the best ranges and * dependencies are not known at all. Another operator which works similar to this parameter * optimization operator is the operator {@link ParameterIteration}. In contrast to the optimization * operator, this operator simply iterates through all parameter combinations. This might be * especially useful for plotting purposes. * </p> * * @author Simon Fischer, Helge Homburg, Ingo Mierswa, Tobias Malbrecht 15:35:49 ingomierswa Exp $ */ public class GridSearchParameterOptimizationOperator extends ParameterOptimizationOperator { protected Operator[] operators; protected String[] parameters; protected String[][] values; protected int[] currentIndex; protected int numberOfCombinations; protected int numberOfParameters; private ParameterSet best; public GridSearchParameterOptimizationOperator(OperatorDescription description) { super(description); } @Override public int getParameterValueMode() { return VALUE_MODE_DISCRETE; } /** * Computes the performance for the current parameter values. * * @deprecated As of version 6.0.4, replaced by {@link #computeCurrentPerformance()} */ @Deprecated protected PerformanceVector computeCurrentPerformeance() { try { return computeCurrentPerformance(); } catch (OperatorException e) { return null; } } /** Computes the performance for the current parameter values. */ protected PerformanceVector computeCurrentPerformance() throws OperatorException { // set all parameter values for (int j = 0; j < operators.length; j++) { operators[j].getParameters().setParameter(parameters[j], values[j][currentIndex[j]]); getLogger().fine(operators[j] + "." + parameters[j] + " = " + values[j][currentIndex[j]]); } return super.getPerformanceVector(); } protected void getParametersToOptimize() throws OperatorException { // check parameter values List<ParameterValues> parameterValuesList = parseParameterValues(getParameterList(PARAMETER_PARAMETERS)); numberOfCombinations = 1; numberOfParameters = parameterValuesList.size(); for (Iterator<ParameterValues> iterator = parameterValuesList.iterator(); iterator.hasNext();) { ParameterValues parameterValues = iterator.next(); if (parameterValues instanceof ParameterValueRange) { logWarning("found (and deleted) parameter values range (" + parameterValues.getKey() + ") which makes no sense in grid parameter optimization"); iterator.remove(); } numberOfCombinations *= parameterValues.getNumberOfValues(); } // initialize data structures operators = new Operator[parameterValuesList.size()]; parameters = new String[parameterValuesList.size()]; values = new String[parameterValuesList.size()][]; currentIndex = new int[parameterValuesList.size()]; // get parameter values and fill data structures int i = 0; for (Iterator<ParameterValues> iterator = parameterValuesList.iterator(); iterator.hasNext();) { ParameterValues parameterValues = iterator.next(); operators[i] = parameterValues.getOperator(); parameters[i] = parameterValues.getParameterType().getKey(); values[i] = parameterValues.getValuesArray(); i++; } } @Override public double getCurrentBestPerformance() { if (best != null) { return best.getPerformance().getMainCriterion().getAverage(); } else { return Double.NaN; } } @Override public void doWork() throws OperatorException { getParametersToOptimize(); // disable checkForStop, will be called in #inApplyLoop() anyway getProgress().setCheckForStop(false); getProgress().setTotal(numberOfCombinations); // start optimization log("Total number of combinations is " + numberOfCombinations); if (numberOfCombinations <= 1) { throw new UserError(this, 922); } int counter = 1; best = null; while (true) { getLogger().fine("Using parameter set " + counter + " / " + numberOfCombinations + ":"); PerformanceVector performance = computeCurrentPerformance(); // entering if-block if and only if performance != null, because without a performance // we can not say that this is the optimal set if (performance != null && (best == null || performance.compareTo(best.getPerformance()) > 0)) { String[] bestValues = new String[parameters.length]; for (int j = 0; j < parameters.length; j++) { bestValues[j] = values[j][currentIndex[j]]; } best = new ParameterSet(operators, parameters, bestValues, performance); passResultsThrough(); } // next parameter values int k = 0; boolean ok = true; while (!(++currentIndex[k] < values[k].length)) { currentIndex[k] = 0; k++; if (k >= currentIndex.length) { ok = false; break; } } if (!ok) { break; } inApplyLoop(); counter++; getProgress().setCompleted(counter); } deliver(best); getProgress().complete(); } }