/**
* 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();
}
}