/*
* RapidMiner
*
* Copyright (C) 2001-2011 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.features.weighting;
import java.util.LinkedList;
import java.util.List;
import com.rapidminer.example.Attribute;
import com.rapidminer.example.AttributeWeights;
import com.rapidminer.example.Attributes;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.operator.OperatorDescription;
import com.rapidminer.operator.OperatorException;
import com.rapidminer.operator.features.Individual;
import com.rapidminer.operator.features.Population;
import com.rapidminer.operator.features.PopulationOperator;
import com.rapidminer.operator.features.selection.AbstractGeneticAlgorithm;
import com.rapidminer.operator.features.selection.SelectionCrossover;
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.UndefinedParameterError;
import com.rapidminer.parameter.conditions.BooleanParameterCondition;
/**
* This operator performs the weighting of features with an evolutionary strategies approach. The variance of the
* gaussian additive mutation can be adapted by a 1/5-rule.
*
* @author Ingo Mierswa, Sebastian Land
*/
public class EvolutionaryWeighting extends AbstractGeneticAlgorithm {
/** The parameter name for "The (initial) variance for each mutation." */
public static final String PARAMETER_MUTATION_VARIANCE = "mutation_variance";
/** The parameter name for "If set to true, the 1/5 rule for variance adaption is used." */
public static final String PARAMETER_1_5_RULE = "1_5_rule";
/** The parameter name for "If set to true, the weights are bounded between 0 and 1." */
public static final String PARAMETER_BOUNDED_MUTATION = "bounded_mutation";
/** The parameter name for "Probability for an individual to be selected for crossover." */
public static final String PARAMETER_P_CROSSOVER = "p_crossover";
/** The parameter name for "Type of the crossover." */
public static final String PARAMETER_CROSSOVER_TYPE = "crossover_type";
public static final String PARAMETER_INITIALIZE_WITH_INPUT_WEIGHTS = "initialize_with_input_weights";
public static final String PARAMETER_NOMINAL_MUTATION_RATE = "nominal_mutation_rate";
public static final String PARAMETER_DEFAULT_NOMINAL_MUTATION_RATE = "use_default_mutation_rate";
private WeightingMutation weighting = null;
private boolean useBoundedMutation = false;
private final InputPort attributeWeightsInput = getInputPorts().createPort("attribute weights in");
public EvolutionaryWeighting(OperatorDescription description) {
super(description);
attributeWeightsInput.addPrecondition(new SimplePrecondition(attributeWeightsInput, new MetaData(AttributeWeights.class), false));
}
@Override
public PopulationOperator getCrossoverPopulationOperator(ExampleSet eSet) throws UndefinedParameterError {
return new SelectionCrossover(getParameterAsInt(PARAMETER_CROSSOVER_TYPE), getParameterAsDouble(PARAMETER_P_CROSSOVER), getRandom(), 1, eSet.getAttributes().size(), -1);
}
@Override
public PopulationOperator getMutationPopulationOperator(ExampleSet eSet) throws UndefinedParameterError {
Attributes attributes = eSet.getAttributes();
boolean[] isNominal = new boolean[attributes.size()];
int i = 0;
for (Attribute attribute : attributes) {
isNominal[i] = attribute.isNominal();
i++;
}
double nominalMutationProb = 1d / attributes.size();
if (!getParameterAsBoolean(PARAMETER_DEFAULT_NOMINAL_MUTATION_RATE))
nominalMutationProb = getParameterAsDouble(PARAMETER_NOMINAL_MUTATION_RATE);
this.weighting = new WeightingMutation(getParameterAsDouble(PARAMETER_MUTATION_VARIANCE), useBoundedMutation, isNominal, nominalMutationProb, getRandom());
return weighting;
}
@Override
protected List<PopulationOperator> getPostProcessingPopulationOperators(ExampleSet eSet) throws UndefinedParameterError {
List<PopulationOperator> otherPostOps = new LinkedList<PopulationOperator>();
if (getParameterAsBoolean(PARAMETER_1_5_RULE)) {
otherPostOps.add(new VarianceAdaption(weighting, eSet.getAttributes().size()));
}
return otherPostOps;
}
@Override
public void doWork() throws OperatorException {
// first test if nominal attributes are present to make warning and check bound mutation for nominal values
// handling
boolean useBoundedMutation = getParameterAsBoolean(PARAMETER_BOUNDED_MUTATION);
if (!useBoundedMutation) {
ExampleSet exampleSet = getExampleSetInput().getData();
boolean containsNominalAttributes = false;
for (Attribute attribute : exampleSet.getAttributes()) {
if (attribute.isNominal()) {
containsNominalAttributes = true;
break;
}
}
if (containsNominalAttributes) {
useBoundedMutation = true;
logWarning("If ExampleSet contains nominal attributes, bounded mutation must be used: Switched to bounded mutation automatically.");
}
}
super.doWork();
}
@Override
public Population createInitialPopulation(ExampleSet exampleSet) throws OperatorException {
Population initPop = new Population();
int numberOfIndividuals = getParameterAsInt(PARAMETER_POPULATION_SIZE);
double[] initialWeights = null;
if (getParameterAsBoolean(PARAMETER_INITIALIZE_WITH_INPUT_WEIGHTS)) {
AttributeWeights inputWeights = null;
inputWeights = attributeWeightsInput.getData();
initialWeights = new double[exampleSet.getAttributes().size()];
int index = 0;
for (Attribute attribute : exampleSet.getAttributes()) {
double weight = inputWeights.getWeight(attribute.getName());
if (Double.isNaN(weight))
weight = getRandom().nextDouble();
initialWeights[index++] = weight;
}
initPop.add(new Individual(initialWeights));
}
// fill with variants of the initial weights
if (initialWeights != null) {
while (initPop.getNumberOfIndividuals() < numberOfIndividuals / 2) {
double[] weights = new double[exampleSet.getAttributes().size()];
for (int w = 0; w < weights.length; w++) {
weights[w] = Math.min(1.0d, Math.max(0.0d, initialWeights[w] + (getRandom().nextGaussian() * 0.1d)));
}
initPop.add(new Individual(weights));
}
}
// fill up with random individuals
while (initPop.getNumberOfIndividuals() < numberOfIndividuals) {
double[] weights = new double[exampleSet.getAttributes().size()];
for (int w = 0; w < weights.length; w++) {
weights[w] = getRandom().nextDouble();
}
initPop.add(new Individual(weights));
}
return initPop;
}
@Override
public List<ParameterType> getParameterTypes() {
List<ParameterType> types = super.getParameterTypes();
types.add(new ParameterTypeDouble(PARAMETER_MUTATION_VARIANCE, "The (initial) variance for each mutation.", 0.0d, Double.POSITIVE_INFINITY, 1.0d));
types.add(new ParameterTypeBoolean(PARAMETER_1_5_RULE, "If set to true, the 1/5 rule for variance adaption is used.", true));
types.add(new ParameterTypeBoolean(PARAMETER_BOUNDED_MUTATION, "If set to true, the weights are bounded between 0 and 1.", false));
ParameterType type = new ParameterTypeDouble(PARAMETER_P_CROSSOVER, "Probability for an individual to be selected for crossover.", 0.0d, 1.0d, 0.0d);
types.add(type);
types.add(new ParameterTypeCategory(PARAMETER_CROSSOVER_TYPE, "Type of the crossover.", SelectionCrossover.CROSSOVER_TYPES, SelectionCrossover.UNIFORM));
types.add(new ParameterTypeBoolean(PARAMETER_DEFAULT_NOMINAL_MUTATION_RATE, "Use the default mutation rate for nominal attributes.", true));
type = new ParameterTypeDouble(PARAMETER_NOMINAL_MUTATION_RATE, "The probability to switch nominal attributes between 0 and 1.", 0, 1);
type.registerDependencyCondition(new BooleanParameterCondition(this, PARAMETER_DEFAULT_NOMINAL_MUTATION_RATE, true, false));
types.add(type);
types.add(new ParameterTypeBoolean(PARAMETER_INITIALIZE_WITH_INPUT_WEIGHTS, "Indicates if this operator should look for attribute weights in the given input and use the input weights of all known attributes as starting point for the optimization.", false));
return types;
}
}