/* * 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.learner.tree; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import com.rapidminer.example.ExampleSet; import com.rapidminer.operator.GroupedModel; import com.rapidminer.operator.Model; import com.rapidminer.operator.OperatorCapability; import com.rapidminer.operator.OperatorCreationException; import com.rapidminer.operator.OperatorDescription; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.UserError; import com.rapidminer.operator.learner.meta.AbstractMetaLearner; import com.rapidminer.operator.preprocessing.PreprocessingOperator; import com.rapidminer.operator.preprocessing.discretization.UserBasedDiscretization; import com.rapidminer.operator.preprocessing.filter.attributes.RegexpAttributeFilter; import com.rapidminer.operator.tools.AttributeSubsetSelector; import com.rapidminer.parameter.ParameterTypeList; import com.rapidminer.tools.OperatorService; import com.rapidminer.tools.Tools; import com.rapidminer.tools.container.Pair; /** * This operator is a meta learner for numerical tree builder. It might be used to flatten decision trees, which * consists of many splits on the same attribute. All numerical attributes used for at least one decision in a tree will * be discretized with the decisions' split points as borders. For example, if attribute att1 is splitted on the points * 4.5 and 2.1 then it will be discretized in three values: -Infinity to 2.1, 2.1 to 4.5 and 4.5 to Infinity. After * this, a new tree is grown on the transformed data. Since the used attributes are now numerical, all splits will be * made immediately and hence the depth might be reduced. Please note: The resulting tree might be easier to comprehend, * but this have to make it perform neither better nor worse! To get an impression of the reliability of the result * perform a XValidation. * * @author Sebastian Land */ public class MultiwayDecisionTree extends AbstractMetaLearner { /** * @param description */ public MultiwayDecisionTree(OperatorDescription description) { super(description); } @Override public Model learn(ExampleSet exampleSet) throws OperatorException { GroupedModel groupedModel = new GroupedModel(); // applying inner learner in order to get tree model TreeModel model = (TreeModel) applyInnerLearner(exampleSet); // searching numerical split point inside this tree model Map<String, List<Double>> attributePointMap = new HashMap<String, List<Double>>(); addNodeSplitPoints(model.getRoot(), attributePointMap); try { // operator construction PreprocessingOperator userbasedDiscretization = OperatorService.createOperator(UserBasedDiscretization.class); // setting common parameters userbasedDiscretization.setParameter(AttributeSubsetSelector.PARAMETER_FILTER_TYPE, AttributeSubsetSelector.CONDITION_REGULAR_EXPRESSION + ""); userbasedDiscretization.setParameter(UserBasedDiscretization.PARAMETER_RETURN_PREPROCESSING_MODEL, "true"); // iterating over all attributes which have to be transformed for (Entry<String, List<Double>> currentEntry : attributePointMap.entrySet()) { // sorting split points double[] splitPoints = new double[currentEntry.getValue().size()]; int i = 0; String currentAttributeName = currentEntry.getKey(); for (Double splitPoint : attributePointMap.get(currentAttributeName)) { splitPoints[i] = splitPoint; i++; } Arrays.sort(splitPoints); // setting attribute to consider userbasedDiscretization.setParameter(RegexpAttributeFilter.PARAMETER_REGULAR_EXPRESSION, currentAttributeName); // setting borders for splitting List<String[]> borders = new LinkedList<String[]>(); double lowerBorder = Double.NEGATIVE_INFINITY; for (i = 0; i < splitPoints.length; i++) { String[] pointSpecifier = new String[] { currentEntry + " in " + Tools.formatNumber(lowerBorder, 2) + " to " + Tools.formatNumber(splitPoints[i], 2), splitPoints[i] + "" }; lowerBorder = splitPoints[i]; borders.add(pointSpecifier); } String[] pointSpecifier = new String[] { currentEntry + " in " + Tools.formatNumber(lowerBorder, 2) + " to " + Tools.formatNumber(Double.POSITIVE_INFINITY, 2), "Infinity" }; borders.add(pointSpecifier); userbasedDiscretization.setParameter(UserBasedDiscretization.PARAMETER_RANGE_NAMES, ParameterTypeList.transformList2String(borders)); // executing operators Pair<ExampleSet, Model> result = userbasedDiscretization.doWorkModel(exampleSet); exampleSet = result.getFirst(); groupedModel.addModel(result.getSecond()); } // now apply inner operator on transformed exampleset groupedModel.addModel(applyInnerLearner(exampleSet)); return groupedModel; } catch (OperatorCreationException e) { throw new UserError(this, 904, "operators", e.getMessage()); } } /** * This method recursively adds splitting points from all numerical split conditions. */ private void addNodeSplitPoints(Tree root, Map<String, List<Double>> attributePointMap) { // add split point of this node if (!root.isLeaf()) { SplitCondition condition = root.childIterator().next().getCondition(); if (condition instanceof GreaterSplitCondition) { addSplit(condition.getAttributeName(), ((GreaterSplitCondition) condition).getValue(), attributePointMap); } else if (condition instanceof LessEqualsSplitCondition) { addSplit(condition.getAttributeName(), ((LessEqualsSplitCondition) condition).getValue(), attributePointMap); } } // recursive descent Iterator<Edge> iterator = root.childIterator(); while (iterator.hasNext()) { addNodeSplitPoints(iterator.next().getChild(), attributePointMap); } } private void addSplit(String attributeName, double value, Map<String, List<Double>> attributePointMap) { List<Double> valueList = attributePointMap.get(attributeName); if (valueList == null) { valueList = new LinkedList<Double>(); attributePointMap.put(attributeName, valueList); } valueList.add(value); } @Override public boolean supportsCapability(OperatorCapability capability) { switch (capability) { case BINOMINAL_ATTRIBUTES: case POLYNOMINAL_ATTRIBUTES: case NUMERICAL_ATTRIBUTES: case POLYNOMINAL_LABEL: case BINOMINAL_LABEL: case WEIGHTED_EXAMPLES: case MISSING_VALUES: return true; default: return false; } } }