From 32889e20516529980216ac7158338720024ca74d Mon Sep 17 00:00:00 2001 From: David Vilaverde Date: Mon, 12 Dec 2022 20:42:14 -0500 Subject: [PATCH 1/2] initial commit of the RandomForestClassifier --- .idea/copyright/profiles_settings.xml | 7 + ...g_apache_commons_commons_compress_1_21.xml | 13 ++ .../Maven__org_slf4j_slf4j_api_1_7_36.xml | 13 ++ .../Maven__org_slf4j_slf4j_simple_1_7_25.xml | 13 ++ .idea/vcs.xml | 1 - README.md | 86 ++++++-- pom.xml | 17 ++ scikit-learn-2-java.iml | 27 +++ .../classifier/dt/DecisionTreeClassifier.java | 14 +- .../classifier/dt/PredictionFactory.java | 9 +- .../classifier/dt/TreeClassifier.java | 14 ++ .../ensemble/RandomForestClassifier.java | 190 ++++++++++++++++++ .../RandomForestClassifierTest.java | 94 +++++++++ src/test/resources/rf/iris.tgz | Bin 0 -> 4891 bytes src/test/resources/simplelogger.properties | 1 + 15 files changed, 465 insertions(+), 34 deletions(-) create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml create mode 100644 .idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml create mode 100644 .idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_25.xml create mode 100644 scikit-learn-2-java.iml create mode 100644 src/main/java/rocks/vilaverde/classifier/dt/TreeClassifier.java create mode 100644 src/main/java/rocks/vilaverde/classifier/ensemble/RandomForestClassifier.java create mode 100644 src/test/java/rocks/vilaverde/classifier/RandomForestClassifierTest.java create mode 100644 src/test/resources/rf/iris.tgz create mode 100644 src/test/resources/simplelogger.properties diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..8295f31 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml b/.idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml new file mode 100644 index 0000000..49cd123 --- /dev/null +++ b/.idea/libraries/Maven__org_apache_commons_commons_compress_1_21.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml b/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml new file mode 100644 index 0000000..2d759c1 --- /dev/null +++ b/.idea/libraries/Maven__org_slf4j_slf4j_api_1_7_36.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_25.xml b/.idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_25.xml new file mode 100644 index 0000000..8bc862b --- /dev/null +++ b/.idea/libraries/Maven__org_slf4j_slf4j_simple_1_7_25.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 8306744..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/README.md b/README.md index b202e0a..2273253 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,29 @@ This project aims to used text exported ML models generated by sci-kit learn and make them usable in Java. -For example, a DecisionTreeClassifier model trained on the Iris dataset and exported using `sklearn.tree` -export_text() as shown below: +## Support +* The tree.DecisionTreeClassifier is supported + * Supports `predict()`, + * Supports `predict_proba()` when `export_text()` configured with `show_weights=True` +* The tree.RandomForestClassifier is supported + * Supports `predict()`, + * Supports `predict_proba()` when `export_text()` configured with `show_weights=True` + +## Installing + +### Importing Maven Dependency +```xml + + rocks.vilaverde + scikit-learn-2-java + 1.0.0 + +``` + +## DecisionTreeClassifier + +As an example, a DecisionTreeClassifier model trained on the Iris dataset and exported using `sklearn.tree` +`export_text()` as shown below: ``` >>> from sklearn.datasets import load_iris @@ -14,7 +35,7 @@ export_text() as shown below: >>> y = iris['target'] >>> decision_tree = DecisionTreeClassifier(random_state=0, max_depth=2) >>> decision_tree = decision_tree.fit(X, y) ->>> r = export_text(decision_tree, feature_names=iris['feature_names']) +>>> r = export_text(decision_tree, feature_names=iris['feature_names'], show_weights=True, max_depth=sys.maxsize) >>> print(r) |--- petal width (cm) <= 0.80 @@ -26,16 +47,8 @@ export_text() as shown below: | | |--- class: 2 ``` -can be executed in Java Maven. - -### Importing Maven Dependency -```xml - - rocks.vilaverde - scikit-learn-2-java - 1.0.0 - -``` +The exported text can then be executed in Java. Note that when calling `export_text` it is +recommended that `max_depth` be set to sys.maxsize so that the tree isn't truncated. ### Java Example In this example the iris model exported using `export_tree` is parsed, features are created as a Java Map @@ -44,7 +57,7 @@ and the decision tree is asked to predict the class. ``` Reader tree = getTrainedModel("iris.model"); final Classifier decisionTree = DecisionTreeClassifier.parse(tree, - new PredictionFactory.IntegerPredictionFactory()); + PredictionFactory.INTEGER); Map features = new HashMap<>(); features.put("sepal length (cm)", 3.0); @@ -56,10 +69,47 @@ and the decision tree is asked to predict the class. System.out.println(prediction.toString()); ``` -## Support -* The tree.DecisionTreeClassifier is supported - * Supports `predict()`, - * Supports `predict_proba()` when `export_text()` configured with `show_weights=True` +## RandomForestClassifier + +To use a RandomForestClassifier that has been trained on the Iris dataset, each of the `estimators` +in the classifiers need to be and exported using `from sklearn.tree export export_text` as shown below: + +``` +>>> from sklearn import datasets +>>> from sklearn import tree +>>> from sklearn.ensemble import RandomForestClassifier +>>> +>>> import os +>>> +>>> iris = datasets.load_iris() +>>> X = iris.data +>>> y = iris.target +>>> +>>> clf = RandomForestClassifier(n_estimators = 50, n_jobs=8) +>>> model = clf.fit(X, y) +>>> +>>> for i, t in enumerate(clf.estimators_): +>>> with open(os.path.join('/tmp/estimators', "iris-" + str(i) + ".txt"), "w") as file1: +>>> text_representation = tree.export_text(t, feature_names=iris.feature_names, show_weights=True, decimals=4, max_depth=sys.maxsize) +>>> file1.write(text_representation) +``` + +Once all the estimators are exported into `/tmp/estimators`, you can create a TAR archive, for example: +```bash +cd /tmp/estimators +tar -czvf /tmp/iris.tgz . +``` + +Then you can use the RandomForestClassifier class to parse the TAR archive. + +``` + import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; + ... + + TarArchiveInputStream tree = getArchive("iris.tgz"); + final Classifier decisionTree = RandomForestClassifier.parse(tree, + PredictionFactory.DOUBLE); +``` ## Testing Testing was done using sci-kit learn 1.1.3. diff --git a/pom.xml b/pom.xml index 818978d..32c0331 100644 --- a/pom.xml +++ b/pom.xml @@ -54,11 +54,28 @@ + + org.apache.commons + commons-compress + 1.21 + + + org.slf4j + slf4j-api + 1.7.36 + + org.junit.jupiter junit-jupiter test + + org.slf4j + slf4j-simple + 1.7.25 + test + diff --git a/scikit-learn-2-java.iml b/scikit-learn-2-java.iml new file mode 100644 index 0000000..3329a67 --- /dev/null +++ b/scikit-learn-2-java.iml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/rocks/vilaverde/classifier/dt/DecisionTreeClassifier.java b/src/main/java/rocks/vilaverde/classifier/dt/DecisionTreeClassifier.java index faf8015..f439f01 100644 --- a/src/main/java/rocks/vilaverde/classifier/dt/DecisionTreeClassifier.java +++ b/src/main/java/rocks/vilaverde/classifier/dt/DecisionTreeClassifier.java @@ -1,15 +1,15 @@ package rocks.vilaverde.classifier.dt; -import rocks.vilaverde.classifier.Prediction; -import rocks.vilaverde.classifier.Classifier; import rocks.vilaverde.classifier.Operator; +import rocks.vilaverde.classifier.Prediction; + import java.io.BufferedReader; import java.io.Reader; import java.util.Map; import java.util.Set; import java.util.Stack; -public class DecisionTreeClassifier implements Classifier { +public class DecisionTreeClassifier implements TreeClassifier { /** * Factory method to create the classifier from the {@link Reader}. @@ -19,7 +19,7 @@ public class DecisionTreeClassifier implements Classifier { * @param class * @throws Exception when the model could no be parsed */ - public static Classifier parse(Reader reader, PredictionFactory factory) throws Exception { + public static DecisionTreeClassifier parse(Reader reader, PredictionFactory factory) throws Exception { try (reader) { DecisionTreeClassifier classifier = new DecisionTreeClassifier<>(factory); @@ -53,7 +53,7 @@ private DecisionTreeClassifier(PredictionFactory predictionFactory) { * @return predicted class */ public T predict(Map features) { - return findClassification(features).get(); + return getClassification(features).get(); } /** @@ -64,13 +64,13 @@ public T predict(Map features) { */ @Override public double[] predict_proba(Map features) { - return findClassification(features).getProbability(); + return getClassification(features).getProbability(); } /** * Find the {@link Prediction} in the decision tree. */ - private Prediction findClassification(Map features) { + public Prediction getClassification(Map features) { validateFeature(features); TreeNode currentNode = root; diff --git a/src/main/java/rocks/vilaverde/classifier/dt/PredictionFactory.java b/src/main/java/rocks/vilaverde/classifier/dt/PredictionFactory.java index 0588293..8af8190 100644 --- a/src/main/java/rocks/vilaverde/classifier/dt/PredictionFactory.java +++ b/src/main/java/rocks/vilaverde/classifier/dt/PredictionFactory.java @@ -1,11 +1,3 @@ -///////////////////////////////////////////////////////////////////////////// -// PROPRIETARY RIGHTS STATEMENT -// The contents of this file represent confidential information that is the -// proprietary property of Edge2Web, Inc. Viewing or use of -// this information is prohibited without the express written consent of -// Edge2Web, Inc. Removal of this PROPRIETARY RIGHTS STATEMENT -// is strictly forbidden. Copyright (c) 2016 All rights reserved. -///////////////////////////////////////////////////////////////////////////// package rocks.vilaverde.classifier.dt; /** @@ -16,6 +8,7 @@ public interface PredictionFactory { PredictionFactory BOOLEAN = value -> Boolean.valueOf(value.toLowerCase()); PredictionFactory INTEGER = Integer::valueOf; + PredictionFactory DOUBLE = Double::parseDouble; T create(String value); } diff --git a/src/main/java/rocks/vilaverde/classifier/dt/TreeClassifier.java b/src/main/java/rocks/vilaverde/classifier/dt/TreeClassifier.java new file mode 100644 index 0000000..a0d59e9 --- /dev/null +++ b/src/main/java/rocks/vilaverde/classifier/dt/TreeClassifier.java @@ -0,0 +1,14 @@ +package rocks.vilaverde.classifier.dt; + +import rocks.vilaverde.classifier.Classifier; +import rocks.vilaverde.classifier.Prediction; + +import java.util.Map; + +/** + * Implemented by Tree classifiers. + */ +public interface TreeClassifier extends Classifier { + + Prediction getClassification(Map features); +} diff --git a/src/main/java/rocks/vilaverde/classifier/ensemble/RandomForestClassifier.java b/src/main/java/rocks/vilaverde/classifier/ensemble/RandomForestClassifier.java new file mode 100644 index 0000000..eda0bda --- /dev/null +++ b/src/main/java/rocks/vilaverde/classifier/ensemble/RandomForestClassifier.java @@ -0,0 +1,190 @@ +package rocks.vilaverde.classifier.ensemble; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import rocks.vilaverde.classifier.Classifier; +import rocks.vilaverde.classifier.Prediction; +import rocks.vilaverde.classifier.dt.DecisionTreeClassifier; +import rocks.vilaverde.classifier.dt.PredictionFactory; +import rocks.vilaverde.classifier.dt.TreeClassifier; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.*; +import java.util.concurrent.*; +import java.util.stream.Collectors; + +/** + * A forest of DecisionTreeClassifiers. + */ +public class RandomForestClassifier implements Classifier { + private static final Logger LOG = LoggerFactory.getLogger(RandomForestClassifier.class); + + /** + * Accept a TAR of exported DecisionTreeClassifiers from sklearn and product a + * RandomForestClassifier. This default to running in a single (current) thread. + * @param tar the Tar Archive input stream + * @param factory the factory for creating the prediction class + * @return the {@link Classifier} + * @param the classifer type + * @throws Exception when the model could no be parsed + */ + public static Classifier parse(final TarArchiveInputStream tar, + PredictionFactory factory) throws Exception { + return RandomForestClassifier.parse(tar, factory, 0); + } + + /** + * Accept a TAR of exported DecisionTreeClassifiers from sklearn and product a + * RandomForestClassifier. This can be run in Parallel by + * @param tar the Tar Archive input stream + * @param factory the factory for creating the prediction class + * @param jobs the number of threads to use to search the forest. Using -1 will use all + * available threads. + * @return the {@link Classifier} + * @param the classifer type + * @throws Exception when the model could no be parsed + */ + public static Classifier parse(final TarArchiveInputStream tar, + PredictionFactory factory, + int jobs) throws Exception { + List> forest = new ArrayList<>(); + + try (tar) { + TarArchiveEntry exportedTree = null; + while ((exportedTree = tar.getNextTarEntry()) != null) { + if (!exportedTree.isDirectory()) { + LOG.debug("Parsing tree {}", exportedTree.getName()); + final InputStream noCloseStream = new InputStream() { + @Override + public int read() throws IOException { + return tar.read(); + } + + @Override + public void close() throws IOException { + // don't close otherwise next file in tar won't be read. + } + }; + BufferedReader reader = new BufferedReader(new InputStreamReader(noCloseStream)); + TreeClassifier tree = (TreeClassifier) DecisionTreeClassifier.parse(reader, factory); + forest.add(tree); + } + } + } + + return new RandomForestClassifier<>(forest, jobs); + } + + private int jobs; + private List> forest; + + /** + * Private Constructor + * @param forest + * @param jobs + */ + private RandomForestClassifier(List> forest, int jobs) { + this.forest = forest; + this.jobs = jobs; + } + + @Override + public T predict(Map features) { + + List> predictions = getPredictions(features); + + Map map = predictions.stream().collect( + Collectors.groupingBy(Prediction::get, Collectors.counting()) + ); + + long max = map.values().stream().mapToLong(Long::longValue).max().getAsLong(); + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == max) { + return entry.getKey(); + } + } + + throw new IllegalStateException("no classification"); + } + + @Override + public double[] predict_proba(Map features) { + if (forest.size() == 1) { + return forest.get(0).getClassification(features).getProbability(); + } + + List> predictions = getPredictions(features); + + double[] result = null; + + for (Prediction prediction : predictions) { + double[] prob = prediction.getProbability(); + + if (result == null) { + result = prob; + } else { + for (int i = 0; i < prob.length; i++) { + result[i] += prob[i]; + } + } + } + + int forestSize = forest.size(); + for (int i = 0; i < result.length; i++) { + result[i] /= forestSize; + } + + return result; + } + + protected List> getPredictions(Map features) { + final List> predictions = new ArrayList<>(forest.size()); + + if (jobs == -1) { + jobs = Runtime.getRuntime().availableProcessors(); + } + + if (jobs > 0) { + ExecutorService executor = Executors.newFixedThreadPool(jobs); + + try { + for (TreeClassifier tree : forest) { + executor.submit(() -> { + Prediction pred = tree.getClassification(features); + synchronized (predictions) { + predictions.add(pred); + } + }); + } + } finally { + executor.shutdown(); + try { + executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); + } catch (Exception e) { + LOG.error("interrupted while searching trees", e); + } + } + } else { + for (TreeClassifier tree : forest) { + Prediction prediction = tree.getClassification(features); + predictions.add(prediction); + } + } + + return predictions; + } + + @Override + public Set getFeatureNames() { + Set features = new HashSet<>(); + for (Classifier tree : forest) { + features.addAll(tree.getFeatureNames()); + } + return features; + } +} diff --git a/src/test/java/rocks/vilaverde/classifier/RandomForestClassifierTest.java b/src/test/java/rocks/vilaverde/classifier/RandomForestClassifierTest.java new file mode 100644 index 0000000..ae971c9 --- /dev/null +++ b/src/test/java/rocks/vilaverde/classifier/RandomForestClassifierTest.java @@ -0,0 +1,94 @@ +package rocks.vilaverde.classifier; + +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import rocks.vilaverde.classifier.dt.PredictionFactory; +import rocks.vilaverde.classifier.ensemble.RandomForestClassifier; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +/** + * Tests for the RandomForestClassifier + */ +public class RandomForestClassifierTest { + + @Test + public void randomForestParallel() throws Exception { + TarArchiveInputStream exported = getExportedModel("rf/iris.tgz"); + final Classifier decisionTree = RandomForestClassifier.parse(exported, + PredictionFactory.DOUBLE, 4); + Assertions.assertNotNull(decisionTree); + + double[] proba = decisionTree.predict_proba(getSample1()); + assertSample(proba, .06, .62, .32); + + Double prediction = decisionTree.predict(getSample1()); + Assertions.assertNotNull(prediction); + Assertions.assertEquals(1.0, prediction.doubleValue(), .0); + + prediction = decisionTree.predict(getSample2()); + Assertions.assertEquals(2, prediction.intValue()); + + proba = decisionTree.predict_proba(getSample2()); + assertSample(proba, 0.0, .44, .56); + } + + @Test + public void randomForest() throws Exception { + TarArchiveInputStream exported = getExportedModel("rf/iris.tgz"); + final Classifier decisionTree = RandomForestClassifier.parse(exported, PredictionFactory.DOUBLE); + Assertions.assertNotNull(decisionTree); + + double[] proba = decisionTree.predict_proba(getSample1()); + assertSample(proba, .06, .62, .32); + + Double prediction = decisionTree.predict(getSample1()); + Assertions.assertNotNull(prediction); + Assertions.assertEquals(1.0, prediction.doubleValue(), .0); + + prediction = decisionTree.predict(getSample2()); + Assertions.assertEquals(2, prediction.intValue()); + + proba = decisionTree.predict_proba(getSample2()); + assertSample(proba, 0.0, .44, .56); + } + + private Map getSample1() { + Map features = new HashMap<>(); + features.put("sepal length (cm)", 3.0); + features.put("sepal width (cm)", 5.0); + features.put("petal length (cm)", 4.0); + features.put("petal width (cm)", 2.0); + return features; + } + + private Map getSample2() { + Map features = new HashMap<>(); + features.put("sepal length (cm)", 1.0); + features.put("sepal width (cm)", 2.0); + features.put("petal length (cm)", 3.0); + features.put("petal width (cm)", 4.0); + return features; + } + + private void assertSample(double[] proba, double expected, double expected1, double expected2) { + Assertions.assertNotNull(proba); + Assertions.assertEquals(expected, proba[0], .0); + Assertions.assertEquals(expected1, proba[1], .0); + Assertions.assertEquals(expected2, proba[2], .0); + } + + private TarArchiveInputStream getExportedModel(String fileName) throws IOException { + ClassLoader cl = DecisionTreeClassifierTest.class.getClassLoader(); + InputStream stream = cl.getResourceAsStream(fileName); + if (stream == null) { + throw new RuntimeException(String.format("no zip found with name %s", fileName)); + } + return new TarArchiveInputStream(new GzipCompressorInputStream(stream)); + } +} diff --git a/src/test/resources/rf/iris.tgz b/src/test/resources/rf/iris.tgz new file mode 100644 index 0000000000000000000000000000000000000000..158dc96274f64a9383ff3c380c1bab322d5f432a GIT binary patch literal 4891 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 Date: Mon, 12 Dec 2022 20:50:01 -0500 Subject: [PATCH 2/2] increment version to next snapshot --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 32c0331..02d6863 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ rocks.vilaverde scikit-learn-2-java - 1.0.0-SNAPSHOT + 1.0.1-SNAPSHOT ${project.groupId}:${project.artifactId} A sklearn exported_text models parser for executing in the Java runtime.