You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mahout.apache.org by ad...@apache.org on 2011/12/26 17:24:37 UTC
svn commit: r1224751 - in /mahout/trunk/core/src:
main/java/org/apache/mahout/classifier/df/tools/
test/java/org/apache/mahout/classifier/df/tools/
Author: adeneche
Date: Mon Dec 26 16:24:37 2011
New Revision: 1224751
URL: http://svn.apache.org/viewvc?rev=1224751&view=rev
Log:
MAHOUT-926 Tree/Forest Visualizer
Added:
mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/ForestVisualizer.java
mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/TreeVisualizer.java
mahout/trunk/core/src/test/java/org/apache/mahout/classifier/df/tools/
mahout/trunk/core/src/test/java/org/apache/mahout/classifier/df/tools/VisualizerTest.java
Added: mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/ForestVisualizer.java
URL: http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/ForestVisualizer.java?rev=1224751&view=auto
==============================================================================
--- mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/ForestVisualizer.java (added)
+++ mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/ForestVisualizer.java Mon Dec 26 16:24:37 2011
@@ -0,0 +1,149 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.classifier.df.tools;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.commons.cli2.CommandLine;
+import org.apache.commons.cli2.Group;
+import org.apache.commons.cli2.Option;
+import org.apache.commons.cli2.builder.ArgumentBuilder;
+import org.apache.commons.cli2.builder.DefaultOptionBuilder;
+import org.apache.commons.cli2.builder.GroupBuilder;
+import org.apache.commons.cli2.commandline.Parser;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.mahout.classifier.df.DecisionForest;
+import org.apache.mahout.classifier.df.data.Dataset;
+import org.apache.mahout.classifier.df.node.Node;
+import org.apache.mahout.common.CommandLineUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This tool is to visualize the Decision Forest
+ */
+public final class ForestVisualizer {
+
+ private static final Logger log = LoggerFactory.getLogger(ForestVisualizer.class);
+
+ private ForestVisualizer() {
+ }
+
+ public static String toString(DecisionForest forest, Dataset dataset, String[] attrNames)
+ throws Exception {
+ Method getTrees = forest.getClass().getDeclaredMethod("getTrees");
+ getTrees.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ List<Node> trees = (List<Node>) getTrees.invoke(forest);
+
+ int cnt = 1;
+ StringBuffer buff = new StringBuffer();
+ for (Node tree : trees) {
+ buff.append("Tree[" + cnt + "]:");
+ buff.append(TreeVisualizer.toString(tree, dataset, attrNames));
+ buff.append('\n');
+ cnt++;
+ }
+ return buff.toString();
+ }
+
+ /**
+ * Decision Forest to String
+ * @param forestPath
+ * path to the Decision Forest
+ * @param datasetPath
+ * dataset path
+ * @param attrNames
+ * attribute names
+ */
+ public static String toString(String forestPath, String datasetPath, String[] attrNames)
+ throws Exception {
+ Configuration conf = new Configuration();
+ DecisionForest forest = DecisionForest.load(conf, new Path(forestPath));
+ Dataset dataset = Dataset.load(conf, new Path(datasetPath));
+ return toString(forest, dataset, attrNames);
+ }
+
+ /**
+ * Print Decision Forest
+ * @param forestPath
+ * path to the Decision Forest
+ * @param datasetPath
+ * dataset path
+ * @param attrNames
+ * attribute names
+ */
+ public static void print(String forestPath, String datasetPath, String[] attrNames)
+ throws Exception {
+ System.out.println(toString(forestPath, datasetPath, attrNames));
+ }
+
+ public static void main(String[] args) {
+ DefaultOptionBuilder obuilder = new DefaultOptionBuilder();
+ ArgumentBuilder abuilder = new ArgumentBuilder();
+ GroupBuilder gbuilder = new GroupBuilder();
+
+ Option datasetOpt = obuilder.withLongName("dataset").withShortName("ds").withRequired(true)
+ .withArgument(abuilder.withName("dataset").withMinimum(1).withMaximum(1).create())
+ .withDescription("Dataset path").create();
+
+ Option modelOpt = obuilder.withLongName("model").withShortName("m").withRequired(true)
+ .withArgument(abuilder.withName("path").withMinimum(1).withMaximum(1).create())
+ .withDescription("Path to the Decision Forest").create();
+
+ Option attrNamesOpt = obuilder.withLongName("names").withShortName("n").withRequired(false)
+ .withArgument(abuilder.withName("names").withMinimum(1).create())
+ .withDescription("Optional, Attribute names").create();
+
+ Option helpOpt = obuilder.withLongName("help").withShortName("h")
+ .withDescription("Print out help").create();
+
+ Group group = gbuilder.withName("Options").withOption(datasetOpt).withOption(modelOpt)
+ .withOption(attrNamesOpt).withOption(helpOpt).create();
+
+ try {
+ Parser parser = new Parser();
+ parser.setGroup(group);
+ CommandLine cmdLine = parser.parse(args);
+
+ if (cmdLine.hasOption("help")) {
+ CommandLineUtil.printHelp(group);
+ return;
+ }
+
+ String datasetName = cmdLine.getValue(datasetOpt).toString();
+ String modelName = cmdLine.getValue(modelOpt).toString();
+ String[] attrNames = null;
+ if (cmdLine.hasOption(attrNamesOpt)) {
+ List<?> names = cmdLine.getValues(attrNamesOpt);
+ if (!names.isEmpty()) {
+ attrNames = new String[names.size()];
+ names.toArray(attrNames);
+ }
+ }
+
+ print(modelName, datasetName, attrNames);
+ } catch (Exception e) {
+ log.error("Exception", e);
+ CommandLineUtil.printHelp(group);
+ return;
+ }
+ }
+}
Added: mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/TreeVisualizer.java
URL: http://svn.apache.org/viewvc/mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/TreeVisualizer.java?rev=1224751&view=auto
==============================================================================
--- mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/TreeVisualizer.java (added)
+++ mahout/trunk/core/src/main/java/org/apache/mahout/classifier/df/tools/TreeVisualizer.java Mon Dec 26 16:24:37 2011
@@ -0,0 +1,241 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.classifier.df.tools;
+
+import java.lang.reflect.Field;
+import java.text.DecimalFormat;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.lang.ArrayUtils;
+import org.apache.mahout.classifier.df.data.Data;
+import org.apache.mahout.classifier.df.data.Dataset;
+import org.apache.mahout.classifier.df.data.Instance;
+import org.apache.mahout.classifier.df.node.CategoricalNode;
+import org.apache.mahout.classifier.df.node.Leaf;
+import org.apache.mahout.classifier.df.node.Node;
+import org.apache.mahout.classifier.df.node.NumericalNode;
+
+/**
+ * This tool is to visualize the Decision tree
+ */
+public final class TreeVisualizer {
+
+ private TreeVisualizer() {
+ }
+
+ private static String doubleToString(double value) {
+ DecimalFormat df = new DecimalFormat("0.##");
+ return df.format(value);
+ }
+
+ private static String toStringNode(Node node, Dataset dataset, String[] attrNames,
+ Map<String, Field> fields, int layer) throws IllegalAccessException {
+ StringBuffer buff = new StringBuffer();
+
+ if (node instanceof CategoricalNode) {
+ CategoricalNode cnode = (CategoricalNode) node;
+ int attr = (Integer) fields.get("CategoricalNode.attr").get(cnode);
+ double[] values = (double[]) fields.get("CategoricalNode.values").get(cnode);
+ Node[] childs = (Node[]) fields.get("CategoricalNode.childs").get(cnode);
+ String[][] attrValues = (String[][]) fields.get("Dataset.values").get(dataset);
+ for (int i = 0; i < childs.length; i++) {
+ buff.append("\n");
+ for (int j = 0; j < layer; j++) {
+ buff.append("| ");
+ }
+ buff.append((attrNames == null ? attr : attrNames[attr]) + " = " + attrValues[attr][i]);
+ int index = ArrayUtils.indexOf(values, i);
+ if (index >= 0) {
+ buff.append(toStringNode(childs[index], dataset, attrNames, fields, layer + 1));
+ }
+ }
+ } else if (node instanceof NumericalNode) {
+ NumericalNode nnode = (NumericalNode) node;
+ int attr = (Integer) fields.get("NumericalNode.attr").get(nnode);
+ double split = (Double) fields.get("NumericalNode.split").get(nnode);
+ Node loChild = (Node) fields.get("NumericalNode.loChild").get(nnode);
+ Node hiChild = (Node) fields.get("NumericalNode.hiChild").get(nnode);
+ buff.append("\n");
+ for (int j = 0; j < layer; j++) {
+ buff.append("| ");
+ }
+ buff.append((attrNames == null ? attr : attrNames[attr]) + " < " + doubleToString(split));
+ buff.append(toStringNode(loChild, dataset, attrNames, fields, layer + 1));
+ buff.append("\n");
+ for (int j = 0; j < layer; j++) {
+ buff.append("| ");
+ }
+ buff.append((attrNames == null ? attr : attrNames[attr]) + " >= " + doubleToString(split));
+ buff.append(toStringNode(hiChild, dataset, attrNames, fields, layer + 1));
+ } else if (node instanceof Leaf) {
+ Leaf leaf = (Leaf) node;
+ double label = (Double) fields.get("Leaf.label").get(leaf);
+ if (dataset.isNumerical(dataset.getLabelId())) {
+ buff.append(" : " + doubleToString(label));
+ } else {
+ buff.append(" : " + dataset.getLabelString((int) label));
+ }
+ }
+
+ return buff.toString();
+ }
+
+ private static Map<String, Field> getReflectMap() throws Exception {
+ Map<String, Field> fields = new HashMap<String, Field>();
+
+ Field m = CategoricalNode.class.getDeclaredField("attr");
+ m.setAccessible(true);
+ fields.put("CategoricalNode.attr", m);
+ m = CategoricalNode.class.getDeclaredField("values");
+ m.setAccessible(true);
+ fields.put("CategoricalNode.values", m);
+ m = CategoricalNode.class.getDeclaredField("childs");
+ m.setAccessible(true);
+ fields.put("CategoricalNode.childs", m);
+ m = NumericalNode.class.getDeclaredField("attr");
+ m.setAccessible(true);
+ fields.put("NumericalNode.attr", m);
+ m = NumericalNode.class.getDeclaredField("split");
+ m.setAccessible(true);
+ fields.put("NumericalNode.split", m);
+ m = NumericalNode.class.getDeclaredField("loChild");
+ m.setAccessible(true);
+ fields.put("NumericalNode.loChild", m);
+ m = NumericalNode.class.getDeclaredField("hiChild");
+ m.setAccessible(true);
+ fields.put("NumericalNode.hiChild", m);
+ m = Leaf.class.getDeclaredField("label");
+ m.setAccessible(true);
+ fields.put("Leaf.label", m);
+ m = Dataset.class.getDeclaredField("values");
+ m.setAccessible(true);
+ fields.put("Dataset.values", m);
+
+ return fields;
+ }
+
+ /**
+ * Decision tree to String
+ * @param tree
+ * Node of tree
+ * @param dataset
+ * @param attrNames
+ * attribute names
+ */
+ public static String toString(Node tree, Dataset dataset, String[] attrNames)
+ throws Exception {
+ return toStringNode(tree, dataset, attrNames, getReflectMap(), 0);
+ }
+
+ /**
+ * Print Decision tree
+ * @param tree
+ * Node of tree
+ * @param dataset
+ * @param attrNames
+ * attribute names
+ */
+ public static void print(Node tree, Dataset dataset, String[] attrNames) throws Exception {
+ System.out.println(toString(tree, dataset, attrNames));
+ }
+
+ private static String toStringPredict(Node node, Instance instance, Dataset dataset,
+ String[] attrNames, Map<String, Field> fields) throws IllegalAccessException {
+ StringBuffer buff = new StringBuffer();
+
+ if (node instanceof CategoricalNode) {
+ CategoricalNode cnode = (CategoricalNode) node;
+ int attr = (Integer) fields.get("CategoricalNode.attr").get(cnode);
+ double[] values = (double[]) fields.get("CategoricalNode.values").get(cnode);
+ Node[] childs = (Node[]) fields.get("CategoricalNode.childs").get(cnode);
+ String[][] attrValues = (String[][]) fields.get("Dataset.values").get(dataset);
+
+ int index = ArrayUtils.indexOf(values, instance.get(attr));
+ if (index >= 0) {
+ buff.append((attrNames == null ? attr : attrNames[attr]) + " = "
+ + attrValues[attr][(int) instance.get(attr)]);
+ buff.append(" -> ");
+ buff.append(toStringPredict(childs[index], instance, dataset, attrNames, fields));
+ }
+ } else if (node instanceof NumericalNode) {
+ NumericalNode nnode = (NumericalNode) node;
+ int attr = (Integer) fields.get("NumericalNode.attr").get(nnode);
+ double split = (Double) fields.get("NumericalNode.split").get(nnode);
+ Node loChild = (Node) fields.get("NumericalNode.loChild").get(nnode);
+ Node hiChild = (Node) fields.get("NumericalNode.hiChild").get(nnode);
+
+ if (instance.get(attr) < split) {
+ buff.append("(" + (attrNames == null ? attr : attrNames[attr]) + " = "
+ + doubleToString(instance.get(attr)) + ") < " + doubleToString(split));
+ buff.append(" -> ");
+ buff.append(toStringPredict(loChild, instance, dataset, attrNames, fields));
+ } else {
+ buff.append("(" + (attrNames == null ? attr : attrNames[attr]) + " = "
+ + doubleToString(instance.get(attr)) + ") >= " + doubleToString(split));
+ buff.append(" -> ");
+ buff.append(toStringPredict(hiChild, instance, dataset, attrNames, fields));
+ }
+ } else if (node instanceof Leaf) {
+ Leaf leaf = (Leaf) node;
+ double label = (Double) fields.get("Leaf.label").get(leaf);
+ if (dataset.isNumerical(dataset.getLabelId())) {
+ buff.append(doubleToString(label));
+ } else {
+ buff.append(dataset.getLabelString((int) label));
+ }
+ }
+
+ return buff.toString();
+ }
+
+ /**
+ * Predict trace to String
+ * @param tree
+ * Node of tree
+ * @param data
+ * @param attrNames
+ * attribute names
+ */
+ public static String[] predictTrace(Node tree, Data data, String[] attrNames)
+ throws Exception {
+ Map<String, Field> reflectMap = getReflectMap();
+ String[] prediction = new String[data.size()];
+ for (int i = 0; i < data.size(); i++) {
+ prediction[i] = toStringPredict(tree, data.get(i), data.getDataset(), attrNames, reflectMap);
+ }
+ return prediction;
+ }
+
+ /**
+ * Print predict trace
+ * @param tree
+ * Node of tree
+ * @param data
+ * @param attrNames
+ * attribute names
+ */
+ public static void predictTracePrint(Node tree, Data data, String[] attrNames)
+ throws Exception {
+ Map<String, Field> reflectMap = getReflectMap();
+ for (int i = 0; i < data.size(); i++) {
+ System.out.println(toStringPredict(tree, data.get(i), data.getDataset(), attrNames,
+ reflectMap));
+ }
+ }
+}
Added: mahout/trunk/core/src/test/java/org/apache/mahout/classifier/df/tools/VisualizerTest.java
URL: http://svn.apache.org/viewvc/mahout/trunk/core/src/test/java/org/apache/mahout/classifier/df/tools/VisualizerTest.java?rev=1224751&view=auto
==============================================================================
--- mahout/trunk/core/src/test/java/org/apache/mahout/classifier/df/tools/VisualizerTest.java (added)
+++ mahout/trunk/core/src/test/java/org/apache/mahout/classifier/df/tools/VisualizerTest.java Mon Dec 26 16:24:37 2011
@@ -0,0 +1,124 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.mahout.classifier.df.tools;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.mahout.classifier.df.DecisionForest;
+import org.apache.mahout.classifier.df.builder.DecisionTreeBuilder;
+import org.apache.mahout.classifier.df.data.Data;
+import org.apache.mahout.classifier.df.data.DataLoader;
+import org.apache.mahout.classifier.df.data.Dataset;
+import org.apache.mahout.classifier.df.node.CategoricalNode;
+import org.apache.mahout.classifier.df.node.Leaf;
+import org.apache.mahout.classifier.df.node.Node;
+import org.apache.mahout.classifier.df.node.NumericalNode;
+import org.apache.mahout.common.MahoutTestCase;
+import org.apache.mahout.common.RandomUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class VisualizerTest extends MahoutTestCase {
+
+ private static final String[] TRAIN_DATA = {"sunny,85,85,FALSE,no",
+ "sunny,80,90,TRUE,no", "overcast,83,86,FALSE,yes",
+ "rainy,70,96,FALSE,yes", "rainy,68,80,FALSE,yes", "rainy,65,70,TRUE,no",
+ "overcast,64,65,TRUE,yes", "sunny,72,95,FALSE,no",
+ "sunny,69,70,FALSE,yes", "rainy,75,80,FALSE,yes", "sunny,75,70,TRUE,yes",
+ "overcast,72,90,TRUE,yes", "overcast,81,75,FALSE,yes",
+ "rainy,71,91,TRUE,no"};
+
+ private static final String[] TEST_DATA = {"rainy,70,96,TRUE,-",
+ "overcast,64,65,TRUE,-", "sunny,75,90,TRUE,-",};
+
+ private static final String[] ATTR_NAMES = {"outlook", "temperature",
+ "humidity", "windy", "play"};
+
+ private Random rng;
+ private Data data;
+ private Data testData;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ rng = RandomUtils.getRandom();
+
+ // Dataset
+ Dataset dataset = DataLoader.generateDataset("C N N C L", false, TRAIN_DATA);
+
+ // Training data
+ data = DataLoader.loadData(dataset, TRAIN_DATA);
+
+ // Test data
+ testData = DataLoader.loadData(dataset, TEST_DATA);
+ }
+
+ @Test
+ public void testTreeVisualize() throws Exception {
+ // build tree
+ DecisionTreeBuilder builder = new DecisionTreeBuilder();
+ builder.setM(data.getDataset().nbAttributes() - 1);
+ Node tree = builder.build(rng, data);
+
+ assertEquals(TreeVisualizer.toString(tree, data.getDataset(), ATTR_NAMES),
+ "\noutlook = rainy\n| windy = FALSE : yes\n| windy = TRUE : no\n"
+ + "outlook = sunny\n| humidity < 85 : yes\n| humidity >= 85 : no\n"
+ + "outlook = overcast : yes");
+ }
+
+ @Test
+ public void testPredictTrace() throws Exception {
+ // build tree
+ DecisionTreeBuilder builder = new DecisionTreeBuilder();
+ builder.setM(data.getDataset().nbAttributes() - 1);
+ Node tree = builder.build(rng, data);
+
+ String[] prediction = TreeVisualizer.predictTrace(tree, testData, ATTR_NAMES);
+ Assert.assertArrayEquals(prediction, new String[] {
+ "outlook = rainy -> windy = TRUE -> no", "outlook = overcast -> yes",
+ "outlook = sunny -> (humidity = 90) >= 85 -> no"});
+ }
+
+ @Test
+ public void testForestVisualize() throws Exception {
+ // Tree
+ NumericalNode root = new NumericalNode(2, 90, new Leaf(0),
+ new CategoricalNode(0, new double[] {0, 1, 2}, new Node[] {
+ new NumericalNode(1, 71, new Leaf(0), new Leaf(1)), new Leaf(1),
+ new Leaf(0)}));
+ List<Node> trees = new ArrayList<Node>();
+ trees.add(root);
+
+ // Forest
+ DecisionForest forest = new DecisionForest(trees);
+ assertEquals(ForestVisualizer.toString(forest, data.getDataset(), null),
+ "Tree[1]:\n2 < 90 : yes\n2 >= 90\n"
+ + "| 0 = rainy\n| | 1 < 71 : yes\n| | 1 >= 71 : no\n"
+ + "| 0 = sunny : no\n" + "| 0 = overcast : yes\n");
+
+ assertEquals(ForestVisualizer.toString(forest, data.getDataset(), ATTR_NAMES),
+ "Tree[1]:\nhumidity < 90 : yes\nhumidity >= 90\n"
+ + "| outlook = rainy\n| | temperature < 71 : yes\n| | temperature >= 71 : no\n"
+ + "| outlook = sunny : no\n" + "| outlook = overcast : yes\n");
+ }
+}