/*
* 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.meta;
import java.util.List;
import com.rapidminer.operator.IOContainer;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.performance.PerformanceVector;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.ParameterTypeCategory;
import Jama.Matrix;
/**
* This operator finds the optimal values for a set of parameters using a
* quadratic interaction model. The parameter <var>parameters</var> is a list
* of key value pairs where the keys are of the form
* <code>OperatorName.parameter_name</code> and the value is a comma
* separated list of values (as for the GridParameterOptimization operator). <br/>
* The operator returns an optimal
* {@link ParameterSet} which can as well be written to a file with a
* {@link com.rapidminer.operator.io.ParameterSetLoader}. This parameter set
* can be read in another process using an
* {@link com.rapidminer.operator.io.ParameterSetLoader}. <br/> The file
* format of the parameter set file is straightforward and can also easily be
* generated by external applications. Each line is of the form
* <center><code>operator_name.parameter_name = value</code></center>.
*
* @author Stefan Rueping, Helge Homburg
* @version $Id: QuadraticParameterOptimizationOperator.java,v 1.11 2006/04/05
* 08:57:26 ingomierswa Exp $
*/
public class QuadraticParameterOptimizationOperator extends GridSearchParameterOptimizationOperator {
/** The parameter name for "What to do if range is exceeded." */
public static final String PARAMETER_IF_EXCEEDS_REGION = "if_exceeds_region";
/** The parameter name for "What to do if range is exceeded." */
public static final String PARAMETER_IF_EXCEEDS_RANGE = "if_exceeds_range";
private static final String[] EXCEED_BEHAVIORS = { "ignore", "clip", "fail" };
private static final int IGNORE = 0;
private static final int CLIP = 1;
private static final int FAIL = 2;
private ParameterSet best;
public QuadraticParameterOptimizationOperator(OperatorDescription description) {
super(description);
}
public double getCurrentBestPerformance() {
if (best != null) {
return best.getPerformance().getMainCriterion().getAverage();
} else {
return Double.NaN;
}
}
public IOObject[] apply() throws OperatorException {
IOContainer input = getInput();
getParametersToOptimize();
int ifExceedsRegion = getParameterAsInt(PARAMETER_IF_EXCEEDS_REGION);
int ifExceedsRange = getParameterAsInt(PARAMETER_IF_EXCEEDS_RANGE);
// sort parameter values
String[] valuesToSort;
String s;
double val1;
double val2;
int ind1;
int ind2;
for (int index = 0; index < numberOfParameters; index++) {
valuesToSort = values[index];
// straight-insertion-sort of valuesToSort
for (ind1 = 0; ind1 < valuesToSort.length; ind1++) {
val1 = Double.parseDouble(valuesToSort[ind1]);
for (ind2 = ind1 + 1; ind2 < valuesToSort.length; ind2++) {
val2 = Double.parseDouble(valuesToSort[ind2]);
if (val1 > val2) {
s = valuesToSort[ind1];
valuesToSort[ind1] = valuesToSort[ind2];
valuesToSort[ind2] = s;
val1 = val2;
};
};
};
};
best = null;
int[] bestIndex = new int[numberOfParameters];
ParameterSet[] allParameters = new ParameterSet[numberOfCombinations];
int paramIndex = 0;
// Test all parameter combinations
while (true) {
log("Using parameter set");
// set all parameter values
for (int j = 0; j < operators.length; j++) {
operators[j].getParameters().setParameter(parameters[j], values[j][currentIndex[j]]);
log(operators[j] + "." + parameters[j] + " = " + values[j][currentIndex[j]]);
}
setInput(input.copy());
PerformanceVector performance = getPerformance();
String[] currentValues = new String[parameters.length];
for (int j = 0; j < parameters.length; j++) {
currentValues[j] = values[j][currentIndex[j]];
};
allParameters[paramIndex] = new ParameterSet(operators, parameters, currentValues, performance);
if ((best == null) || (performance.compareTo(best.getPerformance()) > 0)) {
best = allParameters[paramIndex];
// bestIndex = currentIndex;
for (int j = 0; j < numberOfParameters; j++) {
bestIndex[j] = currentIndex[j];
};
};
// 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;
paramIndex++;
};
// start quadratic optimization
int nrParameters = 0;
for (int i = 0; i < numberOfParameters; i++) {
if ((values[i]).length > 2) {
log("Param " + i + ", bestI = " + bestIndex[i]);
nrParameters++;
if (bestIndex[i] == 0) {
bestIndex[i]++;
};
if (bestIndex[i] == (values[i]).length - 1) {
bestIndex[i]--;
};
} else {
logWarning("Parameter " + parameters[i] + " has <3 values, skipped.");
};
};
if (nrParameters > 3) {
logWarning("Optimization not recommended for >3 values. Check results carefully!");
};
if (nrParameters > 0) {
// Designmatrix A fuer den 3^nrParameters-Plan aufstellen,
// A*x=y loesen lassen
// x = neue Parameter
// check, ob neuen Parameter in zulaessigem Bereich
// - Okay, wenn in Kubus von 3^k-Plan
// - Warnung wenn in gegebenem Parameter-Bereich
// - Fehler sonst
int threetok = 1;
for (int i = 0; i < nrParameters; i++) {
threetok *= 3;
};
log("Optimising " + nrParameters + " parameters");
Matrix Designmatrix = new Matrix(threetok, nrParameters + nrParameters * (nrParameters + 1) / 2 + 1);
Matrix y = new Matrix(threetok, 1);
paramIndex = 0;
for (int i = numberOfParameters - 1; i >= 0; i--) {
if ((values[i]).length > 2) {
currentIndex[i] = bestIndex[i] - 1;
} else {
currentIndex[i] = bestIndex[i];
};
paramIndex = paramIndex * (values[i]).length + currentIndex[i];
};
int row = 0;
int c;
while (row < Designmatrix.getRowDimension()) {
y.set(row, 0, allParameters[paramIndex].getPerformance().getMainCriterion().getFitness()); // Performance
// Zahl?
Designmatrix.set(row, 0, 1.0);
c = 1;
// compute A
for (int i = 0; i < nrParameters; i++) {
if ((values[i]).length > 2) {
Designmatrix.set(row, c, Double.parseDouble(values[i][currentIndex[i]]));
c++;
};
};
// compute C
for (int i = 0; i < nrParameters; i++) {
if ((values[i]).length > 2) {
for (int j = i + 1; j < nrParameters; j++) {
if ((values[j]).length > 2) {
Designmatrix.set(row, c, Double.parseDouble(values[i][currentIndex[i]]) * Double.parseDouble(values[j][currentIndex[j]]));
c++;
};
};
};
};
// compute Q:
for (int i = 0; i < nrParameters; i++) {
if ((values[i]).length > 2) {
Designmatrix.set(row, c, Double.parseDouble(values[i][currentIndex[i]]) * Double.parseDouble(values[i][currentIndex[i]]));
c++;
};
};
// update currentIndex and paramIndex
int k = 0;
c = 1;
while (k < numberOfParameters) {
if ((values[k]).length > 2) {
currentIndex[k]++;
paramIndex += c;
if (currentIndex[k] > bestIndex[k] + 1) {
currentIndex[k] = bestIndex[k] - 1;
paramIndex -= 3 * c;
c *= values[k].length;
k++;
} else {
break;
};
} else {
c *= values[k].length;
k++;
};
};
row++;
};
// compute Designmatrix
Matrix beta = Designmatrix.solve(y);
for (int i = 0; i < Designmatrix.getColumnDimension(); i++) {
logWarning(" -- Writing " + beta.get(i, 0) + " at position " + i + " in vector b");
}
// generate Matrix P~
Matrix p = new Matrix(nrParameters, nrParameters);
int betapos = nrParameters + 1;
for (int j = 0; j < (nrParameters - 1); j++) {
for (int i = 1 + j; i < nrParameters; i++) {
p.set(i, j, (beta.get(betapos, 0) * 0.5));
p.set(j, i, (beta.get(betapos, 0) * 0.5));
betapos++;
}
}
for (int i = 0; i < nrParameters; i++) {
p.set(i, i, beta.get(betapos, 0));
betapos++;
}
// generate Matrix y~
Matrix y2 = new Matrix(nrParameters, 1);
for (int i = 0; i < nrParameters; i++) {
y2.set(i, 0, beta.get(i + 1, 0));
}
y2 = y2.times(-0.5);
// get stationary point x
Matrix x = new Matrix(nrParameters, 1);
try {
x = p.solve(y2);
} catch (RuntimeException e) {
logWarning("Quadratic optimization failed. (invalid matrix)");
}
String[] Qvalues = new String[numberOfParameters];
int pc = 0;
boolean ok = true;
for (int j = 0; j < numberOfParameters; j++) {
if ((values[j]).length > 2) {
if ((x.get(pc, 0) > Double.parseDouble(values[j][bestIndex[j] + 1])) || (x.get(pc, 0) < Double.parseDouble(values[j][bestIndex[j] - 1]))) {
logWarning("Parameter " + parameters[j] + " exceeds region of interest (" + x.get(pc, 0) + ")");
if (ifExceedsRegion == CLIP) {
// clip to bound
if (x.get(pc, 0) > Double.parseDouble(values[j][bestIndex[j] + 1])) {
x.set(pc, 0, Double.parseDouble(values[j][bestIndex[j] + 1]));
} else {
x.set(pc, 0, Double.parseDouble(values[j][bestIndex[j] - 1]));
};
} else if (ifExceedsRegion == FAIL) {
ok = false;
};
};
if ((x.get(pc, 0) < Double.parseDouble(values[j][0])) || (x.get(pc, 0) > Double.parseDouble(values[j][values[j].length - 1]))) {
logWarning("Parameter " + parameters[j] + " exceeds range (" + x.get(pc, 0) + ")");
if (ifExceedsRange == IGNORE) {
// ignore error
logWarning(" but no measures taken. Check parameters manually!");
} else if (ifExceedsRange == CLIP) {
// clip to bound
if (x.get(pc, 0) > Double.parseDouble(values[j][0])) {
x.set(pc, 0, Double.parseDouble(values[j][0]));
} else {
x.set(pc, 0, Double.parseDouble(values[j][values[j].length - 1]));
};
} else {
ok = false;
};
};
Qvalues[j] = x.get(pc, 0) + "";
pc++;
// Werte im richtigen Bereich?
} else {
Qvalues[j] = values[j][bestIndex[j]];
};
};
logWarning("Optimised parameter set:");
for (int j = 0; j < operators.length; j++) {
operators[j].getParameters().setParameter(parameters[j], Qvalues[j]);
log(" " + operators[j] + "." + parameters[j] + " = " + Qvalues[j]);
}
if (ok) {
setInput(input.copy());
PerformanceVector Qperformance = getPerformance();
log("Old: " + (best.getPerformance().getMainCriterion().getFitness()));
log("New: " + (Qperformance.getMainCriterion().getFitness()));
if (Qperformance.compareTo(best.getPerformance()) > 0) {
best = new ParameterSet(operators, parameters, Qvalues, Qperformance);
// log
log("Optimised parameter set does increase the performance");
} else {
// anderes log
log("Could not increase performance by quadratic optimization");
};
} else {
// not ok
logWarning("Parameters outside admissible range, not using optimised parameter set.");
};
} else {
// Warning: no parameters to optimize
logWarning("No parameters to optimize");
};
// end quadratic optimization
return new IOObject[] { best, best.getPerformance() };
}
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
types.add(new ParameterTypeCategory(PARAMETER_IF_EXCEEDS_REGION, "What to do if range is exceeded.", EXCEED_BEHAVIORS, CLIP));
types.add(new ParameterTypeCategory(PARAMETER_IF_EXCEEDS_RANGE, "What to do if range is exceeded.", EXCEED_BEHAVIORS, FAIL));
return types;
}
}