You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by is...@apache.org on 2017/11/10 15:07:10 UTC
[1/4] ignite git commit: IGNITE-5218: First version of decision
trees. This closes #2936
Repository: ignite
Updated Branches:
refs/heads/master 6579e69f2 -> db7697b17
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java b/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java
new file mode 100644
index 0000000..03e3198
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/util/MnistUtils.java
@@ -0,0 +1,121 @@
+/*
+ * 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.ignite.ml.util;
+
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Stream;
+import org.apache.ignite.ml.math.impls.vector.DenseLocalOnHeapVector;
+
+/**
+ * Utility class for reading MNIST dataset.
+ */
+public class MnistUtils {
+ /**
+ * Read random {@code count} samples from MNIST dataset from two files (images and labels) into a stream of labeled vectors.
+ * @param imagesPath Path to the file with images.
+ * @param labelsPath Path to the file with labels.
+ * @param rnd Random numbers generatror.
+ * @param count Count of samples to read.
+ * @return Stream of MNIST samples.
+ * @throws IOException
+ */
+ public static Stream<DenseLocalOnHeapVector> mnist(String imagesPath, String labelsPath, Random rnd, int count) throws IOException {
+ FileInputStream isImages = new FileInputStream(imagesPath);
+ FileInputStream isLabels = new FileInputStream(labelsPath);
+
+ int magic = read4Bytes(isImages); // Skip magic number.
+ int numOfImages = read4Bytes(isImages);
+ int imgHeight = read4Bytes(isImages);
+ int imgWidth = read4Bytes(isImages);
+
+ read4Bytes(isLabels); // Skip magic number.
+ read4Bytes(isLabels); // Skip number of labels.
+
+ int numOfPixels = imgHeight * imgWidth;
+
+ System.out.println("Magic: " + magic);
+ System.out.println("Num of images: " + numOfImages);
+ System.out.println("Num of pixels: " + numOfPixels);
+
+ double[][] vecs = new double[numOfImages][numOfPixels + 1];
+
+ for (int imgNum = 0; imgNum < numOfImages; imgNum++) {
+ vecs[imgNum][numOfPixels] = isLabels.read();
+ for (int p = 0; p < numOfPixels; p++) {
+ int c = 128 - isImages.read();
+ vecs[imgNum][p] = (double)c / 128;
+ }
+ }
+
+ List<double[]> lst = Arrays.asList(vecs);
+ Collections.shuffle(lst, rnd);
+
+ isImages.close();
+ isLabels.close();
+
+ return lst.subList(0, count).stream().map(DenseLocalOnHeapVector::new);
+ }
+
+ /**
+ * Convert random {@code count} samples from MNIST dataset from two files (images and labels) into libsvm format.
+ * @param imagesPath Path to the file with images.
+ * @param labelsPath Path to the file with labels.
+ * @param outPath Path to output path.
+ * @param rnd Random numbers generator.
+ * @param count Count of samples to read.
+ * @throws IOException
+ */
+ public static void asLIBSVM(String imagesPath, String labelsPath, String outPath, Random rnd, int count) throws IOException {
+
+ try (FileWriter fos = new FileWriter(outPath)) {
+ mnist(imagesPath, labelsPath, rnd, count).forEach(vec -> {
+ try {
+ fos.write((int)vec.get(vec.size() - 1) + " ");
+
+ for (int i = 0; i < vec.size() - 1; i++) {
+ double val = vec.get(i);
+
+ if (val != 0)
+ fos.write((i + 1) + ":" + val + " ");
+ }
+
+ fos.write("\n");
+
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ });
+ }
+ }
+
+ /**
+ * Utility method for reading 4 bytes from input stream.
+ * @param is Input stream.
+ * @throws IOException
+ */
+ private static int read4Bytes(FileInputStream is) throws IOException {
+ return (is.read() << 24) | (is.read() << 16) | (is.read() << 8) | (is.read());
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/util/Utils.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/util/Utils.java b/modules/ml/src/main/java/org/apache/ignite/ml/util/Utils.java
new file mode 100644
index 0000000..b7669be
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/util/Utils.java
@@ -0,0 +1,53 @@
+/*
+ * 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.ignite.ml.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ * Class with various utility methods.
+ */
+public class Utils {
+ /**
+ * Perform deep copy of an object.
+ * @param orig Original object.
+ * @param <T> Class of original object;
+ * @return Deep copy of original object.
+ */
+ public static <T> T copy(T orig) {
+ Object obj = null;
+ try {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(baos);
+ out.writeObject(orig);
+ out.flush();
+ out.close();
+ ObjectInputStream in = new ObjectInputStream(
+ new ByteArrayInputStream(baos.toByteArray()));
+ obj = in.readObject();
+ }
+ catch (IOException | ClassNotFoundException e) {
+ e.printStackTrace();
+ }
+ return (T)obj;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java b/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
index 5ac7443..47910c8 100644
--- a/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/IgniteMLTestSuite.java
@@ -20,6 +20,7 @@ package org.apache.ignite.ml;
import org.apache.ignite.ml.clustering.ClusteringTestSuite;
import org.apache.ignite.ml.math.MathImplMainTestSuite;
import org.apache.ignite.ml.regressions.RegressionsTestSuite;
+import org.apache.ignite.ml.trees.DecisionTreesTestSuite;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@@ -30,7 +31,8 @@ import org.junit.runners.Suite;
@Suite.SuiteClasses({
MathImplMainTestSuite.class,
RegressionsTestSuite.class,
- ClusteringTestSuite.class
+ ClusteringTestSuite.class,
+ DecisionTreesTestSuite.class
})
public class IgniteMLTestSuite {
// No-op.
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/TestUtils.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/TestUtils.java b/modules/ml/src/test/java/org/apache/ignite/ml/TestUtils.java
index 62fdf2c..d094813 100644
--- a/modules/ml/src/test/java/org/apache/ignite/ml/TestUtils.java
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/TestUtils.java
@@ -23,6 +23,8 @@ import org.apache.ignite.ml.math.Precision;
import org.apache.ignite.ml.math.Vector;
import org.junit.Assert;
+import static org.junit.Assert.assertTrue;
+
/** */
public class TestUtils {
/**
@@ -245,4 +247,17 @@ public class TestUtils {
public static double maximumAbsoluteRowSum(Matrix mtx) {
return IntStream.range(0, mtx.rowSize()).mapToObj(mtx::viewRow).map(v -> Math.abs(v.sum())).reduce(Math::max).get();
}
+
+ /** */
+ public static void checkIsInEpsilonNeighbourhood(Vector[] v1s, Vector[] v2s, double epsilon) {
+ for (int i = 0; i < v1s.length; i++) {
+ assertTrue("Not in epsilon neighbourhood (index " + i + ") ",
+ v1s[i].minus(v2s[i]).kNorm(2) < epsilon);
+ }
+ }
+
+ /** */
+ public static void checkIsInEpsilonNeighbourhood(Vector v1, Vector v2, double epsilon) {
+ checkIsInEpsilonNeighbourhood(new Vector[] {v1}, new Vector[] {v2}, epsilon);
+ }
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedBlockMatrixTest.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedBlockMatrixTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedBlockMatrixTest.java
index 2943bc0..fd6ed78 100644
--- a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedBlockMatrixTest.java
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedBlockMatrixTest.java
@@ -24,6 +24,7 @@ import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.Set;
+import java.util.UUID;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.internal.util.IgniteUtils;
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/trees/BaseDecisionTreeTest.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trees/BaseDecisionTreeTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/trees/BaseDecisionTreeTest.java
new file mode 100644
index 0000000..65f0ae4
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trees/BaseDecisionTreeTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.util.Arrays;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.ml.math.impls.vector.DenseLocalOnHeapVector;
+import org.apache.ignite.ml.structures.LabeledVectorDouble;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+/**
+ * Base class for decision trees test.
+ */
+public class BaseDecisionTreeTest extends GridCommonAbstractTest {
+ /** Count of nodes. */
+ private static final int NODE_COUNT = 4;
+
+ /** Grid instance. */
+ protected Ignite ignite;
+
+ /**
+ * Default constructor.
+ */
+ public BaseDecisionTreeTest() {
+ super(false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override protected void beforeTest() throws Exception {
+ ignite = grid(NODE_COUNT);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeTestsStarted() throws Exception {
+ for (int i = 1; i <= NODE_COUNT; i++)
+ startGrid(i);
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void afterTestsStopped() throws Exception {
+ stopAllGrids();
+ }
+
+ /**
+ * Convert double array to {@link LabeledVectorDouble}
+ *
+ * @param arr Array for conversion.
+ * @return LabeledVectorDouble.
+ */
+ protected static LabeledVectorDouble<DenseLocalOnHeapVector> asLabeledVector(double arr[]) {
+ return new LabeledVectorDouble<>(new DenseLocalOnHeapVector(Arrays.copyOf(arr, arr.length - 1)), arr[arr.length - 1]);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/trees/ColumnDecisionTreeTrainerTest.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trees/ColumnDecisionTreeTrainerTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/trees/ColumnDecisionTreeTrainerTest.java
new file mode 100644
index 0000000..2b03b47
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trees/ColumnDecisionTreeTrainerTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.StorageConstants;
+import org.apache.ignite.ml.math.Tracer;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.impls.matrix.SparseDistributedMatrix;
+import org.apache.ignite.ml.math.impls.vector.DenseLocalOnHeapVector;
+import org.apache.ignite.ml.structures.LabeledVectorDouble;
+import org.apache.ignite.ml.trees.models.DecisionTreeModel;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainerInput;
+import org.apache.ignite.ml.trees.trainers.columnbased.MatrixColumnDecisionTreeTrainerInput;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.ContinuousSplitCalculators;
+import org.apache.ignite.ml.trees.trainers.columnbased.regcalcs.RegionCalculators;
+
+/** Tests behaviour of ColumnDecisionTreeTrainer. */
+public class ColumnDecisionTreeTrainerTest extends BaseDecisionTreeTest {
+ /**
+ * Test {@link ColumnDecisionTreeTrainerTest} for mixed (continuous and categorical) data with Gini impurity.
+ */
+ public void testCacheMixedGini() {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+ int totalPts = 1 << 10;
+ int featCnt = 2;
+
+ HashMap<Integer, Integer> catsInfo = new HashMap<>();
+ catsInfo.put(1, 3);
+
+ Random rnd = new Random(12349L);
+
+ SplitDataGenerator<DenseLocalOnHeapVector> gen = new SplitDataGenerator<>(
+ featCnt, catsInfo, () -> new DenseLocalOnHeapVector(featCnt + 1), rnd).
+ split(0, 1, new int[] {0, 2}).
+ split(1, 0, -10.0);
+
+ testByGen(totalPts, catsInfo, gen, ContinuousSplitCalculators.GINI.apply(ignite), RegionCalculators.GINI, RegionCalculators.MEAN, rnd);
+ }
+
+ /**
+ * Test {@link ColumnDecisionTreeTrainerTest} for mixed (continuous and categorical) data with Variance impurity.
+ */
+ public void testCacheMixed() {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+ int totalPts = 1 << 10;
+ int featCnt = 2;
+
+ HashMap<Integer, Integer> catsInfo = new HashMap<>();
+ catsInfo.put(1, 3);
+
+ Random rnd = new Random(12349L);
+
+ SplitDataGenerator<DenseLocalOnHeapVector> gen = new SplitDataGenerator<>(
+ featCnt, catsInfo, () -> new DenseLocalOnHeapVector(featCnt + 1), rnd).
+ split(0, 1, new int[] {0, 2}).
+ split(1, 0, -10.0);
+
+ testByGen(totalPts, catsInfo, gen, ContinuousSplitCalculators.VARIANCE, RegionCalculators.VARIANCE, RegionCalculators.MEAN, rnd);
+ }
+
+ /**
+ * Test {@link ColumnDecisionTreeTrainerTest} for continuous data with Variance impurity.
+ */
+ public void testCacheCont() {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+ int totalPts = 1 << 10;
+ int featCnt = 12;
+
+ HashMap<Integer, Integer> catsInfo = new HashMap<>();
+
+ Random rnd = new Random(12349L);
+
+ SplitDataGenerator<DenseLocalOnHeapVector> gen = new SplitDataGenerator<>(
+ featCnt, catsInfo, () -> new DenseLocalOnHeapVector(featCnt + 1), rnd).
+ split(0, 0, -10.0).
+ split(1, 0, 0.0).
+ split(1, 1, 2.0).
+ split(3, 7, 50.0);
+
+ testByGen(totalPts, catsInfo, gen, ContinuousSplitCalculators.VARIANCE, RegionCalculators.VARIANCE, RegionCalculators.MEAN, rnd);
+ }
+
+ /**
+ * Test {@link ColumnDecisionTreeTrainerTest} for continuous data with Gini impurity.
+ */
+ public void testCacheContGini() {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+ int totalPts = 1 << 10;
+ int featCnt = 12;
+
+ HashMap<Integer, Integer> catsInfo = new HashMap<>();
+
+ Random rnd = new Random(12349L);
+
+ SplitDataGenerator<DenseLocalOnHeapVector> gen = new SplitDataGenerator<>(
+ featCnt, catsInfo, () -> new DenseLocalOnHeapVector(featCnt + 1), rnd).
+ split(0, 0, -10.0).
+ split(1, 0, 0.0).
+ split(1, 1, 2.0).
+ split(3, 7, 50.0);
+
+ testByGen(totalPts, catsInfo, gen, ContinuousSplitCalculators.GINI.apply(ignite), RegionCalculators.GINI, RegionCalculators.MEAN, rnd);
+ }
+
+ /**
+ * Test {@link ColumnDecisionTreeTrainerTest} for categorical data with Variance impurity.
+ */
+ public void testCacheCat() {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+ int totalPts = 1 << 10;
+ int featCnt = 12;
+
+ HashMap<Integer, Integer> catsInfo = new HashMap<>();
+ catsInfo.put(5, 7);
+
+ Random rnd = new Random(12349L);
+
+ SplitDataGenerator<DenseLocalOnHeapVector> gen = new SplitDataGenerator<>(
+ featCnt, catsInfo, () -> new DenseLocalOnHeapVector(featCnt + 1), rnd).
+ split(0, 5, new int[] {0, 2, 5});
+
+ testByGen(totalPts, catsInfo, gen, ContinuousSplitCalculators.VARIANCE, RegionCalculators.VARIANCE, RegionCalculators.MEAN, rnd);
+ }
+
+ /** */
+ private <D extends ContinuousRegionInfo> void testByGen(int totalPts, HashMap<Integer, Integer> catsInfo,
+ SplitDataGenerator<DenseLocalOnHeapVector> gen,
+ IgniteFunction<ColumnDecisionTreeTrainerInput, ? extends ContinuousSplitCalculator<D>> calc,
+ IgniteFunction<ColumnDecisionTreeTrainerInput, IgniteFunction<DoubleStream, Double>> catImpCalc,
+ IgniteFunction<DoubleStream, Double> regCalc, Random rnd) {
+
+ List<IgniteBiTuple<Integer, DenseLocalOnHeapVector>> lst = gen.
+ points(totalPts, (i, rn) -> i).
+ collect(Collectors.toList());
+
+ int featCnt = gen.featuresCnt();
+
+ Collections.shuffle(lst, rnd);
+
+ SparseDistributedMatrix m = new SparseDistributedMatrix(totalPts, featCnt + 1, StorageConstants.COLUMN_STORAGE_MODE, StorageConstants.RANDOM_ACCESS_MODE);
+
+ Map<Integer, List<LabeledVectorDouble>> byRegion = new HashMap<>();
+
+ int i = 0;
+ for (IgniteBiTuple<Integer, DenseLocalOnHeapVector> bt : lst) {
+ byRegion.putIfAbsent(bt.get1(), new LinkedList<>());
+ byRegion.get(bt.get1()).add(asLabeledVector(bt.get2().getStorage().data()));
+ m.setRow(i, bt.get2().getStorage().data());
+ i++;
+ }
+
+ ColumnDecisionTreeTrainer<D> trainer =
+ new ColumnDecisionTreeTrainer<>(3, calc, catImpCalc, regCalc, ignite);
+
+ DecisionTreeModel mdl = trainer.train(new MatrixColumnDecisionTreeTrainerInput(m, catsInfo));
+
+ byRegion.keySet().forEach(k -> {
+ LabeledVectorDouble sp = byRegion.get(k).get(0);
+ Tracer.showAscii(sp.vector());
+ System.out.println("Act: " + sp.label() + " " + " pred: " + mdl.predict(sp.vector()));
+ assert mdl.predict(sp.vector()) == sp.doubleLabel();
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/trees/DecisionTreesTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trees/DecisionTreesTestSuite.java b/modules/ml/src/test/java/org/apache/ignite/ml/trees/DecisionTreesTestSuite.java
new file mode 100644
index 0000000..3343503
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trees/DecisionTreesTestSuite.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ignite.ml.trees;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Test suite for all tests located in org.apache.ignite.ml.trees package
+ */
+@RunWith(Suite.class)
+@Suite.SuiteClasses({
+ ColumnDecisionTreeTrainerTest.class,
+ GiniSplitCalculatorTest.class,
+ VarianceSplitCalculatorTest.class
+})
+public class DecisionTreesTestSuite {
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/trees/GiniSplitCalculatorTest.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trees/GiniSplitCalculatorTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/trees/GiniSplitCalculatorTest.java
new file mode 100644
index 0000000..c92b4f5
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trees/GiniSplitCalculatorTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.util.stream.DoubleStream;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.GiniSplitCalculator;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.SplitInfo;
+import org.junit.Test;
+
+/**
+ * Test of {@link GiniSplitCalculator}.
+ */
+public class GiniSplitCalculatorTest {
+ /** Test calculation of region info consisting from one point. */
+ @Test
+ public void testCalculateRegionInfoSimple() {
+ double labels[] = new double[] {0.0};
+
+ assert new GiniSplitCalculator(labels).calculateRegionInfo(DoubleStream.of(labels), 0).impurity() == 0.0;
+ }
+
+ /** Test calculation of region info consisting from two distinct classes. */
+ @Test
+ public void testCalculateRegionInfoTwoClasses() {
+ double labels[] = new double[] {0.0, 1.0};
+
+ assert new GiniSplitCalculator(labels).calculateRegionInfo(DoubleStream.of(labels), 0).impurity() == 0.5;
+ }
+
+ /** Test calculation of region info consisting from three distinct classes. */
+ @Test
+ public void testCalculateRegionInfoThreeClasses() {
+ double labels[] = new double[] {0.0, 1.0, 2.0};
+
+ assert Math.abs(new GiniSplitCalculator(labels).calculateRegionInfo(DoubleStream.of(labels), 0).impurity() - 2.0 / 3) < 1E-5;
+ }
+
+ /** Test calculation of split of region consisting from one point. */
+ @Test
+ public void testSplitSimple() {
+ double labels[] = new double[] {0.0};
+ double values[] = new double[] {0.0};
+ Integer[] samples = new Integer[] {0};
+
+ int cnts[] = new int[] {1};
+
+ GiniSplitCalculator.GiniData data = new GiniSplitCalculator.GiniData(0.0, 1, cnts, 1);
+
+ assert new GiniSplitCalculator(labels).splitRegion(samples, values, labels, 0, data) == null;
+ }
+
+ /** Test calculation of split of region consisting from two points. */
+ @Test
+ public void testSplitTwoClassesTwoPoints() {
+ double labels[] = new double[] {0.0, 1.0};
+ double values[] = new double[] {0.0, 1.0};
+ Integer[] samples = new Integer[] {0, 1};
+
+ int cnts[] = new int[] {1, 1};
+
+ GiniSplitCalculator.GiniData data = new GiniSplitCalculator.GiniData(0.5, 2, cnts, 1.0 * 1.0 + 1.0 * 1.0);
+
+ SplitInfo<GiniSplitCalculator.GiniData> split = new GiniSplitCalculator(labels).splitRegion(samples, values, labels, 0, data);
+
+ assert split.leftData().impurity() == 0;
+ assert split.leftData().counts()[0] == 1;
+ assert split.leftData().counts()[1] == 0;
+ assert split.leftData().getSize() == 1;
+
+ assert split.rightData().impurity() == 0;
+ assert split.rightData().counts()[0] == 0;
+ assert split.rightData().counts()[1] == 1;
+ assert split.rightData().getSize() == 1;
+ }
+
+ /** Test calculation of split of region consisting from four distinct values. */
+ @Test
+ public void testSplitTwoClassesFourPoints() {
+ double labels[] = new double[] {0.0, 0.0, 1.0, 1.0};
+ double values[] = new double[] {0.0, 1.0, 2.0, 3.0};
+
+ Integer[] samples = new Integer[] {0, 1, 2, 3};
+
+ int[] cnts = new int[] {2, 2};
+
+ GiniSplitCalculator.GiniData data = new GiniSplitCalculator.GiniData(0.5, 4, cnts, 2.0 * 2.0 + 2.0 * 2.0);
+
+ SplitInfo<GiniSplitCalculator.GiniData> split = new GiniSplitCalculator(labels).splitRegion(samples, values, labels, 0, data);
+
+ assert split.leftData().impurity() == 0;
+ assert split.leftData().counts()[0] == 2;
+ assert split.leftData().counts()[1] == 0;
+ assert split.leftData().getSize() == 2;
+
+ assert split.rightData().impurity() == 0;
+ assert split.rightData().counts()[0] == 0;
+ assert split.rightData().counts()[1] == 2;
+ assert split.rightData().getSize() == 2;
+ }
+
+ /** Test calculation of split of region consisting from three distinct values. */
+ @Test
+ public void testSplitThreePoints() {
+ double labels[] = new double[] {0.0, 1.0, 2.0};
+ double values[] = new double[] {0.0, 1.0, 2.0};
+ Integer[] samples = new Integer[] {0, 1, 2};
+
+ int[] cnts = new int[] {1, 1, 1};
+
+ GiniSplitCalculator.GiniData data = new GiniSplitCalculator.GiniData(2.0 / 3, 3, cnts, 1.0 * 1.0 + 1.0 * 1.0 + 1.0 * 1.0);
+
+ SplitInfo<GiniSplitCalculator.GiniData> split = new GiniSplitCalculator(labels).splitRegion(samples, values, labels, 0, data);
+
+ assert split.leftData().impurity() == 0.0;
+ assert split.leftData().counts()[0] == 1;
+ assert split.leftData().counts()[1] == 0;
+ assert split.leftData().counts()[2] == 0;
+ assert split.leftData().getSize() == 1;
+
+ assert split.rightData().impurity() == 0.5;
+ assert split.rightData().counts()[0] == 0;
+ assert split.rightData().counts()[1] == 1;
+ assert split.rightData().counts()[2] == 1;
+ assert split.rightData().getSize() == 2;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/trees/SplitDataGenerator.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trees/SplitDataGenerator.java b/modules/ml/src/test/java/org/apache/ignite/ml/trees/SplitDataGenerator.java
new file mode 100644
index 0000000..279e685
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trees/SplitDataGenerator.java
@@ -0,0 +1,390 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.Vector;
+import org.apache.ignite.ml.math.exceptions.MathIllegalArgumentException;
+import org.apache.ignite.ml.util.Utils;
+
+/**
+ * Utility class for generating data which has binary tree split structure.
+ *
+ * @param <V>
+ */
+public class SplitDataGenerator<V extends Vector> {
+ /** */
+ private static final double DELTA = 100.0;
+
+ /** Map of the form of (is categorical -> list of region indexes). */
+ private final Map<Boolean, List<Integer>> di;
+
+ /** List of regions. */
+ private final List<Region> regs;
+
+ /** Data of bounds of regions. */
+ private final Map<Integer, IgniteBiTuple<Double, Double>> boundsData;
+
+ /** Random numbers generator. */
+ private final Random rnd;
+
+ /** Supplier of vectors. */
+ private final Supplier<V> supplier;
+
+ /** Features count. */
+ private final int featCnt;
+
+ /**
+ * Create SplitDataGenerator.
+ *
+ * @param featCnt Features count.
+ * @param catFeaturesInfo Information about categorical features in form of map (feature index -> categories
+ * count).
+ * @param supplier Supplier of vectors.
+ * @param rnd Random numbers generator.
+ */
+ public SplitDataGenerator(int featCnt, Map<Integer, Integer> catFeaturesInfo, Supplier<V> supplier, Random rnd) {
+ regs = new LinkedList<>();
+ boundsData = new HashMap<>();
+ this.rnd = rnd;
+ this.supplier = supplier;
+ this.featCnt = featCnt;
+
+ // Divide indexes into indexes of categorical coordinates and indexes of continuous coordinates.
+ di = IntStream.range(0, featCnt).
+ boxed().
+ collect(Collectors.partitioningBy(catFeaturesInfo::containsKey));
+
+ // Categorical coordinates info.
+ Map<Integer, CatCoordInfo> catCoords = new HashMap<>();
+ di.get(true).forEach(i -> {
+ BitSet bs = new BitSet();
+ bs.set(0, catFeaturesInfo.get(i));
+ catCoords.put(i, new CatCoordInfo(bs));
+ });
+
+ // Continuous coordinates info.
+ Map<Integer, ContCoordInfo> contCoords = new HashMap<>();
+ di.get(false).forEach(i -> {
+ contCoords.put(i, new ContCoordInfo());
+ boundsData.put(i, new IgniteBiTuple<>(-1.0, 1.0));
+ });
+
+ Region firstReg = new Region(catCoords, contCoords, 0);
+ regs.add(firstReg);
+ }
+
+ /**
+ * Categorical coordinate info.
+ */
+ private static class CatCoordInfo implements Serializable {
+ /**
+ * Defines categories which are included in this region
+ */
+ private final BitSet bs;
+
+ /**
+ * Construct CatCoordInfo.
+ *
+ * @param bs Bitset.
+ */
+ CatCoordInfo(BitSet bs) {
+ this.bs = bs;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "CatCoordInfo [" +
+ "bs=" + bs +
+ ']';
+ }
+ }
+
+ /**
+ * Continuous coordinate info.
+ */
+ private static class ContCoordInfo implements Serializable {
+ /**
+ * Left (min) bound of region.
+ */
+ private double left;
+
+ /**
+ * Right (max) bound of region.
+ */
+ private double right;
+
+ /**
+ * Construct ContCoordInfo.
+ */
+ ContCoordInfo() {
+ left = Double.NEGATIVE_INFINITY;
+ right = Double.POSITIVE_INFINITY;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "ContCoordInfo [" +
+ "left=" + left +
+ ", right=" + right +
+ ']';
+ }
+ }
+
+ /**
+ * Class representing information about region.
+ */
+ private static class Region implements Serializable {
+ /**
+ * Information about categorical coordinates restrictions of this region in form of
+ * (coordinate index -> restriction)
+ */
+ private final Map<Integer, CatCoordInfo> catCoords;
+
+ /**
+ * Information about continuous coordinates restrictions of this region in form of
+ * (coordinate index -> restriction)
+ */
+ private final Map<Integer, ContCoordInfo> contCoords;
+
+ /**
+ * Region should contain {@code 1/2^twoPow * totalPoints} points.
+ */
+ private int twoPow;
+
+ /**
+ * Construct region by information about restrictions on coordinates (features) values.
+ *
+ * @param catCoords Restrictions on categorical coordinates.
+ * @param contCoords Restrictions on continuous coordinates
+ * @param twoPow Region should contain {@code 1/2^twoPow * totalPoints} points.
+ */
+ Region(Map<Integer, CatCoordInfo> catCoords, Map<Integer, ContCoordInfo> contCoords, int twoPow) {
+ this.catCoords = catCoords;
+ this.contCoords = contCoords;
+ this.twoPow = twoPow;
+ }
+
+ /** */
+ int divideBy() {
+ return 1 << twoPow;
+ }
+
+ /** */
+ void incTwoPow() {
+ twoPow++;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "Region [" +
+ "catCoords=" + catCoords +
+ ", contCoords=" + contCoords +
+ ", twoPow=" + twoPow +
+ ']';
+ }
+
+ /**
+ * Generate continuous coordinate for this region.
+ *
+ * @param coordIdx Coordinate index.
+ * @param boundsData Data with bounds
+ * @param rnd Random numbers generator.
+ * @return Categorical coordinate value.
+ */
+ double generateContCoord(int coordIdx, Map<Integer, IgniteBiTuple<Double, Double>> boundsData,
+ Random rnd) {
+ ContCoordInfo cci = contCoords.get(coordIdx);
+ double left = cci.left;
+ double right = cci.right;
+
+ if (left == Double.NEGATIVE_INFINITY)
+ left = boundsData.get(coordIdx).get1() - DELTA;
+
+ if (right == Double.POSITIVE_INFINITY)
+ right = boundsData.get(coordIdx).get2() + DELTA;
+
+ double size = right - left;
+
+ return left + rnd.nextDouble() * size;
+ }
+
+ /**
+ * Generate categorical coordinate value for this region.
+ *
+ * @param coordIdx Coordinate index.
+ * @param rnd Random numbers generator.
+ * @return Categorical coordinate value.
+ */
+ double generateCatCoord(int coordIdx, Random rnd) {
+ // Pick random bit.
+ BitSet bs = catCoords.get(coordIdx).bs;
+ int j = rnd.nextInt(bs.length());
+
+ int i = 0;
+ int bn = 0;
+ int bnp = 0;
+
+ while ((bn = bs.nextSetBit(bn)) != -1 && i <= j) {
+ i++;
+ bnp = bn;
+ bn++;
+ }
+
+ return bnp;
+ }
+
+ /**
+ * Generate points for this region.
+ *
+ * @param ptsCnt Count of points to generate.
+ * @param val Label for all points in this region.
+ * @param boundsData Data about bounds of continuous coordinates.
+ * @param catCont Data about which categories can be in this region in the form (coordinate index -> list of
+ * categories indexes).
+ * @param s Vectors supplier.
+ * @param rnd Random numbers generator.
+ * @param <V> Type of vectors.
+ * @return Stream of generated points for this region.
+ */
+ <V extends Vector> Stream<V> generatePoints(int ptsCnt, double val,
+ Map<Integer, IgniteBiTuple<Double, Double>> boundsData, Map<Boolean, List<Integer>> catCont,
+ Supplier<V> s,
+ Random rnd) {
+ return IntStream.range(0, ptsCnt / divideBy()).mapToObj(i -> {
+ V v = s.get();
+ int coordsCnt = v.size();
+ catCont.get(false).forEach(ci -> v.setX(ci, generateContCoord(ci, boundsData, rnd)));
+ catCont.get(true).forEach(ci -> v.setX(ci, generateCatCoord(ci, rnd)));
+
+ v.setX(coordsCnt - 1, val);
+ return v;
+ });
+ }
+ }
+
+ /**
+ * Split region by continuous coordinate.using given threshold.
+ *
+ * @param regIdx Region index.
+ * @param coordIdx Coordinate index.
+ * @param threshold Threshold.
+ * @return {@code this}.
+ */
+ public SplitDataGenerator<V> split(int regIdx, int coordIdx, double threshold) {
+ Region regToSplit = regs.get(regIdx);
+ ContCoordInfo cci = regToSplit.contCoords.get(coordIdx);
+
+ double left = cci.left;
+ double right = cci.right;
+
+ if (threshold < left || threshold > right)
+ throw new MathIllegalArgumentException("Threshold is out of region bounds.");
+
+ regToSplit.incTwoPow();
+
+ Region newReg = Utils.copy(regToSplit);
+ newReg.contCoords.get(coordIdx).left = threshold;
+
+ regs.add(regIdx + 1, newReg);
+ cci.right = threshold;
+
+ IgniteBiTuple<Double, Double> bounds = boundsData.get(coordIdx);
+ double min = bounds.get1();
+ double max = bounds.get2();
+ boundsData.put(coordIdx, new IgniteBiTuple<>(Math.min(threshold, min), Math.max(max, threshold)));
+
+ return this;
+ }
+
+ /**
+ * Split region by categorical coordinate.
+ *
+ * @param regIdx Region index.
+ * @param coordIdx Coordinate index.
+ * @param cats Categories allowed for the left sub region.
+ * @return {@code this}.
+ */
+ public SplitDataGenerator<V> split(int regIdx, int coordIdx, int[] cats) {
+ BitSet subset = new BitSet();
+ Arrays.stream(cats).forEach(subset::set);
+ Region regToSplit = regs.get(regIdx);
+ CatCoordInfo cci = regToSplit.catCoords.get(coordIdx);
+
+ BitSet ssc = (BitSet)subset.clone();
+ BitSet set = cci.bs;
+ ssc.and(set);
+ if (ssc.length() != subset.length())
+ throw new MathIllegalArgumentException("Splitter set is not a subset of a parent subset.");
+
+ ssc.xor(set);
+ set.and(subset);
+
+ regToSplit.incTwoPow();
+ Region newReg = Utils.copy(regToSplit);
+ newReg.catCoords.put(coordIdx, new CatCoordInfo(ssc));
+
+ regs.add(regIdx + 1, newReg);
+
+ return this;
+ }
+
+ /**
+ * Get stream of points generated by this generator.
+ *
+ * @param ptsCnt Points count.
+ */
+ public Stream<IgniteBiTuple<Integer, V>> points(int ptsCnt, BiFunction<Double, Random, Double> f) {
+ regs.forEach(System.out::println);
+
+ return IntStream.range(0, regs.size()).
+ boxed().
+ map(i -> regs.get(i).generatePoints(ptsCnt, f.apply((double)i, rnd), boundsData, di, supplier, rnd).map(v -> new IgniteBiTuple<>(i, v))).flatMap(Function.identity());
+ }
+
+ /**
+ * Count of regions.
+ *
+ * @return Count of regions.
+ */
+ public int regsCount() {
+ return regs.size();
+ }
+
+ /**
+ * Get features count.
+ *
+ * @return Features count.
+ */
+ public int featuresCnt() {
+ return featCnt;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/trees/VarianceSplitCalculatorTest.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trees/VarianceSplitCalculatorTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/trees/VarianceSplitCalculatorTest.java
new file mode 100644
index 0000000..d67cbc6
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trees/VarianceSplitCalculatorTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.util.stream.DoubleStream;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.VarianceSplitCalculator;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.SplitInfo;
+import org.junit.Test;
+
+/**
+ * Test for {@link VarianceSplitCalculator}.
+ */
+public class VarianceSplitCalculatorTest {
+ /** Test calculation of region info consisting from one point. */
+ @Test
+ public void testCalculateRegionInfoSimple() {
+ double labels[] = new double[] {0.0};
+
+ assert new VarianceSplitCalculator().calculateRegionInfo(DoubleStream.of(labels), 1).impurity() == 0.0;
+ }
+
+ /** Test calculation of region info consisting from two classes. */
+ @Test
+ public void testCalculateRegionInfoTwoClasses() {
+ double labels[] = new double[] {0.0, 1.0};
+
+ assert new VarianceSplitCalculator().calculateRegionInfo(DoubleStream.of(labels), 2).impurity() == 0.25;
+ }
+
+ /** Test calculation of region info consisting from three classes. */
+ @Test
+ public void testCalculateRegionInfoThreeClasses() {
+ double labels[] = new double[] {1.0, 2.0, 3.0};
+
+ assert Math.abs(new VarianceSplitCalculator().calculateRegionInfo(DoubleStream.of(labels), 3).impurity() - 2.0 / 3) < 1E-10;
+ }
+
+ /** Test calculation of split of region consisting from one point. */
+ @Test
+ public void testSplitSimple() {
+ double labels[] = new double[] {0.0};
+ double values[] = new double[] {0.0};
+ Integer[] samples = new Integer[] {0};
+
+ VarianceSplitCalculator.VarianceData data = new VarianceSplitCalculator.VarianceData(0.0, 1, 0.0);
+
+ assert new VarianceSplitCalculator().splitRegion(samples, values, labels, 0, data) == null;
+ }
+
+ /** Test calculation of split of region consisting from two classes. */
+ @Test
+ public void testSplitTwoClassesTwoPoints() {
+ double labels[] = new double[] {0.0, 1.0};
+ double values[] = new double[] {0.0, 1.0};
+ Integer[] samples = new Integer[] {0, 1};
+
+ VarianceSplitCalculator.VarianceData data = new VarianceSplitCalculator.VarianceData(0.25, 2, 0.5);
+
+ SplitInfo<VarianceSplitCalculator.VarianceData> split = new VarianceSplitCalculator().splitRegion(samples, values, labels, 0, data);
+
+ assert split.leftData().impurity() == 0;
+ assert split.leftData().mean() == 0;
+ assert split.leftData().getSize() == 1;
+
+ assert split.rightData().impurity() == 0;
+ assert split.rightData().mean() == 1;
+ assert split.rightData().getSize() == 1;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/java/org/apache/ignite/ml/trees/performance/ColumnDecisionTreeTrainerBenchmark.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/trees/performance/ColumnDecisionTreeTrainerBenchmark.java b/modules/ml/src/test/java/org/apache/ignite/ml/trees/performance/ColumnDecisionTreeTrainerBenchmark.java
new file mode 100644
index 0000000..4e7cc24
--- /dev/null
+++ b/modules/ml/src/test/java/org/apache/ignite/ml/trees/performance/ColumnDecisionTreeTrainerBenchmark.java
@@ -0,0 +1,455 @@
+/*
+ * 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.ignite.ml.trees.performance;
+
+import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Random;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.processors.cache.GridCacheProcessor;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.Model;
+import org.apache.ignite.ml.estimators.Estimators;
+import org.apache.ignite.ml.math.StorageConstants;
+import org.apache.ignite.ml.math.Tracer;
+import org.apache.ignite.ml.math.Vector;
+import org.apache.ignite.ml.math.distributed.keys.impl.SparseMatrixKey;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteTriFunction;
+import org.apache.ignite.ml.math.impls.matrix.SparseDistributedMatrix;
+import org.apache.ignite.ml.math.impls.storage.matrix.SparseDistributedMatrixStorage;
+import org.apache.ignite.ml.math.impls.vector.DenseLocalOnHeapVector;
+import org.apache.ignite.ml.structures.LabeledVectorDouble;
+import org.apache.ignite.ml.trees.BaseDecisionTreeTest;
+import org.apache.ignite.ml.trees.SplitDataGenerator;
+import org.apache.ignite.ml.trees.models.DecisionTreeModel;
+import org.apache.ignite.ml.trees.trainers.columnbased.BiIndex;
+import org.apache.ignite.ml.trees.trainers.columnbased.BiIndexedCacheColumnDecisionTreeTrainerInput;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+import org.apache.ignite.ml.trees.trainers.columnbased.MatrixColumnDecisionTreeTrainerInput;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.ContextCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.FeaturesCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.ProjectionsCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.SplitCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.ContinuousSplitCalculators;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.GiniSplitCalculator;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.VarianceSplitCalculator;
+import org.apache.ignite.ml.trees.trainers.columnbased.regcalcs.RegionCalculators;
+import org.apache.ignite.ml.util.MnistUtils;
+import org.apache.ignite.stream.StreamTransformer;
+import org.apache.ignite.testframework.junits.IgniteTestResources;
+import org.apache.log4j.Level;
+import org.junit.Assert;
+
+/**
+ * Various benchmarks for hand runs.
+ */
+public class ColumnDecisionTreeTrainerBenchmark extends BaseDecisionTreeTest {
+ /** Name of the property specifying path to training set images. */
+ private static final String PROP_TRAINING_IMAGES = "mnist.training.images";
+
+ /** Name of property specifying path to training set labels. */
+ private static final String PROP_TRAINING_LABELS = "mnist.training.labels";
+
+ /** Name of property specifying path to test set images. */
+ private static final String PROP_TEST_IMAGES = "mnist.test.images";
+
+ /** Name of property specifying path to test set labels. */
+ private static final String PROP_TEST_LABELS = "mnist.test.labels";
+
+ /** Function to approximate. */
+ private static final Function<Vector, Double> f1 = v -> v.get(0) * v.get(0) + 2 * Math.sin(v.get(1)) + v.get(2);
+
+ /** {@inheritDoc} */
+ @Override protected long getTestTimeout() {
+ return 6000000;
+ }
+
+ /** {@inheritDoc} */
+ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName,
+ IgniteTestResources rsrcs) throws Exception {
+ IgniteConfiguration configuration = super.getConfiguration(igniteInstanceName, rsrcs);
+ // We do not need any extra event types.
+ configuration.setIncludeEventTypes();
+ configuration.setPeerClassLoadingEnabled(false);
+
+ resetLog4j(Level.INFO, false, GridCacheProcessor.class.getPackage().getName());
+
+ return configuration;
+ }
+
+ /**
+ * This test is for manual run only.
+ * To run this test rename this method so it starts from 'test'.
+ */
+ public void tstCacheMixed() {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+ int ptsPerReg = 150;
+ int featCnt = 10;
+
+ HashMap<Integer, Integer> catsInfo = new HashMap<>();
+ catsInfo.put(1, 3);
+
+ Random rnd = new Random(12349L);
+
+ SplitDataGenerator<DenseLocalOnHeapVector> gen = new SplitDataGenerator<>(
+ featCnt, catsInfo, () -> new DenseLocalOnHeapVector(featCnt + 1), rnd).
+ split(0, 1, new int[] {0, 2}).
+ split(1, 0, -10.0).
+ split(0, 0, 0.0);
+
+ testByGenStreamerLoad(ptsPerReg, catsInfo, gen, rnd);
+ }
+
+ /**
+ * Run decision tree classifier on MNIST using bi-indexed cache as a storage for dataset.
+ * To run this test rename this method so it starts from 'test'.
+ *
+ * @throws IOException In case of loading MNIST dataset errors.
+ */
+ public void tstMNISTBiIndexedCache() throws IOException {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+
+ int ptsCnt = 40_000;
+ int featCnt = 28 * 28;
+
+ Properties props = loadMNISTProperties();
+
+ Stream<DenseLocalOnHeapVector> trainingMnistStream = MnistUtils.mnist(props.getProperty(PROP_TRAINING_IMAGES), props.getProperty(PROP_TRAINING_LABELS), new Random(123L), ptsCnt);
+ Stream<DenseLocalOnHeapVector> testMnistStream = MnistUtils.mnist(props.getProperty(PROP_TEST_IMAGES), props.getProperty(PROP_TEST_LABELS), new Random(123L), 10_000);
+
+ IgniteCache<BiIndex, Double> cache = createBiIndexedCache();
+
+ loadVectorsIntoBiIndexedCache(cache.getName(), trainingMnistStream.iterator(), featCnt + 1);
+
+ ColumnDecisionTreeTrainer<GiniSplitCalculator.GiniData> trainer =
+ new ColumnDecisionTreeTrainer<>(10, ContinuousSplitCalculators.GINI.apply(ignite), RegionCalculators.GINI, RegionCalculators.MOST_COMMON, ignite);
+
+ System.out.println(">>> Training started");
+ long before = System.currentTimeMillis();
+ DecisionTreeModel mdl = trainer.train(new BiIndexedCacheColumnDecisionTreeTrainerInput(cache, new HashMap<>(), ptsCnt, featCnt));
+ System.out.println(">>> Training finished in " + (System.currentTimeMillis() - before));
+
+ IgniteTriFunction<Model<Vector, Double>, Stream<IgniteBiTuple<Vector, Double>>, Function<Double, Double>, Double> mse = Estimators.errorsPercentage();
+ Double accuracy = mse.apply(mdl, testMnistStream.map(v -> new IgniteBiTuple<>(v.viewPart(0, featCnt), v.getX(featCnt))), Function.identity());
+ System.out.println(">>> Errs percentage: " + accuracy);
+
+ Assert.assertEquals(0, SplitCache.getOrCreate(ignite).size());
+ Assert.assertEquals(0, FeaturesCache.getOrCreate(ignite).size());
+ Assert.assertEquals(0, ContextCache.getOrCreate(ignite).size());
+ Assert.assertEquals(0, ProjectionsCache.getOrCreate(ignite).size());
+ }
+
+ /**
+ * Run decision tree classifier on MNIST using sparse distributed matrix as a storage for dataset.
+ * To run this test rename this method so it starts from 'test'.
+ *
+ * @throws IOException In case of loading MNIST dataset errors.
+ */
+ public void tstMNISTSparseDistributedMatrix() throws IOException {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+
+ int ptsCnt = 30_000;
+ int featCnt = 28 * 28;
+
+ Properties props = loadMNISTProperties();
+
+ Stream<DenseLocalOnHeapVector> trainingMnistStream = MnistUtils.mnist(props.getProperty(PROP_TRAINING_IMAGES), props.getProperty(PROP_TRAINING_LABELS), new Random(123L), ptsCnt);
+ Stream<DenseLocalOnHeapVector> testMnistStream = MnistUtils.mnist(props.getProperty(PROP_TEST_IMAGES), props.getProperty(PROP_TEST_LABELS), new Random(123L), 10_000);
+
+ SparseDistributedMatrix m = new SparseDistributedMatrix(ptsCnt, featCnt + 1, StorageConstants.COLUMN_STORAGE_MODE, StorageConstants.RANDOM_ACCESS_MODE);
+
+ SparseDistributedMatrixStorage sto = (SparseDistributedMatrixStorage)m.getStorage();
+
+ loadVectorsIntoSparseDistributedMatrixCache(sto.cache().getName(), sto.getUUID(), trainingMnistStream.iterator(), featCnt + 1);
+
+ ColumnDecisionTreeTrainer<GiniSplitCalculator.GiniData> trainer =
+ new ColumnDecisionTreeTrainer<>(10, ContinuousSplitCalculators.GINI.apply(ignite), RegionCalculators.GINI, RegionCalculators.MOST_COMMON, ignite);
+
+ System.out.println(">>> Training started");
+ long before = System.currentTimeMillis();
+ DecisionTreeModel mdl = trainer.train(new MatrixColumnDecisionTreeTrainerInput(m, new HashMap<>()));
+ System.out.println(">>> Training finished in " + (System.currentTimeMillis() - before));
+
+ IgniteTriFunction<Model<Vector, Double>, Stream<IgniteBiTuple<Vector, Double>>, Function<Double, Double>, Double> mse = Estimators.errorsPercentage();
+ Double accuracy = mse.apply(mdl, testMnistStream.map(v -> new IgniteBiTuple<>(v.viewPart(0, featCnt), v.getX(featCnt))), Function.identity());
+ System.out.println(">>> Errs percentage: " + accuracy);
+
+ Assert.assertEquals(0, SplitCache.getOrCreate(ignite).size());
+ Assert.assertEquals(0, FeaturesCache.getOrCreate(ignite).size());
+ Assert.assertEquals(0, ContextCache.getOrCreate(ignite).size());
+ Assert.assertEquals(0, ProjectionsCache.getOrCreate(ignite).size());
+ }
+
+ /** Load properties for MNIST tests. */
+ private static Properties loadMNISTProperties() throws IOException {
+ Properties res = new Properties();
+
+ InputStream is = ColumnDecisionTreeTrainerBenchmark.class.getClassLoader().getResourceAsStream("manualrun/trees/columntrees.manualrun.properties");
+
+ res.load(is);
+
+ return res;
+ }
+
+ /** */
+ private void testByGenStreamerLoad(int ptsPerReg, HashMap<Integer, Integer> catsInfo,
+ SplitDataGenerator<DenseLocalOnHeapVector> gen, Random rnd) {
+
+ List<IgniteBiTuple<Integer, DenseLocalOnHeapVector>> lst = gen.
+ points(ptsPerReg, (i, rn) -> i).
+ collect(Collectors.toList());
+
+ int featCnt = gen.featuresCnt();
+
+ Collections.shuffle(lst, rnd);
+
+ int numRegs = gen.regsCount();
+
+ SparseDistributedMatrix m = new SparseDistributedMatrix(numRegs * ptsPerReg, featCnt + 1, StorageConstants.COLUMN_STORAGE_MODE, StorageConstants.RANDOM_ACCESS_MODE);
+
+ IgniteFunction<DoubleStream, Double> regCalc = s -> s.average().orElse(0.0);
+
+ Map<Integer, List<LabeledVectorDouble>> byRegion = new HashMap<>();
+
+ SparseDistributedMatrixStorage sto = (SparseDistributedMatrixStorage)m.getStorage();
+ long before = System.currentTimeMillis();
+ System.out.println(">>> Batch loading started...");
+ loadVectorsIntoSparseDistributedMatrixCache(sto.cache().getName(), sto.getUUID(), gen.
+ points(ptsPerReg, (i, rn) -> i).map(IgniteBiTuple::get2).iterator(), featCnt + 1);
+ System.out.println(">>> Batch loading took " + (System.currentTimeMillis() - before) + " ms.");
+
+ for (IgniteBiTuple<Integer, DenseLocalOnHeapVector> bt : lst) {
+ byRegion.putIfAbsent(bt.get1(), new LinkedList<>());
+ byRegion.get(bt.get1()).add(asLabeledVector(bt.get2().getStorage().data()));
+ }
+
+ ColumnDecisionTreeTrainer<VarianceSplitCalculator.VarianceData> trainer =
+ new ColumnDecisionTreeTrainer<>(2, ContinuousSplitCalculators.VARIANCE, RegionCalculators.VARIANCE, regCalc, ignite);
+
+ before = System.currentTimeMillis();
+ DecisionTreeModel mdl = trainer.train(new MatrixColumnDecisionTreeTrainerInput(m, catsInfo));
+
+ System.out.println(">>> Took time(ms): " + (System.currentTimeMillis() - before));
+
+ byRegion.keySet().forEach(k -> {
+ LabeledVectorDouble sp = byRegion.get(k).get(0);
+ Tracer.showAscii(sp.vector());
+ System.out.println("Prediction: " + mdl.predict(sp.vector()) + "label: " + sp.doubleLabel());
+ assert mdl.predict(sp.vector()) == sp.doubleLabel();
+ });
+ }
+
+ /**
+ * Test decision tree regression.
+ * To run this test rename this method so it starts from 'test'.
+ */
+ public void tstF1() {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+ int ptsCnt = 10000;
+ Map<Integer, double[]> ranges = new HashMap<>();
+
+ ranges.put(0, new double[] {-100.0, 100.0});
+ ranges.put(1, new double[] {-100.0, 100.0});
+ ranges.put(2, new double[] {-100.0, 100.0});
+
+ int featCnt = 100;
+ double[] defRng = {-1.0, 1.0};
+
+ Vector[] trainVectors = vecsFromRanges(ranges, featCnt, defRng, new Random(123L), ptsCnt, f1);
+
+ SparseDistributedMatrix m = new SparseDistributedMatrix(ptsCnt, featCnt + 1, StorageConstants.COLUMN_STORAGE_MODE, StorageConstants.RANDOM_ACCESS_MODE);
+
+ SparseDistributedMatrixStorage sto = (SparseDistributedMatrixStorage)m.getStorage();
+
+ loadVectorsIntoSparseDistributedMatrixCache(sto.cache().getName(), sto.getUUID(), Arrays.stream(trainVectors).iterator(), featCnt + 1);
+
+ IgniteFunction<DoubleStream, Double> regCalc = s -> s.average().orElse(0.0);
+
+ ColumnDecisionTreeTrainer<VarianceSplitCalculator.VarianceData> trainer =
+ new ColumnDecisionTreeTrainer<>(10, ContinuousSplitCalculators.VARIANCE, RegionCalculators.VARIANCE, regCalc, ignite);
+
+ System.out.println(">>> Training started");
+ long before = System.currentTimeMillis();
+ DecisionTreeModel mdl = trainer.train(new MatrixColumnDecisionTreeTrainerInput(m, new HashMap<>()));
+ System.out.println(">>> Training finished in " + (System.currentTimeMillis() - before));
+
+ Vector[] testVectors = vecsFromRanges(ranges, featCnt, defRng, new Random(123L), 20, f1);
+
+ IgniteTriFunction<Model<Vector, Double>, Stream<IgniteBiTuple<Vector, Double>>, Function<Double, Double>, Double> mse = Estimators.MSE();
+ Double accuracy = mse.apply(mdl, Arrays.stream(testVectors).map(v -> new IgniteBiTuple<>(v.viewPart(0, featCnt), v.getX(featCnt))), Function.identity());
+ System.out.println(">>> MSE: " + accuracy);
+ }
+
+ /**
+ * Load vectors into sparse distributed matrix.
+ *
+ * @param cacheName Name of cache where matrix is stored.
+ * @param uuid UUID of matrix.
+ * @param iter Iterator over vectors.
+ * @param vectorSize size of vectors.
+ */
+ private void loadVectorsIntoSparseDistributedMatrixCache(String cacheName, UUID uuid,
+ Iterator<? extends org.apache.ignite.ml.math.Vector> iter, int vectorSize) {
+ try (IgniteDataStreamer<SparseMatrixKey, Map<Integer, Double>> streamer =
+ Ignition.localIgnite().dataStreamer(cacheName)) {
+ int sampleIdx = 0;
+ streamer.allowOverwrite(true);
+
+ streamer.receiver(StreamTransformer.from((e, arg) -> {
+ Map<Integer, Double> val = e.getValue();
+
+ if (val == null)
+ val = new Int2DoubleOpenHashMap();
+
+ val.putAll((Map<Integer, Double>)arg[0]);
+
+ e.setValue(val);
+
+ return null;
+ }));
+
+ // Feature index -> (sample index -> value)
+ Map<Integer, Map<Integer, Double>> batch = new HashMap<>();
+ IntStream.range(0, vectorSize).forEach(i -> batch.put(i, new HashMap<>()));
+ int batchSize = 1000;
+
+ while (iter.hasNext()) {
+ org.apache.ignite.ml.math.Vector next = iter.next();
+
+ for (int i = 0; i < vectorSize; i++)
+ batch.get(i).put(sampleIdx, next.getX(i));
+
+ System.out.println(sampleIdx);
+ if (sampleIdx % batchSize == 0) {
+ batch.keySet().forEach(fi -> streamer.addData(new SparseMatrixKey(fi, uuid, fi), batch.get(fi)));
+ IntStream.range(0, vectorSize).forEach(i -> batch.put(i, new HashMap<>()));
+ }
+ sampleIdx++;
+ }
+ if (sampleIdx % batchSize != 0) {
+ batch.keySet().forEach(fi -> streamer.addData(new SparseMatrixKey(fi, uuid, fi), batch.get(fi)));
+ IntStream.range(0, vectorSize).forEach(i -> batch.put(i, new HashMap<>()));
+ }
+ }
+ }
+
+ /**
+ * Load vectors into bi-indexed cache.
+ *
+ * @param cacheName Name of cache.
+ * @param iter Iterator over vectors.
+ * @param vectorSize size of vectors.
+ */
+ private void loadVectorsIntoBiIndexedCache(String cacheName,
+ Iterator<? extends org.apache.ignite.ml.math.Vector> iter, int vectorSize) {
+ try (IgniteDataStreamer<BiIndex, Double> streamer =
+ Ignition.localIgnite().dataStreamer(cacheName)) {
+ int sampleIdx = 0;
+
+ streamer.perNodeBufferSize(10000);
+
+ while (iter.hasNext()) {
+ org.apache.ignite.ml.math.Vector next = iter.next();
+
+ for (int i = 0; i < vectorSize; i++)
+ streamer.addData(new BiIndex(sampleIdx, i), next.getX(i));
+
+ sampleIdx++;
+
+ if (sampleIdx % 1000 == 0)
+ System.out.println(">>> Loaded " + sampleIdx + " vectors.");
+ }
+ }
+ }
+
+ /**
+ * Create bi-indexed cache for tests.
+ *
+ * @return Bi-indexed cache.
+ */
+ private IgniteCache<BiIndex, Double> createBiIndexedCache() {
+ CacheConfiguration<BiIndex, Double> cfg = new CacheConfiguration<>();
+
+ // Write to primary.
+ cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+ // Atomic transactions only.
+ cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+ // No eviction.
+ cfg.setEvictionPolicy(null);
+
+ // No copying of values.
+ cfg.setCopyOnRead(false);
+
+ // Cache is partitioned.
+ cfg.setCacheMode(CacheMode.PARTITIONED);
+
+ cfg.setBackups(0);
+
+ cfg.setName("TMP_BI_INDEXED_CACHE");
+
+ return Ignition.localIgnite().getOrCreateCache(cfg);
+ }
+
+ /** */
+ private Vector[] vecsFromRanges(Map<Integer, double[]> ranges, int featCnt, double[] defRng, Random rnd, int ptsCnt,
+ Function<Vector, Double> f) {
+ int vs = featCnt + 1;
+ DenseLocalOnHeapVector[] res = new DenseLocalOnHeapVector[ptsCnt];
+ for (int pt = 0; pt < ptsCnt; pt++) {
+ DenseLocalOnHeapVector v = new DenseLocalOnHeapVector(vs);
+ for (int i = 0; i < featCnt; i++) {
+ double[] range = ranges.getOrDefault(i, defRng);
+ double from = range[0];
+ double to = range[1];
+ double rng = to - from;
+
+ v.setX(i, rnd.nextDouble() * rng);
+ }
+ v.setX(featCnt, f.apply(v));
+ res[pt] = v;
+ }
+
+ return res;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/test/resources/manualrun/trees/columntrees.manualrun.properties
----------------------------------------------------------------------
diff --git a/modules/ml/src/test/resources/manualrun/trees/columntrees.manualrun.properties b/modules/ml/src/test/resources/manualrun/trees/columntrees.manualrun.properties
new file mode 100644
index 0000000..7040010
--- /dev/null
+++ b/modules/ml/src/test/resources/manualrun/trees/columntrees.manualrun.properties
@@ -0,0 +1,5 @@
+# Paths to mnist dataset parts.
+mnist.training.images=/path/to/train-images-idx3-ubyte
+mnist.training.labels=/path/to/train-labels-idx1-ubyte
+mnist.test.images=/path/to/t10k-images-idx3-ubyte
+mnist.test.labels=/path/to/t10k-labels-idx1-ubyte
\ No newline at end of file
[4/4] ignite git commit: IGNITE-5218: First version of decision
trees. This closes #2936
Posted by is...@apache.org.
IGNITE-5218: First version of decision trees.
This closes #2936
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/db7697b1
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/db7697b1
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/db7697b1
Branch: refs/heads/master
Commit: db7697b17cf6eb94754edb2b5e200655a3610dc1
Parents: 6579e69
Author: Artem Malykh <am...@gridgain.com>
Authored: Fri Nov 10 18:03:33 2017 +0300
Committer: Igor Sapego <is...@gridgain.com>
Committed: Fri Nov 10 18:03:33 2017 +0300
----------------------------------------------------------------------
.gitignore | 2 +
examples/pom.xml | 5 +
.../examples/ml/math/trees/MNISTExample.java | 261 +++++++++
.../examples/ml/math/trees/package-info.java | 22 +
modules/ml/licenses/netlib-java-bsd3.txt | 51 ++
modules/ml/pom.xml | 12 +-
.../main/java/org/apache/ignite/ml/Model.java | 4 +-
.../main/java/org/apache/ignite/ml/Trainer.java | 30 +
.../clustering/KMeansDistributedClusterer.java | 19 +-
.../apache/ignite/ml/estimators/Estimators.java | 50 ++
.../ignite/ml/estimators/package-info.java | 22 +
.../ignite/ml/math/distributed/CacheUtils.java | 178 +++++-
.../math/distributed/keys/MatrixCacheKey.java | 6 +-
.../distributed/keys/impl/BlockMatrixKey.java | 17 +-
.../distributed/keys/impl/SparseMatrixKey.java | 59 +-
.../ignite/ml/math/functions/Functions.java | 38 ++
.../ml/math/functions/IgniteBinaryOperator.java | 29 +
.../math/functions/IgniteCurriedBiFunction.java | 29 +
.../ml/math/functions/IgniteSupplier.java | 30 +
.../math/functions/IgniteToDoubleFunction.java | 25 +
.../matrix/SparseBlockDistributedMatrix.java | 4 +-
.../impls/matrix/SparseDistributedMatrix.java | 3 +-
.../storage/matrix/BlockMatrixStorage.java | 12 +-
.../matrix/SparseDistributedMatrixStorage.java | 17 +-
.../ignite/ml/structures/LabeledVector.java | 63 +++
.../ml/structures/LabeledVectorDouble.java | 46 ++
.../ignite/ml/structures/package-info.java | 22 +
.../ignite/ml/trees/CategoricalRegionInfo.java | 72 +++
.../ignite/ml/trees/CategoricalSplitInfo.java | 68 +++
.../ignite/ml/trees/ContinuousRegionInfo.java | 74 +++
.../ml/trees/ContinuousSplitCalculator.java | 50 ++
.../org/apache/ignite/ml/trees/RegionInfo.java | 62 +++
.../ml/trees/models/DecisionTreeModel.java | 44 ++
.../ignite/ml/trees/models/package-info.java | 22 +
.../ml/trees/nodes/CategoricalSplitNode.java | 50 ++
.../ml/trees/nodes/ContinuousSplitNode.java | 56 ++
.../ignite/ml/trees/nodes/DecisionTreeNode.java | 33 ++
.../org/apache/ignite/ml/trees/nodes/Leaf.java | 49 ++
.../apache/ignite/ml/trees/nodes/SplitNode.java | 100 ++++
.../ignite/ml/trees/nodes/package-info.java | 22 +
.../apache/ignite/ml/trees/package-info.java | 22 +
.../ml/trees/trainers/columnbased/BiIndex.java | 113 ++++
...exedCacheColumnDecisionTreeTrainerInput.java | 57 ++
.../CacheColumnDecisionTreeTrainerInput.java | 142 +++++
.../columnbased/ColumnDecisionTreeTrainer.java | 557 +++++++++++++++++++
.../ColumnDecisionTreeTrainerInput.java | 55 ++
.../MatrixColumnDecisionTreeTrainerInput.java | 82 +++
.../trainers/columnbased/RegionProjection.java | 109 ++++
.../trainers/columnbased/TrainingContext.java | 166 ++++++
.../columnbased/caches/ContextCache.java | 68 +++
.../columnbased/caches/FeaturesCache.java | 151 +++++
.../columnbased/caches/ProjectionsCache.java | 284 ++++++++++
.../trainers/columnbased/caches/SplitCache.java | 206 +++++++
.../ContinuousSplitCalculators.java | 34 ++
.../contsplitcalcs/GiniSplitCalculator.java | 234 ++++++++
.../contsplitcalcs/VarianceSplitCalculator.java | 179 ++++++
.../contsplitcalcs/package-info.java | 22 +
.../trainers/columnbased/package-info.java | 22 +
.../columnbased/regcalcs/RegionCalculators.java | 85 +++
.../columnbased/regcalcs/package-info.java | 22 +
.../vectors/CategoricalFeatureProcessor.java | 211 +++++++
.../vectors/ContinuousFeatureProcessor.java | 111 ++++
.../vectors/ContinuousSplitInfo.java | 54 ++
.../columnbased/vectors/FeatureProcessor.java | 81 +++
.../vectors/FeatureVectorProcessorUtils.java | 57 ++
.../columnbased/vectors/SampleInfo.java | 80 +++
.../trainers/columnbased/vectors/SplitInfo.java | 106 ++++
.../columnbased/vectors/package-info.java | 22 +
.../org/apache/ignite/ml/util/MnistUtils.java | 121 ++++
.../java/org/apache/ignite/ml/util/Utils.java | 53 ++
.../org/apache/ignite/ml/IgniteMLTestSuite.java | 4 +-
.../java/org/apache/ignite/ml/TestUtils.java | 15 +
.../SparseDistributedBlockMatrixTest.java | 1 +
.../ignite/ml/trees/BaseDecisionTreeTest.java | 70 +++
.../ml/trees/ColumnDecisionTreeTrainerTest.java | 190 +++++++
.../ignite/ml/trees/DecisionTreesTestSuite.java | 33 ++
.../ml/trees/GiniSplitCalculatorTest.java | 141 +++++
.../ignite/ml/trees/SplitDataGenerator.java | 390 +++++++++++++
.../ml/trees/VarianceSplitCalculatorTest.java | 84 +++
.../ColumnDecisionTreeTrainerBenchmark.java | 455 +++++++++++++++
.../trees/columntrees.manualrun.properties | 5 +
81 files changed, 6538 insertions(+), 114 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index d8dd951..18146f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,3 +89,5 @@ packages
/modules/platforms/cpp/odbc-test/ignite-odbc-tests
/modules/platforms/cpp/stamp-h1
+#Files related to ML manual-runnable tests
+/modules/ml/src/test/resources/manualrun/trees/columntrees.manualrun.properties
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/examples/pom.xml
----------------------------------------------------------------------
diff --git a/examples/pom.xml b/examples/pom.xml
index 30d23ae..2b95e65 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -248,6 +248,11 @@
<artifactId>ignite-ml</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>commons-cli</groupId>
+ <artifactId>commons-cli</artifactId>
+ <version>1.2</version>
+ </dependency>
</dependencies>
</profile>
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/MNISTExample.java
----------------------------------------------------------------------
diff --git a/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/MNISTExample.java b/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/MNISTExample.java
new file mode 100644
index 0000000..6aaadd9
--- /dev/null
+++ b/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/MNISTExample.java
@@ -0,0 +1,261 @@
+/*
+ * 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.ignite.examples.ml.math.trees;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Random;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.apache.commons.cli.BasicParser;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.OptionBuilder;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteDataStreamer;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.examples.ExampleNodeStartup;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.Model;
+import org.apache.ignite.ml.estimators.Estimators;
+import org.apache.ignite.ml.math.Vector;
+import org.apache.ignite.ml.math.functions.IgniteTriFunction;
+import org.apache.ignite.ml.math.impls.vector.DenseLocalOnHeapVector;
+import org.apache.ignite.ml.trees.models.DecisionTreeModel;
+import org.apache.ignite.ml.trees.trainers.columnbased.BiIndex;
+import org.apache.ignite.ml.trees.trainers.columnbased.BiIndexedCacheColumnDecisionTreeTrainerInput;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.ContinuousSplitCalculators;
+import org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs.GiniSplitCalculator;
+import org.apache.ignite.ml.trees.trainers.columnbased.regcalcs.RegionCalculators;
+import org.apache.ignite.ml.util.MnistUtils;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * <p>
+ * Example of usage of decision trees algorithm for MNIST dataset
+ * (it can be found here: http://yann.lecun.com/exdb/mnist/). </p>
+ * <p>
+ * Remote nodes should always be started with special configuration file which
+ * enables P2P class loading: {@code 'ignite.{sh|bat} examples/config/example-ignite.xml'}.</p>
+ * <p>
+ * Alternatively you can run {@link ExampleNodeStartup} in another JVM which will start node
+ * with {@code examples/config/example-ignite.xml} configuration.</p>
+ * <p>
+ * It is recommended to start at least one node prior to launching this example if you intend
+ * to run it with default memory settings.</p>
+ * <p>
+ * This example should with program arguments, for example
+ * -ts_i /path/to/train-images-idx3-ubyte
+ * -ts_l /path/to/train-labels-idx1-ubyte
+ * -tss_i /path/to/t10k-images-idx3-ubyte
+ * -tss_l /path/to/t10k-labels-idx1-ubyte
+ * -cfg examples/config/example-ignite.xml.</p>
+ * <p>
+ * -ts_i specifies path to training set images of MNIST;
+ * -ts_l specifies path to training set labels of MNIST;
+ * -tss_i specifies path to test set images of MNIST;
+ * -tss_l specifies path to test set labels of MNIST;
+ * -cfg specifies path to a config path.</p>
+ */
+public class MNISTExample {
+ /** Name of parameter specifying path to training set images. */
+ private static final String MNIST_TRAINING_IMAGES_PATH = "ts_i";
+
+ /** Name of parameter specifying path to training set labels. */
+ private static final String MNIST_TRAINING_LABELS_PATH = "ts_l";
+
+ /** Name of parameter specifying path to test set images. */
+ private static final String MNIST_TEST_IMAGES_PATH = "tss_i";
+
+ /** Name of parameter specifying path to test set labels. */
+ private static final String MNIST_TEST_LABELS_PATH = "tss_l";
+
+ /** Name of parameter specifying path of Ignite config. */
+ private static final String CONFIG = "cfg";
+
+ /** Default config path. */
+ private static final String DEFAULT_CONFIG = "examples/config/example-ignite.xml";
+
+ /**
+ * Launches example.
+ *
+ * @param args Program arguments.
+ */
+ public static void main(String[] args) {
+ String igniteCfgPath;
+
+ CommandLineParser parser = new BasicParser();
+
+ String trainingImagesPath;
+ String trainingLabelsPath;
+
+ String testImagesPath;
+ String testLabelsPath;
+
+ try {
+ // Parse the command line arguments.
+ CommandLine line = parser.parse(buildOptions(), args);
+
+ trainingImagesPath = line.getOptionValue(MNIST_TRAINING_IMAGES_PATH);
+ trainingLabelsPath = line.getOptionValue(MNIST_TRAINING_LABELS_PATH);
+ testImagesPath = line.getOptionValue(MNIST_TEST_IMAGES_PATH);
+ testLabelsPath = line.getOptionValue(MNIST_TEST_LABELS_PATH);
+ igniteCfgPath = line.getOptionValue(CONFIG, DEFAULT_CONFIG);
+ }
+ catch (ParseException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ try (Ignite ignite = Ignition.start(igniteCfgPath)) {
+ IgniteUtils.setCurrentIgniteName(ignite.configuration().getIgniteInstanceName());
+
+ int ptsCnt = 60000;
+ int featCnt = 28 * 28;
+
+ Stream<DenseLocalOnHeapVector> trainingMnistStream = MnistUtils.mnist(trainingImagesPath, trainingLabelsPath, new Random(123L), ptsCnt);
+ Stream<DenseLocalOnHeapVector> testMnistStream = MnistUtils.mnist(testImagesPath, testLabelsPath, new Random(123L), 10_000);
+
+ IgniteCache<BiIndex, Double> cache = createBiIndexedCache(ignite);
+
+ loadVectorsIntoBiIndexedCache(cache.getName(), trainingMnistStream.iterator(), featCnt + 1, ignite);
+
+ ColumnDecisionTreeTrainer<GiniSplitCalculator.GiniData> trainer =
+ new ColumnDecisionTreeTrainer<>(10, ContinuousSplitCalculators.GINI.apply(ignite), RegionCalculators.GINI, RegionCalculators.MOST_COMMON, ignite);
+
+ System.out.println(">>> Training started");
+ long before = System.currentTimeMillis();
+ DecisionTreeModel mdl = trainer.train(new BiIndexedCacheColumnDecisionTreeTrainerInput(cache, new HashMap<>(), ptsCnt, featCnt));
+ System.out.println(">>> Training finished in " + (System.currentTimeMillis() - before));
+
+ IgniteTriFunction<Model<Vector, Double>, Stream<IgniteBiTuple<Vector, Double>>, Function<Double, Double>, Double> mse = Estimators.errorsPercentage();
+ Double accuracy = mse.apply(mdl, testMnistStream.map(v -> new IgniteBiTuple<>(v.viewPart(0, featCnt), v.getX(featCnt))), Function.identity());
+ System.out.println(">>> Errs percentage: " + accuracy);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Build cli options.
+ */
+ @NotNull private static Options buildOptions() {
+ Options options = new Options();
+
+ Option trsImagesPathOpt = OptionBuilder.withArgName(MNIST_TRAINING_IMAGES_PATH).withLongOpt(MNIST_TRAINING_IMAGES_PATH).hasArg()
+ .withDescription("Path to the MNIST training set.")
+ .isRequired(true).create();
+
+ Option trsLabelsPathOpt = OptionBuilder.withArgName(MNIST_TRAINING_LABELS_PATH).withLongOpt(MNIST_TRAINING_LABELS_PATH).hasArg()
+ .withDescription("Path to the MNIST training set.")
+ .isRequired(true).create();
+
+ Option tssImagesPathOpt = OptionBuilder.withArgName(MNIST_TEST_IMAGES_PATH).withLongOpt(MNIST_TEST_IMAGES_PATH).hasArg()
+ .withDescription("Path to the MNIST test set.")
+ .isRequired(true).create();
+
+ Option tssLabelsPathOpt = OptionBuilder.withArgName(MNIST_TEST_LABELS_PATH).withLongOpt(MNIST_TEST_LABELS_PATH).hasArg()
+ .withDescription("Path to the MNIST test set.")
+ .isRequired(true).create();
+
+ Option configOpt = OptionBuilder.withArgName(CONFIG).withLongOpt(CONFIG).hasArg()
+ .withDescription("Path to the config.")
+ .isRequired(false).create();
+
+ options.addOption(trsImagesPathOpt);
+ options.addOption(trsLabelsPathOpt);
+ options.addOption(tssImagesPathOpt);
+ options.addOption(tssLabelsPathOpt);
+ options.addOption(configOpt);
+
+ return options;
+ }
+
+ /**
+ * Creates cache where data for training is stored.
+ *
+ * @param ignite Ignite instance.
+ * @return cache where data for training is stored.
+ */
+ private static IgniteCache<BiIndex, Double> createBiIndexedCache(Ignite ignite) {
+ CacheConfiguration<BiIndex, Double> cfg = new CacheConfiguration<>();
+
+ // Write to primary.
+ cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+ // Atomic transactions only.
+ cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+ // No eviction.
+ cfg.setEvictionPolicy(null);
+
+ // No copying of values.
+ cfg.setCopyOnRead(false);
+
+ // Cache is partitioned.
+ cfg.setCacheMode(CacheMode.PARTITIONED);
+
+ cfg.setBackups(0);
+
+ cfg.setName("TMP_BI_INDEXED_CACHE");
+
+ return ignite.getOrCreateCache(cfg);
+ }
+
+ /**
+ * Loads vectors into cache.
+ *
+ * @param cacheName Name of cache.
+ * @param vectorsIterator Iterator over vectors to load.
+ * @param vectorSize Size of vector.
+ * @param ignite Ignite instance.
+ */
+ private static void loadVectorsIntoBiIndexedCache(String cacheName, Iterator<? extends Vector> vectorsIterator,
+ int vectorSize, Ignite ignite) {
+ try (IgniteDataStreamer<BiIndex, Double> streamer =
+ ignite.dataStreamer(cacheName)) {
+ int sampleIdx = 0;
+
+ streamer.perNodeBufferSize(10000);
+
+ while (vectorsIterator.hasNext()) {
+ org.apache.ignite.ml.math.Vector next = vectorsIterator.next();
+
+ for (int i = 0; i < vectorSize; i++)
+ streamer.addData(new BiIndex(sampleIdx, i), next.getX(i));
+
+ sampleIdx++;
+
+ if (sampleIdx % 1000 == 0)
+ System.out.println("Loaded " + sampleIdx + " vectors.");
+ }
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/package-info.java
----------------------------------------------------------------------
diff --git a/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/package-info.java b/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/package-info.java
new file mode 100644
index 0000000..9b6867b
--- /dev/null
+++ b/examples/src/main/ml/org/apache/ignite/examples/ml/math/trees/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Decision trees examples.
+ */
+package org.apache.ignite.examples.ml.math.trees;
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/licenses/netlib-java-bsd3.txt
----------------------------------------------------------------------
diff --git a/modules/ml/licenses/netlib-java-bsd3.txt b/modules/ml/licenses/netlib-java-bsd3.txt
new file mode 100644
index 0000000..d6b30c1
--- /dev/null
+++ b/modules/ml/licenses/netlib-java-bsd3.txt
@@ -0,0 +1,51 @@
+This product binaries redistribute netlib-java which is available under the following license:
+
+Copyright (c) 2013 Samuel Halliday
+Copyright (c) 1992-2011 The University of Tennessee and The University
+ of Tennessee Research Foundation. All rights
+ reserved.
+Copyright (c) 2000-2011 The University of California Berkeley. All
+ rights reserved.
+Copyright (c) 2006-2011 The University of Colorado Denver. All rights
+ reserved.
+
+$COPYRIGHT$
+
+Additional copyrights may follow
+
+$HEADER$
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+- Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer listed
+ in this license in the documentation and/or other materials
+ provided with the distribution.
+
+- Neither the name of the copyright holders nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+The copyright holders provide no reassurances that the source code
+provided does not infringe any patent, copyright, or any other
+intellectual property rights of third parties. The copyright holders
+disclaim any liability to any recipient for claims brought against
+recipient by any third party for infringement of that parties
+intellectual property rights.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/pom.xml
----------------------------------------------------------------------
diff --git a/modules/ml/pom.xml b/modules/ml/pom.xml
index 94cfc51..c495f44 100644
--- a/modules/ml/pom.xml
+++ b/modules/ml/pom.xml
@@ -75,13 +75,6 @@
<dependency>
<groupId>org.springframework</groupId>
- <artifactId>spring-beans</artifactId>
- <version>${spring.version}</version>
- <scope>test</scope>
- </dependency>
-
- <dependency>
- <groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
@@ -105,6 +98,11 @@
<version>1.0</version>
</dependency>
+ <dependency>
+ <groupId>com.zaxxer</groupId>
+ <artifactId>SparseBitSet</artifactId>
+ <version>1.0</version>
+ </dependency>
</dependencies>
<profiles>
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/Model.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/Model.java b/modules/ml/src/main/java/org/apache/ignite/ml/Model.java
index 3c60bfa..05ce774 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/Model.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/Model.java
@@ -24,7 +24,7 @@ import java.util.function.BiFunction;
@FunctionalInterface
public interface Model<T, V> extends Serializable {
/** Predict a result for value. */
- public V predict(T val);
+ V predict(T val);
/**
* Combines this model with other model via specified combiner
@@ -33,7 +33,7 @@ public interface Model<T, V> extends Serializable {
* @param combiner Combiner.
* @return Combination of models.
*/
- public default <X, W> Model<T, X> combine(Model<T, W> other, BiFunction<V, W, X> combiner) {
+ default <X, W> Model<T, X> combine(Model<T, W> other, BiFunction<V, W, X> combiner) {
return v -> combiner.apply(predict(v), other.predict(v));
}
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/Trainer.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/Trainer.java b/modules/ml/src/main/java/org/apache/ignite/ml/Trainer.java
new file mode 100644
index 0000000..795e218
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/Trainer.java
@@ -0,0 +1,30 @@
+/*
+ * 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.ignite.ml;
+
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+
+/**
+ * Interface for Trainers. Trainer is just a function which produces model from the data.
+ * See for example {@link ColumnDecisionTreeTrainer}.
+ * @param <M> Type of produced model.
+ * @param <T> Type of data needed for model producing.
+ */
+public interface Trainer<M extends Model, T> {
+ M train(T data);
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/clustering/KMeansDistributedClusterer.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/clustering/KMeansDistributedClusterer.java b/modules/ml/src/main/java/org/apache/ignite/ml/clustering/KMeansDistributedClusterer.java
index d6a3fc3..6c25edc 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/clustering/KMeansDistributedClusterer.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/clustering/KMeansDistributedClusterer.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
+import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import javax.cache.Cache;
@@ -94,7 +95,7 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
boolean converged = false;
int iteration = 0;
int dim = pointsCp.viewRow(0).size();
- IgniteUuid uid = pointsCp.getUUID();
+ UUID uid = pointsCp.getUUID();
// Execute iterations of Lloyd's algorithm until converged
while (iteration < maxIterations && !converged) {
@@ -140,7 +141,7 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
// to their squared distance from the centers. Note that only distances between points
// and new centers are computed in each iteration.
int step = 0;
- IgniteUuid uid = points.getUUID();
+ UUID uid = points.getUUID();
while (step < initSteps) {
// We assume here that costs can fit into memory of one node.
@@ -180,7 +181,7 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
}
/** */
- private List<Vector> getNewCenters(int k, ConcurrentHashMap<Integer, Double> costs, IgniteUuid uid,
+ private List<Vector> getNewCenters(int k, ConcurrentHashMap<Integer, Double> costs, UUID uid,
double sumCosts, String cacheName) {
return distributedFold(cacheName,
(IgniteBiFunction<Cache.Entry<SparseMatrixKey, Map<Integer, Double>>,
@@ -200,7 +201,7 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
list1.addAll(list2);
return list1;
},
- new ArrayList<>()
+ ArrayList::new
);
}
@@ -219,11 +220,11 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
(map1, map2) -> {
map1.putAll(map2);
return map1;
- }, new ConcurrentHashMap<>());
+ }, ConcurrentHashMap::new);
}
/** */
- private ConcurrentHashMap<Integer, Integer> weightCenters(IgniteUuid uid, List<Vector> distinctCenters, String cacheName) {
+ private ConcurrentHashMap<Integer, Integer> weightCenters(UUID uid, List<Vector> distinctCenters, String cacheName) {
return distributedFold(cacheName,
(IgniteBiFunction<Cache.Entry<SparseMatrixKey, Map<Integer, Double>>,
ConcurrentHashMap<Integer, Integer>,
@@ -249,7 +250,7 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
key -> key.matrixId().equals(uid),
(map1, map2) -> MapUtil.mergeMaps(map1, map2, (integer, integer2) -> integer2 + integer,
ConcurrentHashMap::new),
- new ConcurrentHashMap<>());
+ ConcurrentHashMap::new);
}
/** */
@@ -258,7 +259,7 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
}
/** */
- private SumsAndCounts getSumsAndCounts(Vector[] centers, int dim, IgniteUuid uid, String cacheName) {
+ private SumsAndCounts getSumsAndCounts(Vector[] centers, int dim, UUID uid, String cacheName) {
return CacheUtils.distributedFold(cacheName,
(IgniteBiFunction<Cache.Entry<SparseMatrixKey, Map<Integer, Double>>, SumsAndCounts, SumsAndCounts>)(entry, counts) -> {
Map<Integer, Double> vec = entry.getValue();
@@ -278,7 +279,7 @@ public class KMeansDistributedClusterer extends BaseKMeansClusterer<SparseDistri
return counts;
},
key -> key.matrixId().equals(uid),
- SumsAndCounts::merge, new SumsAndCounts()
+ SumsAndCounts::merge, SumsAndCounts::new
);
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/estimators/Estimators.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/estimators/Estimators.java b/modules/ml/src/main/java/org/apache/ignite/ml/estimators/Estimators.java
new file mode 100644
index 0000000..13331d1
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/estimators/Estimators.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ignite.ml.estimators;
+
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.Function;
+import java.util.stream.Stream;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.Model;
+import org.apache.ignite.ml.math.functions.IgniteTriFunction;
+
+/** Estimators. */
+public class Estimators {
+ /** Simple implementation of mean squared error estimator. */
+ public static <T, V> IgniteTriFunction<Model<T, V>, Stream<IgniteBiTuple<T, V>>, Function<V, Double>, Double> MSE() {
+ return (model, stream, f) -> stream.mapToDouble(dp -> {
+ double diff = f.apply(dp.get2()) - f.apply(model.predict(dp.get1()));
+ return diff * diff;
+ }).average().orElse(0);
+ }
+
+ /** Simple implementation of errors percentage estimator. */
+ public static <T, V> IgniteTriFunction<Model<T, V>, Stream<IgniteBiTuple<T, V>>, Function<V, Double>, Double> errorsPercentage() {
+ return (model, stream, f) -> {
+ AtomicLong total = new AtomicLong(0);
+
+ long cnt = stream.
+ peek((ib) -> total.incrementAndGet()).
+ filter(dp -> !model.predict(dp.get1()).equals(dp.get2())).
+ count();
+
+ return (double)cnt / total.get();
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/estimators/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/estimators/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/estimators/package-info.java
new file mode 100644
index 0000000..c03827f
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/estimators/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Contains estimation algorithms.
+ */
+package org.apache.ignite.ml.estimators;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/CacheUtils.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/CacheUtils.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/CacheUtils.java
index 8c8bba7..b9eb386 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/CacheUtils.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/CacheUtils.java
@@ -21,7 +21,11 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BinaryOperator;
+import java.util.stream.Stream;
import javax.cache.Cache;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
@@ -32,17 +36,21 @@ import org.apache.ignite.cluster.ClusterGroup;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.processors.cache.CacheEntryImpl;
import org.apache.ignite.internal.util.typedef.internal.A;
+import org.apache.ignite.lang.IgniteBiTuple;
import org.apache.ignite.lang.IgniteCallable;
import org.apache.ignite.lang.IgnitePredicate;
import org.apache.ignite.lang.IgniteRunnable;
import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.ml.math.KeyMapper;
-import org.apache.ignite.ml.math.distributed.keys.RowColMatrixKey;
-import org.apache.ignite.ml.math.distributed.keys.impl.BlockMatrixKey;
+import org.apache.ignite.ml.math.distributed.keys.BlockMatrixKey;
+import org.apache.ignite.ml.math.distributed.keys.MatrixCacheKey;
import org.apache.ignite.ml.math.functions.IgniteBiFunction;
+import org.apache.ignite.ml.math.functions.IgniteBinaryOperator;
import org.apache.ignite.ml.math.functions.IgniteConsumer;
import org.apache.ignite.ml.math.functions.IgniteDoubleFunction;
import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+import org.apache.ignite.ml.math.functions.IgniteTriFunction;
import org.apache.ignite.ml.math.impls.matrix.BlockEntry;
/**
@@ -131,7 +139,7 @@ public class CacheUtils {
* @return Sum obtained using sparse logic.
*/
@SuppressWarnings("unchecked")
- public static <K, V> double sparseSum(IgniteUuid matrixUuid, String cacheName) {
+ public static <K, V> double sparseSum(UUID matrixUuid, String cacheName) {
A.notNull(matrixUuid, "matrixUuid");
A.notNull(cacheName, "cacheName");
@@ -198,7 +206,7 @@ public class CacheUtils {
* @return Minimum value obtained using sparse logic.
*/
@SuppressWarnings("unchecked")
- public static <K, V> double sparseMin(IgniteUuid matrixUuid, String cacheName) {
+ public static <K, V> double sparseMin(UUID matrixUuid, String cacheName) {
A.notNull(matrixUuid, "matrixUuid");
A.notNull(cacheName, "cacheName");
@@ -235,7 +243,7 @@ public class CacheUtils {
* @return Maximum value obtained using sparse logic.
*/
@SuppressWarnings("unchecked")
- public static <K, V> double sparseMax(IgniteUuid matrixUuid, String cacheName) {
+ public static <K, V> double sparseMax(UUID matrixUuid, String cacheName) {
A.notNull(matrixUuid, "matrixUuid");
A.notNull(cacheName, "cacheName");
@@ -316,7 +324,7 @@ public class CacheUtils {
* @param mapper Mapping {@link IgniteFunction}.
*/
@SuppressWarnings("unchecked")
- public static <K, V> void sparseMap(IgniteUuid matrixUuid, IgniteDoubleFunction<Double> mapper, String cacheName) {
+ public static <K, V> void sparseMap(UUID matrixUuid, IgniteDoubleFunction<Double> mapper, String cacheName) {
A.notNull(matrixUuid, "matrixUuid");
A.notNull(cacheName, "cacheName");
A.notNull(mapper, "mapper");
@@ -350,12 +358,12 @@ public class CacheUtils {
*
* @param matrixUuid Matrix uuid.
*/
- private static <K> IgnitePredicate<K> sparseKeyFilter(IgniteUuid matrixUuid) {
+ private static <K> IgnitePredicate<K> sparseKeyFilter(UUID matrixUuid) {
return key -> {
- if (key instanceof BlockMatrixKey)
- return ((BlockMatrixKey)key).matrixId().equals(matrixUuid);
- else if (key instanceof RowColMatrixKey)
- return ((RowColMatrixKey)key).matrixId().equals(matrixUuid);
+ if (key instanceof MatrixCacheKey)
+ return ((MatrixCacheKey)key).matrixId().equals(matrixUuid);
+ else if (key instanceof IgniteBiTuple)
+ return ((IgniteBiTuple<Integer, UUID>)key).get2().equals(matrixUuid);
else
throw new UnsupportedOperationException();
};
@@ -404,6 +412,76 @@ public class CacheUtils {
}
/**
+ * @param cacheName Cache name.
+ * @param fun An operation that accepts a cache entry and processes it.
+ * @param ignite Ignite.
+ * @param keysGen Keys generator.
+ * @param <K> Cache key object type.
+ * @param <V> Cache value object type.
+ */
+ public static <K, V> void update(String cacheName, Ignite ignite,
+ IgniteBiFunction<Ignite, Cache.Entry<K, V>, Stream<Cache.Entry<K, V>>> fun, IgniteSupplier<Set<K>> keysGen) {
+ bcast(cacheName, ignite, () -> {
+ Ignite ig = Ignition.localIgnite();
+ IgniteCache<K, V> cache = ig.getOrCreateCache(cacheName);
+
+ Affinity<K> affinity = ig.affinity(cacheName);
+ ClusterNode locNode = ig.cluster().localNode();
+
+ Collection<K> ks = affinity.mapKeysToNodes(keysGen.get()).get(locNode);
+
+ if (ks == null)
+ return;
+
+ Map<K, V> m = new ConcurrentHashMap<>();
+
+ ks.parallelStream().forEach(k -> {
+ V v = cache.localPeek(k);
+ if (v != null)
+ (fun.apply(ignite, new CacheEntryImpl<>(k, v))).forEach(ent -> m.put(ent.getKey(), ent.getValue()));
+ });
+
+ cache.putAll(m);
+ });
+ }
+
+ /**
+ * @param cacheName Cache name.
+ * @param fun An operation that accepts a cache entry and processes it.
+ * @param ignite Ignite.
+ * @param keysGen Keys generator.
+ * @param <K> Cache key object type.
+ * @param <V> Cache value object type.
+ */
+ public static <K, V> void update(String cacheName, Ignite ignite, IgniteConsumer<Cache.Entry<K, V>> fun,
+ IgniteSupplier<Set<K>> keysGen) {
+ bcast(cacheName, ignite, () -> {
+ Ignite ig = Ignition.localIgnite();
+ IgniteCache<K, V> cache = ig.getOrCreateCache(cacheName);
+
+ Affinity<K> affinity = ig.affinity(cacheName);
+ ClusterNode locNode = ig.cluster().localNode();
+
+ Collection<K> ks = affinity.mapKeysToNodes(keysGen.get()).get(locNode);
+
+ if (ks == null)
+ return;
+
+ Map<K, V> m = new ConcurrentHashMap<>();
+
+ for (K k : ks) {
+ V v = cache.localPeek(k);
+ fun.accept(new CacheEntryImpl<>(k, v));
+ m.put(k, v);
+ }
+
+ long before = System.currentTimeMillis();
+ cache.putAll(m);
+ System.out.println("PutAll took: " + (System.currentTimeMillis() - before));
+ });
+ }
+
+ /**
* <b>Currently fold supports only commutative operations.<b/>
*
* @param cacheName Cache name.
@@ -463,11 +541,11 @@ public class CacheUtils {
* @param folder Folder.
* @param keyFilter Key filter.
* @param accumulator Accumulator.
- * @param zeroVal Zero value.
+ * @param zeroValSupp Zero value supplier.
*/
public static <K, V, A> A distributedFold(String cacheName, IgniteBiFunction<Cache.Entry<K, V>, A, A> folder,
- IgnitePredicate<K> keyFilter, BinaryOperator<A> accumulator, A zeroVal) {
- return sparseFold(cacheName, folder, keyFilter, accumulator, zeroVal, null, null, 0,
+ IgnitePredicate<K> keyFilter, BinaryOperator<A> accumulator, IgniteSupplier<A> zeroValSupp) {
+ return sparseFold(cacheName, folder, keyFilter, accumulator, zeroValSupp, null, null, 0,
false);
}
@@ -478,17 +556,17 @@ public class CacheUtils {
* @param folder Folder.
* @param keyFilter Key filter.
* @param accumulator Accumulator.
- * @param zeroVal Zero value.
- * @param defVal Def value.
- * @param defKey Def key.
+ * @param zeroValSupp Zero value supplier.
+ * @param defVal Default value.
+ * @param defKey Default key.
* @param defValCnt Def value count.
* @param isNilpotent Is nilpotent.
*/
private static <K, V, A> A sparseFold(String cacheName, IgniteBiFunction<Cache.Entry<K, V>, A, A> folder,
- IgnitePredicate<K> keyFilter, BinaryOperator<A> accumulator, A zeroVal, V defVal, K defKey, long defValCnt,
- boolean isNilpotent) {
+ IgnitePredicate<K> keyFilter, BinaryOperator<A> accumulator, IgniteSupplier<A> zeroValSupp, V defVal, K defKey,
+ long defValCnt, boolean isNilpotent) {
- A defRes = zeroVal;
+ A defRes = zeroValSupp.get();
if (!isNilpotent)
for (int i = 0; i < defValCnt; i++)
@@ -504,7 +582,7 @@ public class CacheUtils {
Affinity affinity = ignite.affinity(cacheName);
ClusterNode locNode = ignite.cluster().localNode();
- A a = zeroVal;
+ A a = zeroValSupp.get();
// Iterate over all partitions. Some of them will be stored on that local node.
for (int part = 0; part < partsCnt; part++) {
@@ -519,16 +597,54 @@ public class CacheUtils {
return a;
});
- totalRes.add(defRes);
- return totalRes.stream().reduce(zeroVal, accumulator);
+ return totalRes.stream().reduce(defRes, accumulator);
+ }
+
+ public static <K, V, A, W> A reduce(String cacheName, Ignite ignite,
+ IgniteTriFunction<W, Cache.Entry<K, V>, A, A> acc,
+ IgniteSupplier<W> supp,
+ IgniteSupplier<Iterable<Cache.Entry<K, V>>> entriesGen, IgniteBinaryOperator<A> comb,
+ IgniteSupplier<A> zeroValSupp) {
+
+ A defRes = zeroValSupp.get();
+
+ Collection<A> totalRes = bcast(cacheName, ignite, () -> {
+ // Use affinity in filter for ScanQuery. Otherwise we accept consumer in each node which is wrong.
+ A a = zeroValSupp.get();
+
+ W w = supp.get();
+
+ for (Cache.Entry<K, V> kvEntry : entriesGen.get())
+ a = acc.apply(w, kvEntry, a);
+
+ return a;
+ });
+
+ return totalRes.stream().reduce(defRes, comb);
+ }
+
+ public static <K, V, A, W> A reduce(String cacheName, IgniteTriFunction<W, Cache.Entry<K, V>, A, A> acc,
+ IgniteSupplier<W> supp,
+ IgniteSupplier<Iterable<Cache.Entry<K, V>>> entriesGen, IgniteBinaryOperator<A> comb,
+ IgniteSupplier<A> zeroValSupp) {
+ return reduce(cacheName, Ignition.localIgnite(), acc, supp, entriesGen, comb, zeroValSupp);
}
/**
* @param cacheName Cache name.
* @param run {@link Runnable} to broadcast to cache nodes for given cache name.
*/
+ public static void bcast(String cacheName, Ignite ignite, IgniteRunnable run) {
+ ignite.compute(ignite.cluster().forDataNodes(cacheName)).broadcast(run);
+ }
+
+ /**
+ * Broadcast runnable to data nodes of given cache.
+ * @param cacheName Cache name.
+ * @param run Runnable.
+ */
public static void bcast(String cacheName, IgniteRunnable run) {
- ignite().compute(ignite().cluster().forCacheNodes(cacheName)).broadcast(run);
+ bcast(cacheName, ignite(), run);
}
/**
@@ -537,6 +653,18 @@ public class CacheUtils {
* @param <A> Type returned by the callable.
*/
public static <A> Collection<A> bcast(String cacheName, IgniteCallable<A> call) {
- return ignite().compute(ignite().cluster().forCacheNodes(cacheName)).broadcast(call);
+ return bcast(cacheName, ignite(), call);
+ }
+
+ /**
+ * Broadcast callable to data nodes of given cache.
+ * @param cacheName Cache name.
+ * @param ignite Ignite instance.
+ * @param call Callable to broadcast.
+ * @param <A> Type of callable result.
+ * @return Results of callable from each node.
+ */
+ public static <A> Collection<A> bcast(String cacheName, Ignite ignite, IgniteCallable<A> call) {
+ return ignite.compute(ignite.cluster().forDataNodes(cacheName)).broadcast(call);
}
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/MatrixCacheKey.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/MatrixCacheKey.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/MatrixCacheKey.java
index 669e9a4..0242560 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/MatrixCacheKey.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/MatrixCacheKey.java
@@ -17,7 +17,7 @@
package org.apache.ignite.ml.math.distributed.keys;
-import org.apache.ignite.lang.IgniteUuid;
+import java.util.UUID;
/**
* Base matrix cache key.
@@ -26,10 +26,10 @@ public interface MatrixCacheKey {
/**
* @return matrix id.
*/
- public IgniteUuid matrixId();
+ public UUID matrixId();
/**
* @return affinity key.
*/
- public IgniteUuid affinityKey();
+ public Object affinityKey();
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/BlockMatrixKey.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/BlockMatrixKey.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/BlockMatrixKey.java
index 2edd9cb..cc8c488 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/BlockMatrixKey.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/BlockMatrixKey.java
@@ -21,6 +21,7 @@ import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
+import java.util.UUID;
import org.apache.ignite.binary.BinaryObjectException;
import org.apache.ignite.binary.BinaryRawReader;
import org.apache.ignite.binary.BinaryRawWriter;
@@ -47,7 +48,7 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
/** Block col ID */
private long blockIdCol;
/** Matrix ID */
- private IgniteUuid matrixUuid;
+ private UUID matrixUuid;
/** Block affinity key. */
private IgniteUuid affinityKey;
@@ -64,7 +65,7 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
* @param matrixUuid Matrix uuid.
* @param affinityKey Affinity key.
*/
- public BlockMatrixKey(long rowId, long colId, IgniteUuid matrixUuid, @Nullable IgniteUuid affinityKey) {
+ public BlockMatrixKey(long rowId, long colId, UUID matrixUuid, @Nullable IgniteUuid affinityKey) {
assert rowId >= 0;
assert colId >= 0;
assert matrixUuid != null;
@@ -86,7 +87,7 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
}
/** {@inheritDoc} */
- @Override public IgniteUuid matrixId() {
+ @Override public UUID matrixId() {
return matrixUuid;
}
@@ -97,7 +98,7 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
/** {@inheritDoc} */
@Override public void writeExternal(ObjectOutput out) throws IOException {
- U.writeGridUuid(out, matrixUuid);
+ out.writeObject(matrixUuid);
U.writeGridUuid(out, affinityKey);
out.writeLong(blockIdRow);
out.writeLong(blockIdCol);
@@ -105,7 +106,7 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
/** {@inheritDoc} */
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
- matrixUuid = U.readGridUuid(in);
+ matrixUuid = (UUID)in.readObject();
affinityKey = U.readGridUuid(in);
blockIdRow = in.readLong();
blockIdCol = in.readLong();
@@ -115,7 +116,7 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
@Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
BinaryRawWriter out = writer.rawWriter();
- BinaryUtils.writeIgniteUuid(out, matrixUuid);
+ out.writeUuid(matrixUuid);
BinaryUtils.writeIgniteUuid(out, affinityKey);
out.writeLong(blockIdRow);
out.writeLong(blockIdCol);
@@ -125,7 +126,7 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
@Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
BinaryRawReader in = reader.rawReader();
- matrixUuid = BinaryUtils.readIgniteUuid(in);
+ matrixUuid = in.readUuid();
affinityKey = BinaryUtils.readIgniteUuid(in);
blockIdRow = in.readLong();
blockIdCol = in.readLong();
@@ -160,6 +161,4 @@ public class BlockMatrixKey implements org.apache.ignite.ml.math.distributed.key
@Override public String toString() {
return S.toString(BlockMatrixKey.class, this);
}
-
-
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/SparseMatrixKey.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/SparseMatrixKey.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/SparseMatrixKey.java
index 0c34c8b..aa5e0ad 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/SparseMatrixKey.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/distributed/keys/impl/SparseMatrixKey.java
@@ -21,30 +21,24 @@ import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
-import org.apache.ignite.binary.BinaryObjectException;
-import org.apache.ignite.binary.BinaryRawReader;
-import org.apache.ignite.binary.BinaryRawWriter;
-import org.apache.ignite.binary.BinaryReader;
-import org.apache.ignite.binary.BinaryWriter;
-import org.apache.ignite.binary.Binarylizable;
-import org.apache.ignite.internal.binary.BinaryUtils;
+import java.util.UUID;
+import org.apache.ignite.cache.affinity.AffinityKeyMapped;
import org.apache.ignite.internal.util.typedef.F;
import org.apache.ignite.internal.util.typedef.internal.S;
-import org.apache.ignite.internal.util.typedef.internal.U;
-import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.ml.math.distributed.keys.RowColMatrixKey;
import org.apache.ignite.ml.math.impls.matrix.SparseDistributedMatrix;
/**
* Key implementation for {@link SparseDistributedMatrix}.
*/
-public class SparseMatrixKey implements RowColMatrixKey, Externalizable, Binarylizable {
+public class SparseMatrixKey implements RowColMatrixKey, Externalizable {
/** */
private int idx;
/** */
- private IgniteUuid matrixId;
+ private UUID matrixId;
/** */
- private IgniteUuid affinityKey;
+ @AffinityKeyMapped
+ private Object affinityKey;
/**
* Default constructor (required by Externalizable).
@@ -56,7 +50,7 @@ public class SparseMatrixKey implements RowColMatrixKey, Externalizable, Binaryl
/**
* Build Key.
*/
- public SparseMatrixKey(int idx, IgniteUuid matrixId, IgniteUuid affinityKey) {
+ public SparseMatrixKey(int idx, UUID matrixId, Object affinityKey) {
assert idx >= 0 : "Index must be positive.";
assert matrixId != null : "Matrix id can`t be null.";
@@ -71,54 +65,35 @@ public class SparseMatrixKey implements RowColMatrixKey, Externalizable, Binaryl
}
/** {@inheritDoc} */
- @Override public IgniteUuid matrixId() {
+ @Override public UUID matrixId() {
return matrixId;
}
/** {@inheritDoc} */
- @Override public IgniteUuid affinityKey() {
+ @Override public Object affinityKey() {
return affinityKey;
}
/** {@inheritDoc} */
@Override public void writeExternal(ObjectOutput out) throws IOException {
- U.writeGridUuid(out, matrixId);
- U.writeGridUuid(out, affinityKey);
+// U.writeGridUuid(out, matrixId);
+ out.writeObject(matrixId);
+ out.writeObject(affinityKey);
out.writeInt(idx);
}
/** {@inheritDoc} */
@Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
- matrixId = U.readGridUuid(in);
- affinityKey = U.readGridUuid(in);
- idx = in.readInt();
- }
-
- /** {@inheritDoc} */
- @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
- BinaryRawWriter out = writer.rawWriter();
-
- BinaryUtils.writeIgniteUuid(out, matrixId);
- BinaryUtils.writeIgniteUuid(out, affinityKey);
- out.writeInt(idx);
- }
-
- /** {@inheritDoc} */
- @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
- BinaryRawReader in = reader.rawReader();
-
- matrixId = BinaryUtils.readIgniteUuid(in);
- affinityKey = BinaryUtils.readIgniteUuid(in);
+ matrixId = (UUID)in.readObject();
+ affinityKey = in.readObject();
idx = in.readInt();
}
/** {@inheritDoc} */
@Override public int hashCode() {
- int res = 1;
-
- res += res * 37 + matrixId.hashCode();
- res += res * 37 + idx;
-
+ int res = idx;
+ res = 31 * res + (matrixId != null ? matrixId.hashCode() : 0);
+ res = 31 * res + (affinityKey != null ? affinityKey.hashCode() : 0);
return res;
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/Functions.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/Functions.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/Functions.java
index 022dd04..0b4ad12 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/Functions.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/Functions.java
@@ -17,7 +17,9 @@
package org.apache.ignite.ml.math.functions;
+import java.util.Comparator;
import java.util.List;
+import java.util.function.BiFunction;
import org.apache.ignite.lang.IgniteBiTuple;
/**
@@ -75,6 +77,30 @@ public final class Functions {
/** Function that returns {@code max(abs(a), abs(b))}. */
public static final IgniteBiFunction<Double, Double, Double> MAX_ABS = (a, b) -> Math.max(Math.abs(a), Math.abs(b));
+ /**
+ * Generic 'max' function.
+ * @param a First object to compare.
+ * @param b Second object to compare.
+ * @param f Comparator.
+ * @param <T> Type of objects to compare.
+ * @return Maximum between {@code a} and {@code b} in terms of comparator {@code f}.
+ */
+ public static <T> T MAX_GENERIC(T a, T b, Comparator<T> f) {
+ return f.compare(a, b) > 0 ? a : b;
+ }
+
+ /**
+ * Generic 'min' function.
+ * @param a First object to compare.
+ * @param b Second object to compare.
+ * @param f Comparator.
+ * @param <T> Type of objects to compare.
+ * @return Minimum between {@code a} and {@code b} in terms of comparator {@code f}.
+ */
+ public static <T> T MIN_GENERIC(T a, T b, Comparator<T> f) {
+ return f.compare(a, b) > 0 ? a : b;
+ }
+
/** Function that returns {@code min(abs(a), abs(b))}. */
public static final IgniteBiFunction<Double, Double, Double> MIN_ABS = (a, b) -> Math.min(Math.abs(a), Math.abs(b));
@@ -185,4 +211,16 @@ public final class Functions {
return Math.pow(a, b);
};
}
+
+ /**
+ * Curry bifunction.
+ * @param f Bifunction to curry.
+ * @param <A> Type of first argument of {@code f}.
+ * @param <B> Type of second argument of {@code f}.
+ * @param <C> Return type of {@code f}.
+ * @return Curried bifunction.
+ */
+ public static <A, B, C> IgniteCurriedBiFunction<A, B, C> curry(BiFunction<A, B, C> f) {
+ return a -> b -> f.apply(a, b);
+ }
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteBinaryOperator.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteBinaryOperator.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteBinaryOperator.java
new file mode 100644
index 0000000..1170b67
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteBinaryOperator.java
@@ -0,0 +1,29 @@
+/*
+ * 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.ignite.ml.math.functions;
+
+import java.io.Serializable;
+import java.util.function.BinaryOperator;
+
+/**
+ * Serializable binary operator.
+ *
+ * @see java.util.function.BinaryOperator
+ */
+public interface IgniteBinaryOperator<A> extends BinaryOperator<A>, Serializable {
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteCurriedBiFunction.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteCurriedBiFunction.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteCurriedBiFunction.java
new file mode 100644
index 0000000..3dd8490
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteCurriedBiFunction.java
@@ -0,0 +1,29 @@
+/*
+ * 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.ignite.ml.math.functions;
+
+import java.io.Serializable;
+import java.util.function.BiFunction;
+
+/**
+ * Serializable binary function.
+ *
+ * @see BiFunction
+ */
+public interface IgniteCurriedBiFunction<A, B, T> extends IgniteFunction<A, IgniteFunction<B, T>>, Serializable {
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteSupplier.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteSupplier.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteSupplier.java
new file mode 100644
index 0000000..8c05b75
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteSupplier.java
@@ -0,0 +1,30 @@
+/*
+ * 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.ignite.ml.math.functions;
+
+import java.io.Serializable;
+import java.util.function.Supplier;
+
+/**
+ * Serializable supplier.
+ *
+ * @see java.util.function.Consumer
+ */
+@FunctionalInterface
+public interface IgniteSupplier<T> extends Supplier<T>, Serializable {
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteToDoubleFunction.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteToDoubleFunction.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteToDoubleFunction.java
new file mode 100644
index 0000000..59a8bf3
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/functions/IgniteToDoubleFunction.java
@@ -0,0 +1,25 @@
+/*
+ * 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.ignite.ml.math.functions;
+
+import java.io.Serializable;
+import java.util.function.ToDoubleFunction;
+
+@FunctionalInterface
+public interface IgniteToDoubleFunction<T> extends ToDoubleFunction<T>, Serializable {
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseBlockDistributedMatrix.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseBlockDistributedMatrix.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseBlockDistributedMatrix.java
index 3d542bc..e829168 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseBlockDistributedMatrix.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseBlockDistributedMatrix.java
@@ -20,13 +20,13 @@ package org.apache.ignite.ml.math.impls.matrix;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.UUID;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.affinity.Affinity;
import org.apache.ignite.cluster.ClusterNode;
import org.apache.ignite.internal.util.lang.IgnitePair;
-import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.ml.math.Matrix;
import org.apache.ignite.ml.math.StorageConstants;
import org.apache.ignite.ml.math.Vector;
@@ -190,7 +190,7 @@ public class SparseBlockDistributedMatrix extends AbstractMatrix implements Stor
}
/** */
- private IgniteUuid getUUID() {
+ private UUID getUUID() {
return ((BlockMatrixStorage)getStorage()).getUUID();
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedMatrix.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedMatrix.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedMatrix.java
index 9a18f8b..594aebc 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedMatrix.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/matrix/SparseDistributedMatrix.java
@@ -19,6 +19,7 @@ package org.apache.ignite.ml.math.impls.matrix;
import java.util.Collection;
import java.util.Map;
+import java.util.UUID;
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
@@ -211,7 +212,7 @@ public class SparseDistributedMatrix extends AbstractMatrix implements StorageCo
}
/** */
- public IgniteUuid getUUID() {
+ public UUID getUUID() {
return ((SparseDistributedMatrixStorage)getStorage()).getUUID();
}
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/BlockMatrixStorage.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/BlockMatrixStorage.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/BlockMatrixStorage.java
index 0d5cf0a..cd76e5a 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/BlockMatrixStorage.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/BlockMatrixStorage.java
@@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
+import java.util.UUID;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.cache.CacheAtomicityMode;
@@ -32,7 +33,6 @@ import org.apache.ignite.cache.CachePeekMode;
import org.apache.ignite.cache.CacheWriteSynchronizationMode;
import org.apache.ignite.configuration.CacheConfiguration;
import org.apache.ignite.internal.util.lang.IgnitePair;
-import org.apache.ignite.internal.util.typedef.internal.U;
import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.ml.math.MatrixStorage;
import org.apache.ignite.ml.math.StorageConstants;
@@ -59,7 +59,7 @@ public class BlockMatrixStorage extends CacheUtils implements MatrixStorage, Sto
/** Amount of columns in the matrix. */
private int cols;
/** Matrix uuid. */
- private IgniteUuid uuid;
+ private UUID uuid;
/** Block size about 8 KB of data. */
private int maxBlockEdge = MAX_BLOCK_SIZE;
@@ -92,7 +92,7 @@ public class BlockMatrixStorage extends CacheUtils implements MatrixStorage, Sto
cache = newCache();
- uuid = IgniteUuid.randomUuid();
+ uuid = UUID.randomUUID();
}
/**
@@ -152,7 +152,7 @@ public class BlockMatrixStorage extends CacheUtils implements MatrixStorage, Sto
out.writeInt(cols);
out.writeInt(blocksInRow);
out.writeInt(blocksInCol);
- U.writeGridUuid(out, uuid);
+ out.writeObject(uuid);
out.writeUTF(cache.getName());
}
@@ -162,7 +162,7 @@ public class BlockMatrixStorage extends CacheUtils implements MatrixStorage, Sto
cols = in.readInt();
blocksInRow = in.readInt();
blocksInCol = in.readInt();
- uuid = U.readGridUuid(in);
+ uuid = (UUID)in.readObject();
cache = ignite().getOrCreateCache(in.readUTF());
}
@@ -201,7 +201,7 @@ public class BlockMatrixStorage extends CacheUtils implements MatrixStorage, Sto
*
* @return storage UUID.
*/
- public IgniteUuid getUUID() {
+ public UUID getUUID() {
return uuid;
}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/SparseDistributedMatrixStorage.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/SparseDistributedMatrixStorage.java b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/SparseDistributedMatrixStorage.java
index 95852b7..c40e73d 100644
--- a/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/SparseDistributedMatrixStorage.java
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/math/impls/storage/matrix/SparseDistributedMatrixStorage.java
@@ -24,6 +24,7 @@ import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Map;
import java.util.Set;
+import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.apache.ignite.IgniteCache;
@@ -33,7 +34,6 @@ import org.apache.ignite.cache.CacheMode;
import org.apache.ignite.cache.CachePeekMode;
import org.apache.ignite.cache.CacheWriteSynchronizationMode;
import org.apache.ignite.configuration.CacheConfiguration;
-import org.apache.ignite.lang.IgniteUuid;
import org.apache.ignite.ml.math.MatrixStorage;
import org.apache.ignite.ml.math.StorageConstants;
import org.apache.ignite.ml.math.distributed.CacheUtils;
@@ -57,7 +57,7 @@ public class SparseDistributedMatrixStorage extends CacheUtils implements Matrix
/** Random or sequential access mode. */
private int acsMode;
/** Matrix uuid. */
- private IgniteUuid uuid;
+ private UUID uuid;
/** Actual distributed storage. */
private IgniteCache<
@@ -91,7 +91,7 @@ public class SparseDistributedMatrixStorage extends CacheUtils implements Matrix
cache = newCache();
- uuid = IgniteUuid.randomUuid();
+ uuid = UUID.randomUUID();
}
/**
@@ -115,6 +115,9 @@ public class SparseDistributedMatrixStorage extends CacheUtils implements Matrix
// Cache is partitioned.
cfg.setCacheMode(CacheMode.PARTITIONED);
+ // TODO: Possibly we should add a fix of https://issues.apache.org/jira/browse/IGNITE-6862 here commented below.
+ // cfg.setReadFromBackup(false);
+
// Random cache name.
cfg.setName(CACHE_NAME);
@@ -205,7 +208,7 @@ public class SparseDistributedMatrixStorage extends CacheUtils implements Matrix
/** Build cache key for row/column. */
public RowColMatrixKey getCacheKey(int idx) {
- return new SparseMatrixKey(idx, uuid, null);
+ return new SparseMatrixKey(idx, uuid, idx);
}
/** {@inheritDoc} */
@@ -239,7 +242,7 @@ public class SparseDistributedMatrixStorage extends CacheUtils implements Matrix
cols = in.readInt();
acsMode = in.readInt();
stoMode = in.readInt();
- uuid = (IgniteUuid)in.readObject();
+ uuid = (UUID)in.readObject();
cache = ignite().getOrCreateCache(in.readUTF());
}
@@ -304,7 +307,7 @@ public class SparseDistributedMatrixStorage extends CacheUtils implements Matrix
}
/** */
- public IgniteUuid getUUID() {
+ public UUID getUUID() {
return uuid;
}
@@ -312,7 +315,7 @@ public class SparseDistributedMatrixStorage extends CacheUtils implements Matrix
@Override public Set<RowColMatrixKey> getAllKeys() {
int range = stoMode == ROW_STORAGE_MODE ? rows : cols;
- return IntStream.range(0, range).mapToObj(i -> new SparseMatrixKey(i, getUUID(), null)).collect(Collectors.toSet());
+ return IntStream.range(0, range).mapToObj(i -> new SparseMatrixKey(i, getUUID(), i)).collect(Collectors.toSet());
}
/** {@inheritDoc} */
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVector.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVector.java b/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVector.java
new file mode 100644
index 0000000..51b973a
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVector.java
@@ -0,0 +1,63 @@
+/*
+ * 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.ignite.ml.structures;
+
+import org.apache.ignite.ml.math.Vector;
+
+/**
+ * Class for vector with label.
+ *
+ * @param <V> Some class extending {@link Vector}.
+ * @param <T> Type of label.
+ */
+public class LabeledVector<V extends Vector, T> {
+ /** Vector. */
+ private final V vector;
+
+ /** Label. */
+ private final T lb;
+
+ /**
+ * Construct labeled vector.
+ *
+ * @param vector Vector.
+ * @param lb Label.
+ */
+ public LabeledVector(V vector, T lb) {
+ this.vector = vector;
+ this.lb = lb;
+ }
+
+ /**
+ * Get the vector.
+ *
+ * @return Vector.
+ */
+ public V vector() {
+ return vector;
+ }
+
+ /**
+ * Get the label.
+ *
+ * @return Label.
+ */
+ public T label() {
+ return lb;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVectorDouble.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVectorDouble.java b/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVectorDouble.java
new file mode 100644
index 0000000..4ef9eae
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/structures/LabeledVectorDouble.java
@@ -0,0 +1,46 @@
+/*
+ * 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.ignite.ml.structures;
+
+import org.apache.ignite.ml.math.Vector;
+
+/**
+ * Labeled vector specialized to double label.
+ *
+ * @param <V> Type of vector.
+ */
+public class LabeledVectorDouble<V extends Vector> extends LabeledVector<V, Double> {
+ /**
+ * Construct LabeledVectorDouble.
+ *
+ * @param vector Vector.
+ * @param lb Label.
+ */
+ public LabeledVectorDouble(V vector, Double lb) {
+ super(vector, lb);
+ }
+
+ /**
+ * Get label as double.
+ *
+ * @return label as double.
+ */
+ public double doubleLabel() {
+ return label();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/structures/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/structures/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/structures/package-info.java
new file mode 100644
index 0000000..ec9d79e
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/structures/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Contains some utility structures.
+ */
+package org.apache.ignite.ml.structures;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalRegionInfo.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalRegionInfo.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalRegionInfo.java
new file mode 100644
index 0000000..3ae474e
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalRegionInfo.java
@@ -0,0 +1,72 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.BitSet;
+
+/**
+ * Information about categorical region.
+ */
+public class CategoricalRegionInfo extends RegionInfo implements Externalizable {
+ /**
+ * Bitset representing categories of this region.
+ */
+ private BitSet cats;
+
+ /**
+ * @param impurity Impurity of region.
+ * @param cats Bitset representing categories of this region.
+ */
+ public CategoricalRegionInfo(double impurity, BitSet cats) {
+ super(impurity);
+
+ this.cats = cats;
+ }
+
+ /**
+ * No-op constructor for serialization/deserialization.
+ */
+ public CategoricalRegionInfo() {
+ // No-op
+ }
+
+ /**
+ * Get bitset representing categories of this region.
+ *
+ * @return Bitset representing categories of this region.
+ */
+ public BitSet cats() {
+ return cats;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ super.writeExternal(out);
+ out.writeObject(cats);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ super.readExternal(in);
+ cats = (BitSet)in.readObject();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalSplitInfo.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalSplitInfo.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalSplitInfo.java
new file mode 100644
index 0000000..94cb1e8
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/CategoricalSplitInfo.java
@@ -0,0 +1,68 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.util.BitSet;
+import org.apache.ignite.ml.trees.nodes.CategoricalSplitNode;
+import org.apache.ignite.ml.trees.nodes.SplitNode;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.SplitInfo;
+
+/**
+ * Information about split of categorical feature.
+ *
+ * @param <D> Class representing information of left and right subregions.
+ */
+public class CategoricalSplitInfo<D extends RegionInfo> extends SplitInfo<D> {
+ /** Bitset indicating which vectors are assigned to left subregion. */
+ private final BitSet bs;
+
+ /**
+ * @param regionIdx Index of region which is split.
+ * @param leftData Data of left subregion.
+ * @param rightData Data of right subregion.
+ * @param bs Bitset indicating which vectors are assigned to left subregion.
+ */
+ public CategoricalSplitInfo(int regionIdx, D leftData, D rightData,
+ BitSet bs) {
+ super(regionIdx, leftData, rightData);
+ this.bs = bs;
+ }
+
+ /** {@inheritDoc} */
+ @Override public SplitNode createSplitNode(int featureIdx) {
+ return new CategoricalSplitNode(featureIdx, bs);
+ }
+
+ /**
+ * Get bitset indicating which vectors are assigned to left subregion.
+ */
+ public BitSet bitSet() {
+ return bs;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "CategoricalSplitInfo [" +
+ "infoGain=" + infoGain +
+ ", regionIdx=" + regionIdx +
+ ", leftData=" + leftData +
+ ", bs=" + bs +
+ ", rightData=" + rightData +
+ ']';
+ }
+}
[2/4] ignite git commit: IGNITE-5218: First version of decision
trees. This closes #2936
Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/FeaturesCache.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/FeaturesCache.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/FeaturesCache.java
new file mode 100644
index 0000000..fcc1f16
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/FeaturesCache.java
@@ -0,0 +1,151 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.caches;
+
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cache.affinity.AffinityKeyMapped;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.ml.math.functions.IgniteBiFunction;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+
+/**
+ * Cache storing features for {@link ColumnDecisionTreeTrainer}.
+ */
+public class FeaturesCache {
+ /**
+ * Name of cache which is used for storing features for {@link ColumnDecisionTreeTrainer}.
+ */
+ public static final String COLUMN_DECISION_TREE_TRAINER_FEATURES_CACHE_NAME = "COLUMN_DECISION_TREE_TRAINER_FEATURES_CACHE_NAME";
+
+ /**
+ * Key of features cache.
+ */
+ public static class FeatureKey {
+ /** Column key of cache used as input for {@link ColumnDecisionTreeTrainer}. */
+ @AffinityKeyMapped
+ private Object parentColKey;
+
+ /** Index of feature. */
+ private final int featureIdx;
+
+ /** UUID of training. */
+ private final UUID trainingUUID;
+
+ /**
+ * Construct FeatureKey.
+ *
+ * @param featureIdx Feature index.
+ * @param trainingUUID UUID of training.
+ * @param parentColKey Column key of cache used as input.
+ */
+ public FeatureKey(int featureIdx, UUID trainingUUID, Object parentColKey) {
+ this.parentColKey = parentColKey;
+ this.featureIdx = featureIdx;
+ this.trainingUUID = trainingUUID;
+ this.parentColKey = parentColKey;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ FeatureKey key = (FeatureKey)o;
+
+ if (featureIdx != key.featureIdx)
+ return false;
+ return trainingUUID != null ? trainingUUID.equals(key.trainingUUID) : key.trainingUUID == null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int res = trainingUUID != null ? trainingUUID.hashCode() : 0;
+ res = 31 * res + featureIdx;
+ return res;
+ }
+ }
+
+ /**
+ * Create new projections cache for ColumnDecisionTreeTrainer if needed.
+ *
+ * @param ignite Ignite instance.
+ */
+ public static IgniteCache<FeatureKey, double[]> getOrCreate(Ignite ignite) {
+ CacheConfiguration<FeatureKey, double[]> cfg = new CacheConfiguration<>();
+
+ // Write to primary.
+ cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+ // Atomic transactions only.
+ cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+ // No eviction.
+ cfg.setEvictionPolicy(null);
+
+ // No copying of values.
+ cfg.setCopyOnRead(false);
+
+ // Cache is partitioned.
+ cfg.setCacheMode(CacheMode.PARTITIONED);
+
+ cfg.setOnheapCacheEnabled(true);
+
+ cfg.setBackups(0);
+
+ cfg.setName(COLUMN_DECISION_TREE_TRAINER_FEATURES_CACHE_NAME);
+
+ return ignite.getOrCreateCache(cfg);
+ }
+
+ /**
+ * Construct FeatureKey from index, uuid and affinity key.
+ *
+ * @param idx Feature index.
+ * @param uuid UUID of training.
+ * @param aff Affinity key.
+ * @return FeatureKey.
+ */
+ public static FeatureKey getFeatureCacheKey(int idx, UUID uuid, Object aff) {
+ return new FeatureKey(idx, uuid, aff);
+ }
+
+ /**
+ * Clear all data from features cache related to given training.
+ *
+ * @param featuresCnt Count of features.
+ * @param affinity Affinity function.
+ * @param uuid Training uuid.
+ * @param ignite Ignite instance.
+ */
+ public static void clear(int featuresCnt, IgniteBiFunction<Integer, Ignite, Object> affinity, UUID uuid,
+ Ignite ignite) {
+ Set<FeatureKey> toRmv = IntStream.range(0, featuresCnt).boxed().map(fIdx -> getFeatureCacheKey(fIdx, uuid, affinity.apply(fIdx, ignite))).collect(Collectors.toSet());
+
+ getOrCreate(ignite).removeAll(toRmv);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ProjectionsCache.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ProjectionsCache.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ProjectionsCache.java
new file mode 100644
index 0000000..29cf6b4
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ProjectionsCache.java
@@ -0,0 +1,284 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.caches;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.PrimitiveIterator;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cache.affinity.Affinity;
+import org.apache.ignite.cache.affinity.AffinityKeyMapped;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.functions.IgniteBiFunction;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+import org.apache.ignite.ml.trees.trainers.columnbased.RegionProjection;
+
+/**
+ * Cache used for storing data of region projections on features.
+ */
+public class ProjectionsCache {
+ /**
+ * Name of cache which is used for storing data of region projections on features of {@link
+ * ColumnDecisionTreeTrainer}.
+ */
+ public static final String CACHE_NAME = "COLUMN_DECISION_TREE_TRAINER_PROJECTIONS_CACHE_NAME";
+
+ /**
+ * Key of region projections cache.
+ */
+ public static class RegionKey {
+ /** Column key of cache used as input for {@link ColumnDecisionTreeTrainer}. */
+ @AffinityKeyMapped
+ private final Object parentColKey;
+
+ /** Feature index. */
+ private final int featureIdx;
+
+ /** Region index. */
+ private final int regBlockIdx;
+
+ /** Training UUID. */
+ private final UUID trainingUUID;
+
+ /**
+ * Construct a RegionKey from feature index, index of block, key of column in input cache and UUID of training.
+ *
+ * @param featureIdx Feature index.
+ * @param regBlockIdx Index of block.
+ * @param parentColKey Key of column in input cache.
+ * @param trainingUUID UUID of training.
+ */
+ public RegionKey(int featureIdx, int regBlockIdx, Object parentColKey, UUID trainingUUID) {
+ this.featureIdx = featureIdx;
+ this.regBlockIdx = regBlockIdx;
+ this.trainingUUID = trainingUUID;
+ this.parentColKey = parentColKey;
+ }
+
+ /**
+ * Feature index.
+ *
+ * @return Feature index.
+ */
+ public int featureIdx() {
+ return featureIdx;
+ }
+
+ /**
+ * Region block index.
+ *
+ * @return Region block index.
+ */
+ public int regionBlockIndex() {
+ return regBlockIdx;
+ }
+
+ /**
+ * UUID of training.
+ *
+ * @return UUID of training.
+ */
+ public UUID trainingUUID() {
+ return trainingUUID;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ RegionKey key = (RegionKey)o;
+
+ if (featureIdx != key.featureIdx)
+ return false;
+ if (regBlockIdx != key.regBlockIdx)
+ return false;
+ return trainingUUID != null ? trainingUUID.equals(key.trainingUUID) : key.trainingUUID == null;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int res = trainingUUID != null ? trainingUUID.hashCode() : 0;
+ res = 31 * res + featureIdx;
+ res = 31 * res + regBlockIdx;
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "RegionKey [" +
+ "parentColKey=" + parentColKey +
+ ", featureIdx=" + featureIdx +
+ ", regBlockIdx=" + regBlockIdx +
+ ", trainingUUID=" + trainingUUID +
+ ']';
+ }
+ }
+
+ /**
+ * Affinity service for region projections cache.
+ *
+ * @return Affinity service for region projections cache.
+ */
+ public static Affinity<RegionKey> affinity() {
+ return Ignition.localIgnite().affinity(CACHE_NAME);
+ }
+
+ /**
+ * Get or create region projections cache.
+ *
+ * @param ignite Ignite instance.
+ * @return Region projections cache.
+ */
+ public static IgniteCache<RegionKey, List<RegionProjection>> getOrCreate(Ignite ignite) {
+ CacheConfiguration<RegionKey, List<RegionProjection>> cfg = new CacheConfiguration<>();
+
+ // Write to primary.
+ cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+ // Atomic transactions only.
+ cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+ // No eviction.
+ cfg.setEvictionPolicy(null);
+
+ // No copying of values.
+ cfg.setCopyOnRead(false);
+
+ // Cache is partitioned.
+ cfg.setCacheMode(CacheMode.PARTITIONED);
+
+ cfg.setBackups(0);
+
+ cfg.setOnheapCacheEnabled(true);
+
+ cfg.setName(CACHE_NAME);
+
+ return ignite.getOrCreateCache(cfg);
+ }
+
+ /**
+ * Get region projections in the form of map (regionIndex -> regionProjections).
+ *
+ * @param featureIdx Feature index.
+ * @param maxDepth Max depth of decision tree.
+ * @param regionIndexes Indexes of regions for which we want get projections.
+ * @param blockSize Size of regions block.
+ * @param affinity Affinity function.
+ * @param trainingUUID UUID of training.
+ * @param ignite Ignite instance.
+ * @return Region projections in the form of map (regionIndex -> regionProjections).
+ */
+ public static Map<Integer, RegionProjection> projectionsOfRegions(int featureIdx, int maxDepth,
+ IntStream regionIndexes, int blockSize, IgniteFunction<Integer, Object> affinity, UUID trainingUUID,
+ Ignite ignite) {
+ HashMap<Integer, RegionProjection> regsForSearch = new HashMap<>();
+ IgniteCache<RegionKey, List<RegionProjection>> cache = getOrCreate(ignite);
+
+ PrimitiveIterator.OfInt itr = regionIndexes.iterator();
+
+ int curBlockIdx = -1;
+ List<RegionProjection> block = null;
+
+ Object affinityKey = affinity.apply(featureIdx);
+
+ while (itr.hasNext()) {
+ int i = itr.nextInt();
+
+ int blockIdx = i / blockSize;
+
+ if (blockIdx != curBlockIdx) {
+ block = cache.localPeek(key(featureIdx, blockIdx, affinityKey, trainingUUID));
+ curBlockIdx = blockIdx;
+ }
+
+ if (block == null)
+ throw new IllegalStateException("Unexpected null block at index " + i);
+
+ RegionProjection reg = block.get(i % blockSize);
+
+ if (reg.depth() < maxDepth)
+ regsForSearch.put(i, reg);
+ }
+
+ return regsForSearch;
+ }
+
+ /**
+ * Returns projections of regions on given feature filtered by maximal depth in the form of (region index -> region projection).
+ *
+ * @param featureIdx Feature index.
+ * @param maxDepth Maximal depth of the tree.
+ * @param regsCnt Count of regions.
+ * @param blockSize Size of regions blocks.
+ * @param affinity Affinity function.
+ * @param trainingUUID UUID of training.
+ * @param ignite Ignite instance.
+ * @return Projections of regions on given feature filtered by maximal depth in the form of (region index -> region projection).
+ */
+ public static Map<Integer, RegionProjection> projectionsOfFeature(int featureIdx, int maxDepth, int regsCnt,
+ int blockSize, IgniteFunction<Integer, Object> affinity, UUID trainingUUID, Ignite ignite) {
+ return projectionsOfRegions(featureIdx, maxDepth, IntStream.range(0, regsCnt), blockSize, affinity, trainingUUID, ignite);
+ }
+
+ /**
+ * Construct key for projections cache.
+ *
+ * @param featureIdx Feature index.
+ * @param regBlockIdx Region block index.
+ * @param parentColKey Column key of cache used as input for {@link ColumnDecisionTreeTrainer}.
+ * @param uuid UUID of training.
+ * @return Key for projections cache.
+ */
+ public static RegionKey key(int featureIdx, int regBlockIdx, Object parentColKey, UUID uuid) {
+ return new RegionKey(featureIdx, regBlockIdx, parentColKey, uuid);
+ }
+
+ /**
+ * Clear data from projections cache related to given training.
+ *
+ * @param featuresCnt Features count.
+ * @param regs Regions count.
+ * @param aff Affinity function.
+ * @param uuid UUID of training.
+ * @param ignite Ignite instance.
+ */
+ public static void clear(int featuresCnt, int regs, IgniteBiFunction<Integer, Ignite, Object> aff, UUID uuid,
+ Ignite ignite) {
+ Set<RegionKey> toRmv = IntStream.range(0, featuresCnt).boxed().
+ flatMap(fIdx -> IntStream.range(0, regs).boxed().map(reg -> new IgniteBiTuple<>(fIdx, reg))).
+ map(t -> key(t.get1(), t.get2(), aff.apply(t.get1(), ignite), uuid)).
+ collect(Collectors.toSet());
+
+ getOrCreate(ignite).removeAll(toRmv);
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/SplitCache.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/SplitCache.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/SplitCache.java
new file mode 100644
index 0000000..ecbc861
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/SplitCache.java
@@ -0,0 +1,206 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.caches;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import javax.cache.Cache;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.cache.affinity.Affinity;
+import org.apache.ignite.cache.affinity.AffinityKeyMapped;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.internal.processors.cache.CacheEntryImpl;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.functions.IgniteBiFunction;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+
+/**
+ * Class for working with cache used for storing of best splits during training with {@link ColumnDecisionTreeTrainer}.
+ */
+public class SplitCache {
+ /** Name of splits cache. */
+ public static final String CACHE_NAME = "COLUMN_DECISION_TREE_TRAINER_SPLIT_CACHE_NAME";
+
+ /**
+ * Class used for keys in the splits cache.
+ */
+ public static class SplitKey {
+ /** UUID of current training. */
+ private final UUID trainingUUID;
+
+ /** Affinity key of input data. */
+ @AffinityKeyMapped
+ private final Object parentColKey;
+
+ /** Index of feature by which the split is made. */
+ private final int featureIdx;
+
+ /**
+ * Construct SplitKey.
+ *
+ * @param trainingUUID UUID of the training.
+ * @param parentColKey Affinity key used to ensure that cache entry for given feature will be on the same node
+ * as column with that feature in input.
+ * @param featureIdx Feature index.
+ */
+ public SplitKey(UUID trainingUUID, Object parentColKey, int featureIdx) {
+ this.trainingUUID = trainingUUID;
+ this.featureIdx = featureIdx;
+ this.parentColKey = parentColKey;
+ }
+
+ /** Get UUID of current training. */
+ public UUID trainingUUID() {
+ return trainingUUID;
+ }
+
+ /**
+ * Get feature index.
+ *
+ * @return Feature index.
+ */
+ public int featureIdx() {
+ return featureIdx;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ SplitKey splitKey = (SplitKey)o;
+
+ if (featureIdx != splitKey.featureIdx)
+ return false;
+ return trainingUUID != null ? trainingUUID.equals(splitKey.trainingUUID) : splitKey.trainingUUID == null;
+
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int res = trainingUUID != null ? trainingUUID.hashCode() : 0;
+ res = 31 * res + featureIdx;
+ return res;
+ }
+ }
+
+ /**
+ * Construct the key for splits cache.
+ *
+ * @param featureIdx Feature index.
+ * @param parentColKey Affinity key used to ensure that cache entry for given feature will be on the same node as
+ * column with that feature in input.
+ * @param uuid UUID of current training.
+ * @return Key for splits cache.
+ */
+ public static SplitKey key(int featureIdx, Object parentColKey, UUID uuid) {
+ return new SplitKey(uuid, parentColKey, featureIdx);
+ }
+
+ /**
+ * Get or create splits cache.
+ *
+ * @param ignite Ignite instance.
+ * @return Splits cache.
+ */
+ public static IgniteCache<SplitKey, IgniteBiTuple<Integer, Double>> getOrCreate(Ignite ignite) {
+ CacheConfiguration<SplitKey, IgniteBiTuple<Integer, Double>> cfg = new CacheConfiguration<>();
+
+ // Write to primary.
+ cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.PRIMARY_SYNC);
+
+ // Atomic transactions only.
+ cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+ // No eviction.
+ cfg.setEvictionPolicy(null);
+
+ // No copying of values.
+ cfg.setCopyOnRead(false);
+
+ // Cache is partitioned.
+ cfg.setCacheMode(CacheMode.PARTITIONED);
+
+ cfg.setBackups(0);
+
+ cfg.setOnheapCacheEnabled(true);
+
+ cfg.setName(CACHE_NAME);
+
+ return ignite.getOrCreateCache(cfg);
+ }
+
+ /**
+ * Affinity function used in splits cache.
+ *
+ * @return Affinity function used in splits cache.
+ */
+ public static Affinity<SplitKey> affinity() {
+ return Ignition.localIgnite().affinity(CACHE_NAME);
+ }
+
+ /**
+ * Returns local entries for keys corresponding to {@code featureIndexes}.
+ *
+ * @param featureIndexes Index of features.
+ * @param affinity Affinity function.
+ * @param trainingUUID UUID of training.
+ * @return local entries for keys corresponding to {@code featureIndexes}.
+ */
+ public static Iterable<Cache.Entry<SplitKey, IgniteBiTuple<Integer, Double>>> localEntries(
+ Set<Integer> featureIndexes,
+ IgniteBiFunction<Integer, Ignite, Object> affinity,
+ UUID trainingUUID) {
+ Ignite ignite = Ignition.localIgnite();
+ Set<SplitKey> keys = featureIndexes.stream().map(fIdx -> new SplitKey(trainingUUID, affinity.apply(fIdx, ignite), fIdx)).collect(Collectors.toSet());
+
+ Collection<SplitKey> locKeys = affinity().mapKeysToNodes(keys).getOrDefault(ignite.cluster().localNode(), Collections.emptyList());
+
+ return () -> {
+ Function<SplitKey, Cache.Entry<SplitKey, IgniteBiTuple<Integer, Double>>> f = k -> (new CacheEntryImpl<>(k, getOrCreate(ignite).localPeek(k)));
+ return locKeys.stream().map(f).iterator();
+ };
+ }
+
+ /**
+ * Clears data related to current training from splits cache related to given training.
+ *
+ * @param featuresCnt Count of features.
+ * @param affinity Affinity function.
+ * @param uuid UUID of the given training.
+ * @param ignite Ignite instance.
+ */
+ public static void clear(int featuresCnt, IgniteBiFunction<Integer, Ignite, Object> affinity, UUID uuid,
+ Ignite ignite) {
+ Set<SplitKey> toRmv = IntStream.range(0, featuresCnt).boxed().map(fIdx -> new SplitKey(uuid, affinity.apply(fIdx, ignite), fIdx)).collect(Collectors.toSet());
+
+ getOrCreate(ignite).removeAll(toRmv);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/ContinuousSplitCalculators.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/ContinuousSplitCalculators.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/ContinuousSplitCalculators.java
new file mode 100644
index 0000000..9fd4c66
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/ContinuousSplitCalculators.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.contsplitcalcs;
+
+import org.apache.ignite.Ignite;
+import org.apache.ignite.ml.math.functions.IgniteCurriedBiFunction;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainerInput;
+
+/** Continuous Split Calculators. */
+public class ContinuousSplitCalculators {
+ /** Variance split calculator. */
+ public static IgniteFunction<ColumnDecisionTreeTrainerInput, VarianceSplitCalculator> VARIANCE = input ->
+ new VarianceSplitCalculator();
+
+ /** Gini split calculator. */
+ public static IgniteCurriedBiFunction<Ignite, ColumnDecisionTreeTrainerInput, GiniSplitCalculator> GINI = ignite ->
+ input -> new GiniSplitCalculator(input.labels(ignite));
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/GiniSplitCalculator.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/GiniSplitCalculator.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/GiniSplitCalculator.java
new file mode 100644
index 0000000..259c84c
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/GiniSplitCalculator.java
@@ -0,0 +1,234 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.contsplitcalcs;
+
+import it.unimi.dsi.fastutil.doubles.Double2IntArrayMap;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.PrimitiveIterator;
+import java.util.stream.DoubleStream;
+import org.apache.ignite.ml.trees.ContinuousRegionInfo;
+import org.apache.ignite.ml.trees.ContinuousSplitCalculator;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.ContinuousSplitInfo;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.SplitInfo;
+
+/**
+ * Calculator for Gini impurity.
+ */
+public class GiniSplitCalculator implements ContinuousSplitCalculator<GiniSplitCalculator.GiniData> {
+ /** Mapping assigning index to each member value */
+ private final Map<Double, Integer> mapping = new Double2IntArrayMap();
+
+ /**
+ * Create Gini split calculator from labels.
+ *
+ * @param labels Labels.
+ */
+ public GiniSplitCalculator(double[] labels) {
+ int i = 0;
+
+ for (double label : labels) {
+ if (!mapping.containsKey(label)) {
+ mapping.put(label, i);
+ i++;
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public GiniData calculateRegionInfo(DoubleStream s, int l) {
+ PrimitiveIterator.OfDouble itr = s.iterator();
+
+ Map<Double, Integer> m = new HashMap<>();
+
+ int size = 0;
+
+ while (itr.hasNext()) {
+ size++;
+ m.compute(itr.next(), (a, i) -> i != null ? i + 1 : 1);
+ }
+
+ double c2 = m.values().stream().mapToDouble(v -> v * v).sum();
+
+ int[] cnts = new int[mapping.size()];
+
+ m.forEach((key, value) -> cnts[mapping.get(key)] = value);
+
+ return new GiniData(size != 0 ? 1 - c2 / (size * size) : 0.0, size, cnts, c2);
+ }
+
+ /** {@inheritDoc} */
+ @Override public SplitInfo<GiniData> splitRegion(Integer[] s, double[] values, double[] labels, int regionIdx,
+ GiniData d) {
+ int size = d.getSize();
+
+ double lg = 0.0;
+ double rg = d.impurity();
+
+ double lc2 = 0.0;
+ double rc2 = d.c2;
+ int lSize = 0;
+
+ double minImpurity = d.impurity() * size;
+ double curThreshold;
+ double curImpurity;
+ double threshold = Double.NEGATIVE_INFINITY;
+
+ int i = 0;
+ int nextIdx = s[0];
+ i++;
+ double[] lrImps = new double[] {0.0, d.impurity(), lc2, rc2};
+
+ int[] lMapCur = new int[d.counts().length];
+ int[] rMapCur = new int[d.counts().length];
+
+ System.arraycopy(d.counts(), 0, rMapCur, 0, d.counts().length);
+
+ int[] lMap = new int[d.counts().length];
+ int[] rMap = new int[d.counts().length];
+
+ System.arraycopy(d.counts(), 0, rMap, 0, d.counts().length);
+
+ do {
+ // Process all values equal to prev.
+ while (i < s.length) {
+ moveLeft(labels[nextIdx], i, size - i, lMapCur, rMapCur, lrImps);
+ curImpurity = (i * lrImps[0] + (size - i) * lrImps[1]);
+ curThreshold = values[nextIdx];
+
+ if (values[nextIdx] != values[(nextIdx = s[i++])]) {
+ if (curImpurity < minImpurity) {
+ lSize = i - 1;
+
+ lg = lrImps[0];
+ rg = lrImps[1];
+
+ lc2 = lrImps[2];
+ rc2 = lrImps[3];
+
+ System.arraycopy(lMapCur, 0, lMap, 0, lMapCur.length);
+ System.arraycopy(rMapCur, 0, rMap, 0, rMapCur.length);
+
+ minImpurity = curImpurity;
+ threshold = curThreshold;
+ }
+
+ break;
+ }
+ }
+ }
+ while (i < s.length - 1);
+
+ if (lSize == size || lSize == 0)
+ return null;
+
+ GiniData lData = new GiniData(lg, lSize, lMap, lc2);
+ int rSize = size - lSize;
+ GiniData rData = new GiniData(rg, rSize, rMap, rc2);
+
+ return new ContinuousSplitInfo<>(regionIdx, threshold, lData, rData);
+ }
+
+ /**
+ * Add point to the left interval and remove it from the right interval and calculate necessary statistics on
+ * intervals with new bounds.
+ */
+ private void moveLeft(double x, int lSize, int rSize, int[] lMap, int[] rMap, double[] data) {
+ double lc2 = data[2];
+ double rc2 = data[3];
+
+ Integer idx = mapping.get(x);
+
+ int cxl = lMap[idx];
+ int cxr = rMap[idx];
+
+ lc2 += 2 * cxl + 1;
+ rc2 -= 2 * cxr - 1;
+
+ lMap[idx] += 1;
+ rMap[idx] -= 1;
+
+ data[0] = 1 - lc2 / (lSize * lSize);
+ data[1] = 1 - rc2 / (rSize * rSize);
+
+ data[2] = lc2;
+ data[3] = rc2;
+ }
+
+ /**
+ * Data used for gini impurity calculations.
+ */
+ public static class GiniData extends ContinuousRegionInfo {
+ /** Sum of squares of counts of each label. */
+ private double c2;
+
+ /** Counts of each label. On i-th position there is count of label which is mapped to index i. */
+ private int[] m;
+
+ /**
+ * Create Gini data.
+ *
+ * @param impurity Impurity (i.e. Gini impurity).
+ * @param size Count of samples.
+ * @param m Counts of each label.
+ * @param c2 Sum of squares of counts of each label.
+ */
+ public GiniData(double impurity, int size, int[] m, double c2) {
+ super(impurity, size);
+ this.m = m;
+ this.c2 = c2;
+ }
+
+ /**
+ * No-op constructor for serialization/deserialization..
+ */
+ public GiniData() {
+ // No-op.
+ }
+
+ /** Get counts of each label. */
+ public int[] counts() {
+ return m;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ super.writeExternal(out);
+ out.writeDouble(c2);
+ out.writeInt(m.length);
+ for (int i : m)
+ out.writeInt(i);
+
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ super.readExternal(in);
+
+ c2 = in.readDouble();
+ int size = in.readInt();
+ m = new int[size];
+
+ for (int i = 0; i < size; i++)
+ m[i] = in.readInt();
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/VarianceSplitCalculator.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/VarianceSplitCalculator.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/VarianceSplitCalculator.java
new file mode 100644
index 0000000..66c54f2
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/VarianceSplitCalculator.java
@@ -0,0 +1,179 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.contsplitcalcs;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.PrimitiveIterator;
+import java.util.stream.DoubleStream;
+import org.apache.ignite.ml.trees.ContinuousRegionInfo;
+import org.apache.ignite.ml.trees.ContinuousSplitCalculator;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.ContinuousSplitInfo;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.SplitInfo;
+
+/**
+ * Calculator of variance in a given region.
+ */
+public class VarianceSplitCalculator implements ContinuousSplitCalculator<VarianceSplitCalculator.VarianceData> {
+ /**
+ * Data used in variance calculations.
+ */
+ public static class VarianceData extends ContinuousRegionInfo {
+ /** Mean value in a given region. */
+ double mean;
+
+ /**
+ * @param var Variance in this region.
+ * @param size Size of data for which variance is calculated.
+ * @param mean Mean value in this region.
+ */
+ public VarianceData(double var, int size, double mean) {
+ super(var, size);
+ this.mean = mean;
+ }
+
+ /**
+ * No-op constructor. For serialization/deserialization.
+ */
+ public VarianceData() {
+ // No-op.
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ super.writeExternal(out);
+ out.writeDouble(mean);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ super.readExternal(in);
+ mean = in.readDouble();
+ }
+
+ /**
+ * Returns mean.
+ */
+ public double mean() {
+ return mean;
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public VarianceData calculateRegionInfo(DoubleStream s, int size) {
+ PrimitiveIterator.OfDouble itr = s.iterator();
+ int i = 0;
+
+ double mean = 0.0;
+ double m2 = 0.0;
+
+ // Here we calculate variance and mean by incremental computation.
+ while (itr.hasNext()) {
+ i++;
+ double x = itr.next();
+ double delta = x - mean;
+ mean += delta / i;
+ double delta2 = x - mean;
+ m2 += delta * delta2;
+ }
+
+ return new VarianceData(m2 / i, size, mean);
+ }
+
+ /** {@inheritDoc} */
+ @Override public SplitInfo<VarianceData> splitRegion(Integer[] s, double[] values, double[] labels, int regionIdx,
+ VarianceData d) {
+ int size = d.getSize();
+
+ double lm2 = 0.0;
+ double rm2 = d.impurity() * size;
+ int lSize = size;
+
+ double lMean = 0.0;
+ double rMean = d.mean;
+
+ double minImpurity = d.impurity() * size;
+ double curThreshold;
+ double curImpurity;
+ double threshold = Double.NEGATIVE_INFINITY;
+
+ int i = 0;
+ int nextIdx = s[0];
+ i++;
+ double[] lrImps = new double[] {lm2, rm2, lMean, rMean};
+
+ do {
+ // Process all values equal to prev.
+ while (i < s.length) {
+ moveLeft(labels[nextIdx], lrImps[2], i, lrImps[0], lrImps[3], size - i, lrImps[1], lrImps);
+ curImpurity = (lrImps[0] + lrImps[1]);
+ curThreshold = values[nextIdx];
+
+ if (values[nextIdx] != values[(nextIdx = s[i++])]) {
+ if (curImpurity < minImpurity) {
+ lSize = i - 1;
+
+ lm2 = lrImps[0];
+ rm2 = lrImps[1];
+
+ lMean = lrImps[2];
+ rMean = lrImps[3];
+
+ minImpurity = curImpurity;
+ threshold = curThreshold;
+ }
+
+ break;
+ }
+ }
+ }
+ while (i < s.length - 1);
+
+ if (lSize == size)
+ return null;
+
+ VarianceData lData = new VarianceData(lm2 / (lSize != 0 ? lSize : 1), lSize, lMean);
+ int rSize = size - lSize;
+ VarianceData rData = new VarianceData(rm2 / (rSize != 0 ? rSize : 1), rSize, rMean);
+
+ return new ContinuousSplitInfo<>(regionIdx, threshold, lData, rData);
+ }
+
+ /**
+ * Add point to the left interval and remove it from the right interval and calculate necessary statistics on
+ * intervals with new bounds.
+ */
+ private void moveLeft(double x, double lMean, int lSize, double lm2, double rMean, int rSize, double rm2,
+ double[] data) {
+ // We add point to the left interval.
+ double lDelta = x - lMean;
+ double lMeanNew = lMean + lDelta / lSize;
+ double lm2New = lm2 + lDelta * (x - lMeanNew);
+
+ // We remove point from the right interval. lSize + 1 is the size of right interval before removal.
+ double rMeanNew = (rMean * (rSize + 1) - x) / rSize;
+ double rm2New = rm2 - (x - rMean) * (x - rMeanNew);
+
+ data[0] = lm2New;
+ data[1] = rm2New;
+
+ data[2] = lMeanNew;
+ data[3] = rMeanNew;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/package-info.java
new file mode 100644
index 0000000..08c8a75
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/contsplitcalcs/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Calculators of splits by continuous features.
+ */
+package org.apache.ignite.ml.trees.trainers.columnbased.contsplitcalcs;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/package-info.java
new file mode 100644
index 0000000..8523914
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Contains column based decision tree algorithms.
+ */
+package org.apache.ignite.ml.trees.trainers.columnbased;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/RegionCalculators.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/RegionCalculators.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/RegionCalculators.java
new file mode 100644
index 0000000..5c4b354
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/RegionCalculators.java
@@ -0,0 +1,85 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.regcalcs;
+
+import it.unimi.dsi.fastutil.doubles.Double2IntOpenHashMap;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.PrimitiveIterator;
+import java.util.stream.DoubleStream;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainerInput;
+
+/** Some commonly used functions for calculations of regions of space which correspond to decision tree leaf nodes. */
+public class RegionCalculators {
+ /** Mean value in the region. */
+ public static final IgniteFunction<DoubleStream, Double> MEAN = s -> s.average().orElse(0.0);
+
+ /** Most common value in the region. */
+ public static final IgniteFunction<DoubleStream, Double> MOST_COMMON =
+ s -> {
+ PrimitiveIterator.OfDouble itr = s.iterator();
+ Map<Double, Integer> voc = new HashMap<>();
+
+ while (itr.hasNext())
+ voc.compute(itr.next(), (d, i) -> i != null ? i + 1 : 0);
+
+ return voc.entrySet().stream().max(Comparator.comparing(Map.Entry::getValue)).map(Map.Entry::getKey).orElse(0.0);
+ };
+
+ /** Variance of a region. */
+ public static final IgniteFunction<ColumnDecisionTreeTrainerInput, IgniteFunction<DoubleStream, Double>> VARIANCE = input ->
+ s -> {
+ PrimitiveIterator.OfDouble itr = s.iterator();
+ int i = 0;
+
+ double mean = 0.0;
+ double m2 = 0.0;
+
+ while (itr.hasNext()) {
+ i++;
+ double x = itr.next();
+ double delta = x - mean;
+ mean += delta / i;
+ double delta2 = x - mean;
+ m2 += delta * delta2;
+ }
+
+ return i > 0 ? m2 / i : 0.0;
+ };
+
+ /** Gini impurity of a region. */
+ public static final IgniteFunction<ColumnDecisionTreeTrainerInput, IgniteFunction<DoubleStream, Double>> GINI = input ->
+ s -> {
+ PrimitiveIterator.OfDouble itr = s.iterator();
+
+ Double2IntOpenHashMap m = new Double2IntOpenHashMap();
+
+ int size = 0;
+
+ while (itr.hasNext()) {
+ size++;
+ m.compute(itr.next(), (a, i) -> i != null ? i + 1 : 1);
+ }
+
+ double c2 = m.values().stream().mapToDouble(v -> v * v).sum();
+
+ return size != 0 ? 1 - c2 / (size * size) : 0.0;
+ };
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/package-info.java
new file mode 100644
index 0000000..e8edd8f
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/regcalcs/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Region calculators.
+ */
+package org.apache.ignite.ml.trees.trainers.columnbased.regcalcs;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/CategoricalFeatureProcessor.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/CategoricalFeatureProcessor.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/CategoricalFeatureProcessor.java
new file mode 100644
index 0000000..9469768
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/CategoricalFeatureProcessor.java
@@ -0,0 +1,211 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.vectors;
+
+import com.zaxxer.sparsebits.SparseBitSet;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.trees.CategoricalRegionInfo;
+import org.apache.ignite.ml.trees.CategoricalSplitInfo;
+import org.apache.ignite.ml.trees.RegionInfo;
+import org.apache.ignite.ml.trees.trainers.columnbased.RegionProjection;
+
+import static org.apache.ignite.ml.trees.trainers.columnbased.vectors.FeatureVectorProcessorUtils.splitByBitSet;
+
+/**
+ * Categorical feature vector processor implementation used by {@see ColumnDecisionTreeTrainer}.
+ */
+public class CategoricalFeatureProcessor
+ implements FeatureProcessor<CategoricalRegionInfo, CategoricalSplitInfo<CategoricalRegionInfo>> {
+ /** Count of categories for this feature. */
+ private final int catsCnt;
+
+ /** Function for calculating impurity of a given region of points. */
+ private final IgniteFunction<DoubleStream, Double> calc;
+
+ /**
+ * @param calc Function for calculating impurity of a given region of points.
+ * @param catsCnt Number of categories.
+ */
+ public CategoricalFeatureProcessor(IgniteFunction<DoubleStream, Double> calc, int catsCnt) {
+ this.calc = calc;
+ this.catsCnt = catsCnt;
+ }
+
+ /** */
+ private SplitInfo<CategoricalRegionInfo> split(BitSet leftCats, int intervalIdx, Map<Integer, Integer> mapping,
+ Integer[] sampleIndexes, double[] values, double[] labels, double impurity) {
+ Map<Boolean, List<Integer>> leftRight = Arrays.stream(sampleIndexes).
+ collect(Collectors.partitioningBy((smpl) -> leftCats.get(mapping.get((int)values[smpl]))));
+
+ List<Integer> left = leftRight.get(true);
+ int leftSize = left.size();
+ double leftImpurity = calc.apply(left.stream().mapToDouble(s -> labels[s]));
+
+ List<Integer> right = leftRight.get(false);
+ int rightSize = right.size();
+ double rightImpurity = calc.apply(right.stream().mapToDouble(s -> labels[s]));
+
+ int totalSize = leftSize + rightSize;
+
+ // Result of this call will be sent back to trainer node, we do not need vectors inside of sent data.
+ CategoricalSplitInfo<CategoricalRegionInfo> res = new CategoricalSplitInfo<>(intervalIdx,
+ new CategoricalRegionInfo(leftImpurity, null), // cats can be computed on the last step.
+ new CategoricalRegionInfo(rightImpurity, null),
+ leftCats);
+
+ res.setInfoGain(impurity - (double)leftSize / totalSize * leftImpurity - (double)rightSize / totalSize * rightImpurity);
+ return res;
+ }
+
+ /**
+ * Get a stream of subsets given categories count.
+ *
+ * @param catsCnt categories count.
+ * @return Stream of subsets given categories count.
+ */
+ private Stream<BitSet> powerSet(int catsCnt) {
+ Iterable<BitSet> iterable = () -> new PSI(catsCnt);
+ return StreamSupport.stream(iterable.spliterator(), false);
+ }
+
+ /** {@inheritDoc} */
+ @Override public SplitInfo findBestSplit(RegionProjection<CategoricalRegionInfo> regionPrj, double[] values,
+ double[] labels, int regIdx) {
+ Map<Integer, Integer> mapping = mapping(regionPrj.data().cats());
+
+ return powerSet(regionPrj.data().cats().length()).
+ map(s -> split(s, regIdx, mapping, regionPrj.sampleIndexes(), values, labels, regionPrj.data().impurity())).
+ max(Comparator.comparingDouble(SplitInfo::infoGain)).
+ orElse(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override public RegionProjection<CategoricalRegionInfo> createInitialRegion(Integer[] sampleIndexes,
+ double[] values, double[] labels) {
+ BitSet set = new BitSet();
+ set.set(0, catsCnt);
+
+ Double impurity = calc.apply(Arrays.stream(labels));
+
+ return new RegionProjection<>(sampleIndexes, new CategoricalRegionInfo(impurity, set), 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override public SparseBitSet calculateOwnershipBitSet(RegionProjection<CategoricalRegionInfo> regionPrj,
+ double[] values,
+ CategoricalSplitInfo<CategoricalRegionInfo> s) {
+ SparseBitSet res = new SparseBitSet();
+ Arrays.stream(regionPrj.sampleIndexes()).forEach(smpl -> res.set(smpl, s.bitSet().get((int)values[smpl])));
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteBiTuple<RegionProjection, RegionProjection> performSplit(SparseBitSet bs,
+ RegionProjection<CategoricalRegionInfo> reg, CategoricalRegionInfo leftData, CategoricalRegionInfo rightData) {
+ return performSplitGeneric(bs, null, reg, leftData, rightData);
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteBiTuple<RegionProjection, RegionProjection> performSplitGeneric(
+ SparseBitSet bs, double[] values, RegionProjection<CategoricalRegionInfo> reg, RegionInfo leftData,
+ RegionInfo rightData) {
+ int depth = reg.depth();
+
+ int lSize = bs.cardinality();
+ int rSize = reg.sampleIndexes().length - lSize;
+ IgniteBiTuple<Integer[], Integer[]> lrSamples = splitByBitSet(lSize, rSize, reg.sampleIndexes(), bs);
+ BitSet leftCats = calculateCats(lrSamples.get1(), values);
+ CategoricalRegionInfo lInfo = new CategoricalRegionInfo(leftData.impurity(), leftCats);
+
+ // TODO: IGNITE-5892 Check how it will work with sparse data.
+ BitSet rightCats = calculateCats(lrSamples.get2(), values);
+ CategoricalRegionInfo rInfo = new CategoricalRegionInfo(rightData.impurity(), rightCats);
+
+ RegionProjection<CategoricalRegionInfo> rPrj = new RegionProjection<>(lrSamples.get2(), rInfo, depth + 1);
+ RegionProjection<CategoricalRegionInfo> lPrj = new RegionProjection<>(lrSamples.get1(), lInfo, depth + 1);
+ return new IgniteBiTuple<>(lPrj, rPrj);
+ }
+
+ /**
+ * Powerset iterator. Iterates not over the whole powerset, but on half of it.
+ */
+ private static class PSI implements Iterator<BitSet> {
+
+ /** Current subset number. */
+ private int i = 1; // We are not interested in {emptyset, set} split and therefore start from 1.
+
+ /** Size of set, subsets of which we iterate over. */
+ final int size;
+
+ /**
+ * @param bitCnt Size of set, subsets of which we iterate over.
+ */
+ PSI(int bitCnt) {
+ this.size = 1 << (bitCnt - 1);
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean hasNext() {
+ return i < size;
+ }
+
+ /** {@inheritDoc} */
+ @Override public BitSet next() {
+ BitSet res = BitSet.valueOf(new long[] {i});
+ i++;
+ return res;
+ }
+ }
+
+ /** */
+ private Map<Integer, Integer> mapping(BitSet bs) {
+ int bn = 0;
+ Map<Integer, Integer> res = new HashMap<>();
+
+ int i = 0;
+ while ((bn = bs.nextSetBit(bn)) != -1) {
+ res.put(bn, i);
+ i++;
+ bn++;
+ }
+
+ return res;
+ }
+
+ /** Get set of categories of given samples */
+ private BitSet calculateCats(Integer[] sampleIndexes, double[] values) {
+ BitSet res = new BitSet();
+
+ for (int smpl : sampleIndexes)
+ res.set((int)values[smpl]);
+
+ return res;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousFeatureProcessor.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousFeatureProcessor.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousFeatureProcessor.java
new file mode 100644
index 0000000..4117993
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousFeatureProcessor.java
@@ -0,0 +1,111 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.vectors;
+
+import com.zaxxer.sparsebits.SparseBitSet;
+import java.util.Arrays;
+import java.util.Comparator;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.trees.ContinuousRegionInfo;
+import org.apache.ignite.ml.trees.ContinuousSplitCalculator;
+import org.apache.ignite.ml.trees.RegionInfo;
+import org.apache.ignite.ml.trees.trainers.columnbased.RegionProjection;
+
+import static org.apache.ignite.ml.trees.trainers.columnbased.vectors.FeatureVectorProcessorUtils.splitByBitSet;
+
+/**
+ * Container of projection of samples on continuous feature.
+ *
+ * @param <D> Information about regions. Designed to contain information which will make computations of impurity
+ * optimal.
+ */
+public class ContinuousFeatureProcessor<D extends ContinuousRegionInfo> implements
+ FeatureProcessor<D, ContinuousSplitInfo<D>> {
+ /** ContinuousSplitCalculator used for calculating of best split of each region. */
+ private final ContinuousSplitCalculator<D> calc;
+
+ /**
+ * @param splitCalc Calculator used for calculating splits.
+ */
+ public ContinuousFeatureProcessor(ContinuousSplitCalculator<D> splitCalc) {
+ this.calc = splitCalc;
+ }
+
+ /** {@inheritDoc} */
+ @Override public SplitInfo<D> findBestSplit(RegionProjection<D> ri, double[] values, double[] labels, int regIdx) {
+ SplitInfo<D> res = calc.splitRegion(ri.sampleIndexes(), values, labels, regIdx, ri.data());
+
+ if (res == null)
+ return null;
+
+ double lWeight = (double)res.leftData.getSize() / ri.sampleIndexes().length;
+ double rWeight = (double)res.rightData.getSize() / ri.sampleIndexes().length;
+
+ double infoGain = ri.data().impurity() - lWeight * res.leftData().impurity() - rWeight * res.rightData().impurity();
+ res.setInfoGain(infoGain);
+
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public RegionProjection<D> createInitialRegion(Integer[] samples, double[] values, double[] labels) {
+ Arrays.sort(samples, Comparator.comparingDouble(s -> values[s]));
+ return new RegionProjection<>(samples, calc.calculateRegionInfo(Arrays.stream(labels), samples.length), 0);
+ }
+
+ /** {@inheritDoc} */
+ @Override public SparseBitSet calculateOwnershipBitSet(RegionProjection<D> reg, double[] values,
+ ContinuousSplitInfo<D> s) {
+ SparseBitSet res = new SparseBitSet();
+
+ for (int i = 0; i < s.leftData().getSize(); i++)
+ res.set(reg.sampleIndexes()[i]);
+
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteBiTuple<RegionProjection, RegionProjection> performSplit(SparseBitSet bs,
+ RegionProjection<D> reg, D leftData, D rightData) {
+ int lSize = leftData.getSize();
+ int rSize = rightData.getSize();
+ int depth = reg.depth();
+
+ IgniteBiTuple<Integer[], Integer[]> lrSamples = splitByBitSet(lSize, rSize, reg.sampleIndexes(), bs);
+
+ RegionProjection<D> left = new RegionProjection<>(lrSamples.get1(), leftData, depth + 1);
+ RegionProjection<D> right = new RegionProjection<>(lrSamples.get2(), rightData, depth + 1);
+
+ return new IgniteBiTuple<>(left, right);
+ }
+
+ /** {@inheritDoc} */
+ @Override public IgniteBiTuple<RegionProjection, RegionProjection> performSplitGeneric(SparseBitSet bs,
+ double[] labels, RegionProjection<D> reg, RegionInfo leftData, RegionInfo rightData) {
+ int lSize = bs.cardinality();
+ int rSize = reg.sampleIndexes().length - lSize;
+ int depth = reg.depth();
+
+ IgniteBiTuple<Integer[], Integer[]> lrSamples = splitByBitSet(lSize, rSize, reg.sampleIndexes(), bs);
+
+ D ld = calc.calculateRegionInfo(Arrays.stream(lrSamples.get1()).mapToDouble(s -> labels[s]), lSize);
+ D rd = calc.calculateRegionInfo(Arrays.stream(lrSamples.get2()).mapToDouble(s -> labels[s]), rSize);
+
+ return new IgniteBiTuple<>(new RegionProjection<>(lrSamples.get1(), ld, depth + 1), new RegionProjection<>(lrSamples.get2(), rd, depth + 1));
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousSplitInfo.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousSplitInfo.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousSplitInfo.java
new file mode 100644
index 0000000..d6f2847
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/ContinuousSplitInfo.java
@@ -0,0 +1,54 @@
+package org.apache.ignite.ml.trees.trainers.columnbased.vectors;
+
+import org.apache.ignite.ml.trees.RegionInfo;
+import org.apache.ignite.ml.trees.nodes.ContinuousSplitNode;
+import org.apache.ignite.ml.trees.nodes.SplitNode;
+
+/**
+ * Information about split of continuous region.
+ *
+ * @param <D> Class encapsulating information about the region.
+ */
+public class ContinuousSplitInfo<D extends RegionInfo> extends SplitInfo<D> {
+ /**
+ * Threshold used for split.
+ * Samples with values less or equal than this go to left region, others go to the right region.
+ */
+ private final double threshold;
+
+ /**
+ * @param regionIdx Index of region being split.
+ * @param threshold Threshold used for split. Samples with values less or equal than this go to left region, others
+ * go to the right region.
+ * @param leftData Information about left subregion.
+ * @param rightData Information about right subregion.
+ */
+ public ContinuousSplitInfo(int regionIdx, double threshold, D leftData, D rightData) {
+ super(regionIdx, leftData, rightData);
+ this.threshold = threshold;
+ }
+
+ /** {@inheritDoc} */
+ @Override public SplitNode createSplitNode(int featureIdx) {
+ return new ContinuousSplitNode(threshold, featureIdx);
+ }
+
+ /**
+ * Threshold used for splits.
+ * Samples with values less or equal than this go to left region, others go to the right region.
+ */
+ public double threshold() {
+ return threshold;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "ContinuousSplitInfo [" +
+ "threshold=" + threshold +
+ ", infoGain=" + infoGain +
+ ", regionIdx=" + regionIdx +
+ ", leftData=" + leftData +
+ ", rightData=" + rightData +
+ ']';
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureProcessor.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureProcessor.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureProcessor.java
new file mode 100644
index 0000000..cb8f5c2
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureProcessor.java
@@ -0,0 +1,81 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.vectors;
+
+import com.zaxxer.sparsebits.SparseBitSet;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.trees.RegionInfo;
+import org.apache.ignite.ml.trees.trainers.columnbased.RegionProjection;
+
+/**
+ * Base interface for feature processors used in {@see org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer}
+ *
+ * @param <D> Class representing data of regions resulted from split.
+ * @param <S> Class representing data of split.
+ */
+public interface FeatureProcessor<D extends RegionInfo, S extends SplitInfo<D>> {
+ /**
+ * Finds best split by this feature among all splits of all regions.
+ *
+ * @return best split by this feature among all splits of all regions.
+ */
+ SplitInfo findBestSplit(RegionProjection<D> regionPrj, double[] values, double[] labels, int regIdx);
+
+ /**
+ * Creates initial region from samples.
+ *
+ * @param samples samples.
+ * @return region.
+ */
+ RegionProjection<D> createInitialRegion(Integer[] samples, double[] values, double[] labels);
+
+ /**
+ * Calculates the bitset mapping each data point to left (corresponding bit is set) or right subregion.
+ *
+ * @param s data used for calculating the split.
+ * @return Bitset mapping each data point to left (corresponding bit is set) or right subregion.
+ */
+ SparseBitSet calculateOwnershipBitSet(RegionProjection<D> regionPrj, double[] values, S s);
+
+ /**
+ * Splits given region using bitset which maps data point to left or right subregion.
+ * This method is present for the vectors of the same type to be able to pass between them information about regions
+ * and therefore used iff the optimal split is received on feature of the same type.
+ *
+ * @param bs Bitset which maps data point to left or right subregion.
+ * @param leftData Data of the left subregion.
+ * @param rightData Data of the right subregion.
+ * @return This feature vector.
+ */
+ IgniteBiTuple<RegionProjection, RegionProjection> performSplit(SparseBitSet bs, RegionProjection<D> reg, D leftData,
+ D rightData);
+
+ /**
+ * Splits given region using bitset which maps data point to left or right subregion. This method is used iff the
+ * optimal split is received on feature of different type, therefore information about regions is limited to the
+ * {@see RegionInfo} class which is base for all classes used to represent region data.
+ *
+ * @param bs Bitset which maps data point to left or right subregion.
+ * @param leftData Data of the left subregion.
+ * @param rightData Data of the right subregion.
+ * @return This feature vector.
+ */
+ IgniteBiTuple<RegionProjection, RegionProjection> performSplitGeneric(SparseBitSet bs, double[] values,
+ RegionProjection<D> reg, RegionInfo leftData,
+ RegionInfo rightData);
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureVectorProcessorUtils.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureVectorProcessorUtils.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureVectorProcessorUtils.java
new file mode 100644
index 0000000..69ff019
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/FeatureVectorProcessorUtils.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.vectors;
+
+import com.zaxxer.sparsebits.SparseBitSet;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+/** Utility class for feature vector processors. */
+public class FeatureVectorProcessorUtils {
+ /**
+ * Split target array into two (left and right) arrays by bitset.
+ *
+ * @param lSize Left array size;
+ * @param rSize Right array size.
+ * @param samples Arrays to split size.
+ * @param bs Bitset specifying split.
+ * @return BiTuple containing result of split.
+ */
+ public static IgniteBiTuple<Integer[], Integer[]> splitByBitSet(int lSize, int rSize, Integer[] samples,
+ SparseBitSet bs) {
+ Integer[] lArr = new Integer[lSize];
+ Integer[] rArr = new Integer[rSize];
+
+ int lc = 0;
+ int rc = 0;
+
+ for (int i = 0; i < lSize + rSize; i++) {
+ int si = samples[i];
+
+ if (bs.get(si)) {
+ lArr[lc] = si;
+ lc++;
+ }
+ else {
+ rArr[rc] = si;
+ rc++;
+ }
+ }
+
+ return new IgniteBiTuple<>(lArr, rArr);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SampleInfo.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SampleInfo.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SampleInfo.java
new file mode 100644
index 0000000..8aa4f79
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SampleInfo.java
@@ -0,0 +1,80 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.vectors;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+/**
+ * Information about given sample within given fixed feature.
+ */
+public class SampleInfo implements Externalizable {
+ /** Value of projection of this sample on given fixed feature. */
+ private double val;
+
+ /** Sample index. */
+ private int sampleIdx;
+
+ /**
+ * @param val Value of projection of this sample on given fixed feature.
+ * @param sampleIdx Sample index.
+ */
+ public SampleInfo(double val, int sampleIdx) {
+ this.val = val;
+ this.sampleIdx = sampleIdx;
+ }
+
+ /**
+ * No-op constructor used for serialization/deserialization.
+ */
+ public SampleInfo() {
+ // No-op.
+ }
+
+ /**
+ * Get the value of projection of this sample on given fixed feature.
+ *
+ * @return Value of projection of this sample on given fixed feature.
+ */
+ public double val() {
+ return val;
+ }
+
+ /**
+ * Get the sample index.
+ *
+ * @return Sample index.
+ */
+ public int sampleInd() {
+ return sampleIdx;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeDouble(val);
+ out.writeInt(sampleIdx);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ val = in.readDouble();
+ sampleIdx = in.readInt();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SplitInfo.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SplitInfo.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SplitInfo.java
new file mode 100644
index 0000000..124e82f
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/SplitInfo.java
@@ -0,0 +1,106 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.vectors;
+
+import org.apache.ignite.ml.trees.RegionInfo;
+import org.apache.ignite.ml.trees.nodes.SplitNode;
+
+/**
+ * Class encapsulating information about the split.
+ *
+ * @param <D> Class representing information of left and right subregions.
+ */
+public abstract class SplitInfo<D extends RegionInfo> {
+ /** Information gain of this split. */
+ protected double infoGain;
+
+ /** Index of the region to split. */
+ protected final int regionIdx;
+
+ /** Data of left subregion. */
+ protected final D leftData;
+
+ /** Data of right subregion. */
+ protected final D rightData;
+
+ /**
+ * Construct the split info.
+ *
+ * @param regionIdx Index of the region to split.
+ * @param leftData Data of left subregion.
+ * @param rightData Data of right subregion.
+ */
+ public SplitInfo(int regionIdx, D leftData, D rightData) {
+ this.regionIdx = regionIdx;
+ this.leftData = leftData;
+ this.rightData = rightData;
+ }
+
+ /**
+ * Index of region to split.
+ *
+ * @return Index of region to split.
+ */
+ public int regionIndex() {
+ return regionIdx;
+ }
+
+ /**
+ * Information gain of the split.
+ *
+ * @return Information gain of the split.
+ */
+ public double infoGain() {
+ return infoGain;
+ }
+
+ /**
+ * Data of right subregion.
+ *
+ * @return Data of right subregion.
+ */
+ public D rightData() {
+ return rightData;
+ }
+
+ /**
+ * Data of left subregion.
+ *
+ * @return Data of left subregion.
+ */
+ public D leftData() {
+ return leftData;
+ }
+
+ /**
+ * Create SplitNode from this split info.
+ *
+ * @param featureIdx Index of feature by which goes split.
+ * @return SplitNode from this split info.
+ */
+ public abstract SplitNode createSplitNode(int featureIdx);
+
+ /**
+ * Set information gain.
+ *
+ * @param infoGain Information gain.
+ */
+ public void setInfoGain(double infoGain) {
+ this.infoGain = infoGain;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/package-info.java
new file mode 100644
index 0000000..0dea204
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/vectors/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Contains feature containers needed by column based decision tree trainers.
+ */
+package org.apache.ignite.ml.trees.trainers.columnbased.vectors;
\ No newline at end of file
[3/4] ignite git commit: IGNITE-5218: First version of decision
trees. This closes #2936
Posted by is...@apache.org.
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousRegionInfo.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousRegionInfo.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousRegionInfo.java
new file mode 100644
index 0000000..e98bb72
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousRegionInfo.java
@@ -0,0 +1,74 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+/**
+ * Information about region used by continuous features.
+ */
+public class ContinuousRegionInfo extends RegionInfo {
+ /**
+ * Count of samples in this region.
+ */
+ private int size;
+
+ /**
+ * @param impurity Impurity of the region.
+ * @param size Size of this region
+ */
+ public ContinuousRegionInfo(double impurity, int size) {
+ super(impurity);
+ this.size = size;
+ }
+
+ /**
+ * No-op constructor for serialization/deserialization.
+ */
+ public ContinuousRegionInfo() {
+ // No-op
+ }
+
+ /**
+ * Get the size of region.
+ */
+ public int getSize() {
+ return size;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "ContinuousRegionInfo [" +
+ "size=" + size +
+ ']';
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ super.writeExternal(out);
+ out.writeInt(size);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ super.readExternal(in);
+ size = in.readInt();
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousSplitCalculator.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousSplitCalculator.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousSplitCalculator.java
new file mode 100644
index 0000000..f9b81d0
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/ContinuousSplitCalculator.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.util.stream.DoubleStream;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.SplitInfo;
+
+/**
+ * This class is used for calculation of best split by continuous feature.
+ *
+ * @param <C> Class in which information about region will be stored.
+ */
+public interface ContinuousSplitCalculator<C extends ContinuousRegionInfo> {
+ /**
+ * Calculate region info 'from scratch'.
+ *
+ * @param s Stream of labels in this region.
+ * @param l Index of sample projection on this feature in array sorted by this projection value and intervals
+ * bitsets. ({@see org.apache.ignite.ml.trees.trainers.columnbased.vectors.ContinuousFeatureProcessor}).
+ * @return Region info.
+ */
+ C calculateRegionInfo(DoubleStream s, int l);
+
+ /**
+ * Calculate split info of best split of region given information about this region.
+ *
+ * @param sampleIndexes Indexes of samples of this region.
+ * @param values All values of this feature.
+ * @param labels All labels of this feature.
+ * @param regionIdx Index of region being split.
+ * @param data Information about region being split which can be used for computations.
+ * @return Information about best split of region with index given by regionIdx.
+ */
+ SplitInfo<C> splitRegion(Integer[] sampleIndexes, double[] values, double[] labels, int regionIdx, C data);
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/RegionInfo.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/RegionInfo.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/RegionInfo.java
new file mode 100644
index 0000000..8ec7db3
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/RegionInfo.java
@@ -0,0 +1,62 @@
+/*
+ * 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.ignite.ml.trees;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+/** Class containing information about region. */
+public class RegionInfo implements Externalizable {
+ /** Impurity in this region. */
+ private double impurity;
+
+ /**
+ * @param impurity Impurity of this region.
+ */
+ public RegionInfo(double impurity) {
+ this.impurity = impurity;
+ }
+
+ /**
+ * No-op constructor for serialization/deserialization.
+ */
+ public RegionInfo() {
+ // No-op
+ }
+
+ /**
+ * Get impurity in this region.
+ *
+ * @return Impurity of this region.
+ */
+ public double impurity() {
+ return impurity;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeDouble(impurity);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ impurity = in.readDouble();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/DecisionTreeModel.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/DecisionTreeModel.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/DecisionTreeModel.java
new file mode 100644
index 0000000..86e9326
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/DecisionTreeModel.java
@@ -0,0 +1,44 @@
+/*
+ * 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.ignite.ml.trees.models;
+
+import org.apache.ignite.ml.Model;
+import org.apache.ignite.ml.math.Vector;
+import org.apache.ignite.ml.trees.nodes.DecisionTreeNode;
+
+/**
+ * Model for decision tree.
+ */
+public class DecisionTreeModel implements Model<Vector, Double> {
+ /** Root node of the decision tree. */
+ private final DecisionTreeNode root;
+
+ /**
+ * Construct decision tree model.
+ *
+ * @param root Root of decision tree.
+ */
+ public DecisionTreeModel(DecisionTreeNode root) {
+ this.root = root;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Double predict(Vector val) {
+ return root.process(val);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/package-info.java
new file mode 100644
index 0000000..ce8418e
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/models/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Contains decision tree models.
+ */
+package org.apache.ignite.ml.trees.models;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/CategoricalSplitNode.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/CategoricalSplitNode.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/CategoricalSplitNode.java
new file mode 100644
index 0000000..cae6d4a
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/CategoricalSplitNode.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ignite.ml.trees.nodes;
+
+import java.util.BitSet;
+import org.apache.ignite.ml.math.Vector;
+
+/**
+ * Split node by categorical feature.
+ */
+public class CategoricalSplitNode extends SplitNode {
+ /** Bitset specifying which categories belong to left subregion. */
+ private final BitSet bs;
+
+ /**
+ * Construct categorical split node.
+ *
+ * @param featureIdx Index of feature by which split is done.
+ * @param bs Bitset specifying which categories go to the left subtree.
+ */
+ public CategoricalSplitNode(int featureIdx, BitSet bs) {
+ super(featureIdx);
+ this.bs = bs;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean goLeft(Vector v) {
+ return bs.get((int)v.getX(featureIdx));
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "CategoricalSplitNode [bs=" + bs + ']';
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/ContinuousSplitNode.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/ContinuousSplitNode.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/ContinuousSplitNode.java
new file mode 100644
index 0000000..285cfcd
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/ContinuousSplitNode.java
@@ -0,0 +1,56 @@
+/*
+ * 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.ignite.ml.trees.nodes;
+
+import org.apache.ignite.ml.math.Vector;
+
+/**
+ * Split node representing split of continuous feature.
+ */
+public class ContinuousSplitNode extends SplitNode {
+ /** Threshold. Values which are less or equal then threshold are assigned to the left subregion. */
+ private final double threshold;
+
+ /**
+ * Construct ContinuousSplitNode by threshold and feature index.
+ *
+ * @param threshold Threshold.
+ * @param featureIdx Feature index.
+ */
+ public ContinuousSplitNode(double threshold, int featureIdx) {
+ super(featureIdx);
+ this.threshold = threshold;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean goLeft(Vector v) {
+ return v.getX(featureIdx) <= threshold;
+ }
+
+ /** Threshold. Values which are less or equal then threshold are assigned to the left subregion. */
+ public double threshold() {
+ return threshold;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "ContinuousSplitNode [" +
+ "threshold=" + threshold +
+ ']';
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/DecisionTreeNode.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/DecisionTreeNode.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/DecisionTreeNode.java
new file mode 100644
index 0000000..d31623d
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/DecisionTreeNode.java
@@ -0,0 +1,33 @@
+/*
+ * 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.ignite.ml.trees.nodes;
+
+import org.apache.ignite.ml.math.Vector;
+
+/**
+ * Node of decision tree.
+ */
+public interface DecisionTreeNode {
+ /**
+ * Assign the double value to the given vector.
+ *
+ * @param v Vector.
+ * @return Value assigned to the given vector.
+ */
+ double process(Vector v);
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/Leaf.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/Leaf.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/Leaf.java
new file mode 100644
index 0000000..79b441f
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/Leaf.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ignite.ml.trees.nodes;
+
+import org.apache.ignite.ml.math.Vector;
+
+/**
+ * Terminal node of the decision tree.
+ */
+public class Leaf implements DecisionTreeNode {
+ /**
+ * Value in subregion represented by this node.
+ */
+ private final double val;
+
+ /**
+ * Construct the leaf of decision tree.
+ *
+ * @param val Value in subregion represented by this node.
+ */
+ public Leaf(double val) {
+ this.val = val;
+ }
+
+ /**
+ * Return value in subregion represented by this node.
+ *
+ * @param v Vector.
+ * @return Value in subregion represented by this node.
+ */
+ @Override public double process(Vector v) {
+ return val;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/SplitNode.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/SplitNode.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/SplitNode.java
new file mode 100644
index 0000000..4c258d1
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/SplitNode.java
@@ -0,0 +1,100 @@
+/*
+ * 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.ignite.ml.trees.nodes;
+
+import org.apache.ignite.ml.math.Vector;
+
+/**
+ * Node in decision tree representing a split.
+ */
+public abstract class SplitNode implements DecisionTreeNode {
+ /** Left subtree. */
+ protected DecisionTreeNode l;
+
+ /** Right subtree. */
+ protected DecisionTreeNode r;
+
+ /** Feature index. */
+ protected final int featureIdx;
+
+ /**
+ * Constructs SplitNode with a given feature index.
+ *
+ * @param featureIdx Feature index.
+ */
+ public SplitNode(int featureIdx) {
+ this.featureIdx = featureIdx;
+ }
+
+ /**
+ * Indicates if the given vector is in left subtree.
+ *
+ * @param v Vector
+ * @return Status of given vector being left subtree.
+ */
+ abstract boolean goLeft(Vector v);
+
+ /**
+ * Left subtree.
+ *
+ * @return Left subtree.
+ */
+ public DecisionTreeNode left() {
+ return l;
+ }
+
+ /**
+ * Right subtree.
+ *
+ * @return Right subtree.
+ */
+ public DecisionTreeNode right() {
+ return r;
+ }
+
+ /**
+ * Set the left subtree.
+ *
+ * @param n left subtree.
+ */
+ public void setLeft(DecisionTreeNode n) {
+ l = n;
+ }
+
+ /**
+ * Set the right subtree.
+ *
+ * @param n right subtree.
+ */
+ public void setRight(DecisionTreeNode n) {
+ r = n;
+ }
+
+ /**
+ * Delegates processing to subtrees.
+ *
+ * @param v Vector.
+ * @return Value assigned to the given vector.
+ */
+ @Override public double process(Vector v) {
+ if (left() != null && goLeft(v))
+ return left().process(v);
+ else
+ return right().process(v);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/package-info.java
new file mode 100644
index 0000000..d6deb9d
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/nodes/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Contains classes representing decision tree nodes.
+ */
+package org.apache.ignite.ml.trees.nodes;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/package-info.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/package-info.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/package-info.java
new file mode 100644
index 0000000..b07ba4a
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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 description. -->
+ * Contains decision tree algorithms.
+ */
+package org.apache.ignite.ml.trees;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndex.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndex.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndex.java
new file mode 100644
index 0000000..0d27c8a
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndex.java
@@ -0,0 +1,113 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.cache.affinity.AffinityKeyMapped;
+
+/**
+ * Class representing a simple index in 2d matrix in the form (row, col).
+ */
+public class BiIndex implements Externalizable {
+ /** Row. */
+ private int row;
+
+ /** Column. */
+ @AffinityKeyMapped
+ private int col;
+
+ /**
+ * No-op constructor for serialization/deserialization.
+ */
+ public BiIndex() {
+ // No-op.
+ }
+
+ /**
+ * Construct BiIndex from row and column.
+ *
+ * @param row Row.
+ * @param col Column.
+ */
+ public BiIndex(int row, int col) {
+ this.row = row;
+ this.col = col;
+ }
+
+ /**
+ * Returns row.
+ *
+ * @return Row.
+ */
+ public int row() {
+ return row;
+ }
+
+ /**
+ * Returns column.
+ *
+ * @return Column.
+ */
+ public int col() {
+ return col;
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+
+ BiIndex idx = (BiIndex)o;
+
+ if (row != idx.row)
+ return false;
+ return col == idx.col;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int hashCode() {
+ int res = row;
+ res = 31 * res + col;
+ return res;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "BiIndex [" +
+ "row=" + row +
+ ", col=" + col +
+ ']';
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(row);
+ out.writeInt(col);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ row = in.readInt();
+ col = in.readInt();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndexedCacheColumnDecisionTreeTrainerInput.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndexedCacheColumnDecisionTreeTrainerInput.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndexedCacheColumnDecisionTreeTrainerInput.java
new file mode 100644
index 0000000..04281fb
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/BiIndexedCacheColumnDecisionTreeTrainerInput.java
@@ -0,0 +1,57 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import java.util.Map;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+/**
+ * Adapter for column decision tree trainer for bi-indexed cache.
+ */
+public class BiIndexedCacheColumnDecisionTreeTrainerInput extends CacheColumnDecisionTreeTrainerInput<BiIndex, Double> {
+ /**
+ * Construct an input for {@link ColumnDecisionTreeTrainer}.
+ *
+ * @param cache Bi-indexed cache.
+ * @param catFeaturesInfo Information about categorical feature in the form (feature index -> number of
+ * categories).
+ * @param samplesCnt Count of samples.
+ * @param featuresCnt Count of features.
+ */
+ public BiIndexedCacheColumnDecisionTreeTrainerInput(IgniteCache<BiIndex, Double> cache,
+ Map<Integer, Integer> catFeaturesInfo, int samplesCnt, int featuresCnt) {
+ super(cache,
+ () -> IntStream.range(0, samplesCnt).mapToObj(s -> new BiIndex(s, featuresCnt)),
+ e -> Stream.of(new IgniteBiTuple<>(e.getKey().row(), e.getValue())),
+ DoubleStream::of,
+ fIdx -> IntStream.range(0, samplesCnt).mapToObj(s -> new BiIndex(s, fIdx)),
+ catFeaturesInfo,
+ featuresCnt,
+ samplesCnt);
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object affinityKey(int idx, Ignite ignite) {
+ return idx;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/CacheColumnDecisionTreeTrainerInput.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/CacheColumnDecisionTreeTrainerInput.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/CacheColumnDecisionTreeTrainerInput.java
new file mode 100644
index 0000000..9518caf
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/CacheColumnDecisionTreeTrainerInput.java
@@ -0,0 +1,142 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import java.util.Map;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import java.util.stream.Stream;
+import javax.cache.Cache;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.internal.processors.cache.CacheEntryImpl;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+
+/**
+ * Adapter of a given cache to {@see CacheColumnDecisionTreeTrainerInput}
+ *
+ * @param <K> Class of keys of the cache.
+ * @param <V> Class of values of the cache.
+ */
+public abstract class CacheColumnDecisionTreeTrainerInput<K, V> implements ColumnDecisionTreeTrainerInput {
+ /** Supplier of labels key. */
+ private final IgniteSupplier<Stream<K>> labelsKeys;
+
+ /** Count of features. */
+ private final int featuresCnt;
+
+ /** Function which maps feature index to Stream of keys corresponding to this feature index. */
+ private final IgniteFunction<Integer, Stream<K>> keyMapper;
+
+ /** Information about which features are categorical in form of feature index -> number of categories. */
+ private final Map<Integer, Integer> catFeaturesInfo;
+
+ /** Cache name. */
+ private final String cacheName;
+
+ /** Count of samples. */
+ private final int samplesCnt;
+
+ /** Function used for mapping cache values to stream of tuples. */
+ private final IgniteFunction<Cache.Entry<K, V>, Stream<IgniteBiTuple<Integer, Double>>> valuesMapper;
+
+ /**
+ * Function which map value of entry with label key to DoubleStream.
+ * Look at {@code CacheColumnDecisionTreeTrainerInput::labels} for understanding how {@code labelsKeys} and
+ * {@code labelsMapper} interact.
+ */
+ private final IgniteFunction<V, DoubleStream> labelsMapper;
+
+ /**
+ * Constructs input for {@see org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer}.
+ *
+ * @param c Cache.
+ * @param valuesMapper Function for mapping cache entry to stream used by {@link
+ * org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer}.
+ * @param labelsMapper Function used for mapping cache value to labels array.
+ * @param keyMapper Function used for mapping feature index to the cache key.
+ * @param catFeaturesInfo Information about which features are categorical in form of feature index -> number of
+ * categories.
+ * @param featuresCnt Count of features.
+ * @param samplesCnt Count of samples.
+ */
+ // TODO: IGNITE-5724 think about boxing/unboxing
+ public CacheColumnDecisionTreeTrainerInput(IgniteCache<K, V> c,
+ IgniteSupplier<Stream<K>> labelsKeys,
+ IgniteFunction<Cache.Entry<K, V>, Stream<IgniteBiTuple<Integer, Double>>> valuesMapper,
+ IgniteFunction<V, DoubleStream> labelsMapper,
+ IgniteFunction<Integer, Stream<K>> keyMapper,
+ Map<Integer, Integer> catFeaturesInfo,
+ int featuresCnt, int samplesCnt) {
+
+ cacheName = c.getName();
+ this.labelsKeys = labelsKeys;
+ this.valuesMapper = valuesMapper;
+ this.labelsMapper = labelsMapper;
+ this.keyMapper = keyMapper;
+ this.catFeaturesInfo = catFeaturesInfo;
+ this.samplesCnt = samplesCnt;
+ this.featuresCnt = featuresCnt;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Stream<IgniteBiTuple<Integer, Double>> values(int idx) {
+ return cache(Ignition.localIgnite()).getAll(keyMapper.apply(idx).collect(Collectors.toSet())).
+ entrySet().
+ stream().
+ flatMap(ent -> valuesMapper.apply(new CacheEntryImpl<>(ent.getKey(), ent.getValue())));
+ }
+
+ /** {@inheritDoc} */
+ @Override public double[] labels(Ignite ignite) {
+ return labelsKeys.get().map(k -> get(k, ignite)).flatMapToDouble(labelsMapper).toArray();
+ }
+
+ /** {@inheritDoc} */
+ @Override public Map<Integer, Integer> catFeaturesInfo() {
+ return catFeaturesInfo;
+ }
+
+ /** {@inheritDoc} */
+ @Override public int featuresCount() {
+ return featuresCnt;
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object affinityKey(int idx, Ignite ignite) {
+ return ignite.affinity(cacheName).affinityKey(keyMapper.apply(idx));
+ }
+
+ /** */
+ private V get(K k, Ignite ignite) {
+ V res = cache(ignite).localPeek(k);
+
+ if (res == null)
+ res = cache(ignite).get(k);
+
+ return res;
+ }
+
+ /** */
+ private IgniteCache<K, V> cache(Ignite ignite) {
+ return ignite.getOrCreateCache(cacheName);
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainer.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainer.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainer.java
new file mode 100644
index 0000000..32e33f3
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainer.java
@@ -0,0 +1,557 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import com.zaxxer.sparsebits.SparseBitSet;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.cache.Cache;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.cache.CachePeekMode;
+import org.apache.ignite.cache.affinity.Affinity;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.internal.processors.cache.CacheEntryImpl;
+import org.apache.ignite.internal.util.typedef.X;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.Trainer;
+import org.apache.ignite.ml.math.Vector;
+import org.apache.ignite.ml.math.distributed.CacheUtils;
+import org.apache.ignite.ml.math.functions.Functions;
+import org.apache.ignite.ml.math.functions.IgniteBiFunction;
+import org.apache.ignite.ml.math.functions.IgniteCurriedBiFunction;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.functions.IgniteSupplier;
+import org.apache.ignite.ml.trees.ContinuousRegionInfo;
+import org.apache.ignite.ml.trees.ContinuousSplitCalculator;
+import org.apache.ignite.ml.trees.models.DecisionTreeModel;
+import org.apache.ignite.ml.trees.nodes.DecisionTreeNode;
+import org.apache.ignite.ml.trees.nodes.Leaf;
+import org.apache.ignite.ml.trees.nodes.SplitNode;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.ContextCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.FeaturesCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.FeaturesCache.FeatureKey;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.ProjectionsCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.ProjectionsCache.RegionKey;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.SplitCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.SplitCache.SplitKey;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.FeatureProcessor;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.SplitInfo;
+import org.jetbrains.annotations.NotNull;
+
+import static org.apache.ignite.ml.trees.trainers.columnbased.caches.FeaturesCache.getFeatureCacheKey;
+
+/**
+ * This trainer stores observations as columns and features as rows.
+ * Ideas from https://github.com/fabuzaid21/yggdrasil are used here.
+ */
+public class ColumnDecisionTreeTrainer<D extends ContinuousRegionInfo> implements
+ Trainer<DecisionTreeModel, ColumnDecisionTreeTrainerInput> {
+ /**
+ * Function used to assign a value to a region.
+ */
+ private final IgniteFunction<DoubleStream, Double> regCalc;
+
+ /**
+ * Function used to calculate impurity in regions used by categorical features.
+ */
+ private final IgniteFunction<ColumnDecisionTreeTrainerInput, ? extends ContinuousSplitCalculator<D>> continuousCalculatorProvider;
+
+ /**
+ * Categorical calculator provider.
+ **/
+ private final IgniteFunction<ColumnDecisionTreeTrainerInput, IgniteFunction<DoubleStream, Double>> categoricalCalculatorProvider;
+
+ /**
+ * Cache used for storing data for training.
+ */
+ private IgniteCache<RegionKey, List<RegionProjection>> prjsCache;
+
+ /**
+ * Minimal information gain.
+ */
+ private static final double MIN_INFO_GAIN = 1E-10;
+
+ /**
+ * Maximal depth of the decision tree.
+ */
+ private final int maxDepth;
+
+ /**
+ * Size of block which is used for storing regions in cache.
+ */
+ private static final int BLOCK_SIZE = 1 << 4;
+
+ /** Ignite instance. */
+ private final Ignite ignite;
+
+ /**
+ * Construct {@link ColumnDecisionTreeTrainer}.
+ *
+ * @param maxDepth Maximal depth of the decision tree.
+ * @param continuousCalculatorProvider Provider of calculator of splits for region projection on continuous
+ * features.
+ * @param categoricalCalculatorProvider Provider of calculator of splits for region projection on categorical
+ * features.
+ * @param regCalc Function used to assign a value to a region.
+ */
+ public ColumnDecisionTreeTrainer(int maxDepth,
+ IgniteFunction<ColumnDecisionTreeTrainerInput, ? extends ContinuousSplitCalculator<D>> continuousCalculatorProvider,
+ IgniteFunction<ColumnDecisionTreeTrainerInput, IgniteFunction<DoubleStream, Double>> categoricalCalculatorProvider,
+ IgniteFunction<DoubleStream, Double> regCalc,
+ Ignite ignite) {
+ this.maxDepth = maxDepth;
+ this.continuousCalculatorProvider = continuousCalculatorProvider;
+ this.categoricalCalculatorProvider = categoricalCalculatorProvider;
+ this.regCalc = regCalc;
+ this.ignite = ignite;
+ }
+
+ /**
+ * Utility class used to get index of feature by which split is done and split info.
+ */
+ private static class IndexAndSplitInfo {
+ /**
+ * Index of feature by which split is done.
+ */
+ private final int featureIdx;
+
+ /**
+ * Split information.
+ */
+ private final SplitInfo info;
+
+ /**
+ * @param featureIdx Index of feature by which split is done.
+ * @param info Split information.
+ */
+ IndexAndSplitInfo(int featureIdx, SplitInfo info) {
+ this.featureIdx = featureIdx;
+ this.info = info;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String toString() {
+ return "IndexAndSplitInfo [featureIdx=" + featureIdx + ", info=" + info + ']';
+ }
+ }
+
+ /**
+ * Utility class used to build decision tree. Basically it is pointer to leaf node.
+ */
+ private static class TreeTip {
+ /** */
+ private Consumer<DecisionTreeNode> leafSetter;
+
+ /** */
+ private int depth;
+
+ /** */
+ TreeTip(Consumer<DecisionTreeNode> leafSetter, int depth) {
+ this.leafSetter = leafSetter;
+ this.depth = depth;
+ }
+ }
+
+ /**
+ * Utility class used as decision tree root node.
+ */
+ private static class RootNode implements DecisionTreeNode {
+ /** */
+ private DecisionTreeNode s;
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public double process(Vector v) {
+ return s.process(v);
+ }
+
+ /** */
+ void setSplit(DecisionTreeNode s) {
+ this.s = s;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override public DecisionTreeModel train(ColumnDecisionTreeTrainerInput i) {
+ prjsCache = ProjectionsCache.getOrCreate(ignite);
+ IgniteCache<UUID, TrainingContext<D>> ctxtCache = ContextCache.getOrCreate(ignite);
+ SplitCache.getOrCreate(ignite);
+
+ UUID trainingUUID = UUID.randomUUID();
+
+ TrainingContext<D> ct = new TrainingContext<>(i, continuousCalculatorProvider.apply(i), categoricalCalculatorProvider.apply(i), trainingUUID, ignite);
+ ctxtCache.put(trainingUUID, ct);
+
+ CacheUtils.bcast(prjsCache.getName(), ignite, () -> {
+ Ignite ignite = Ignition.localIgnite();
+ IgniteCache<RegionKey, List<RegionProjection>> projCache = ProjectionsCache.getOrCreate(ignite);
+ IgniteCache<FeatureKey, double[]> featuresCache = FeaturesCache.getOrCreate(ignite);
+
+ Affinity<RegionKey> targetAffinity = ignite.affinity(ProjectionsCache.CACHE_NAME);
+
+ ClusterNode locNode = ignite.cluster().localNode();
+
+ Map<FeatureKey, double[]> fm = new ConcurrentHashMap<>();
+ Map<RegionKey, List<RegionProjection>> pm = new ConcurrentHashMap<>();
+
+ targetAffinity.
+ mapKeysToNodes(IntStream.range(0, i.featuresCount()).
+ mapToObj(idx -> ProjectionsCache.key(idx, 0, i.affinityKey(idx, ignite), trainingUUID)).
+ collect(Collectors.toSet())).getOrDefault(locNode, Collections.emptyList()).
+ forEach(k -> {
+ FeatureProcessor vec;
+
+ int featureIdx = k.featureIdx();
+
+ IgniteCache<UUID, TrainingContext<D>> ctxCache = ContextCache.getOrCreate(ignite);
+ TrainingContext ctx = ctxCache.get(trainingUUID);
+ double[] vals = new double[ctx.labels().length];
+
+ vec = ctx.featureProcessor(featureIdx);
+ i.values(featureIdx).forEach(t -> vals[t.get1()] = t.get2());
+
+ fm.put(getFeatureCacheKey(featureIdx, trainingUUID, i.affinityKey(featureIdx, ignite)), vals);
+
+ List<RegionProjection> newReg = new ArrayList<>(BLOCK_SIZE);
+ newReg.add(vec.createInitialRegion(getSamples(i.values(featureIdx), ctx.labels().length), vals, ctx.labels()));
+ pm.put(k, newReg);
+ });
+
+ featuresCache.putAll(fm);
+ projCache.putAll(pm);
+
+ return null;
+ });
+
+ return doTrain(i, trainingUUID);
+ }
+
+ /**
+ * Get samples array.
+ *
+ * @param values Stream of tuples in the form of (index, value).
+ * @param size size of stream.
+ * @return Samples array.
+ */
+ private Integer[] getSamples(Stream<IgniteBiTuple<Integer, Double>> values, int size) {
+ Integer[] res = new Integer[size];
+
+ values.forEach(v -> res[v.get1()] = v.get1());
+
+ return res;
+ }
+
+ /** */
+ @NotNull
+ private DecisionTreeModel doTrain(ColumnDecisionTreeTrainerInput input, UUID uuid) {
+ RootNode root = new RootNode();
+
+ // List containing setters of leaves of the tree.
+ List<TreeTip> tips = new LinkedList<>();
+ tips.add(new TreeTip(root::setSplit, 0));
+
+ int curDepth = 0;
+ int regsCnt = 1;
+
+ int featuresCnt = input.featuresCount();
+ IntStream.range(0, featuresCnt).mapToObj(fIdx -> SplitCache.key(fIdx, input.affinityKey(fIdx, ignite), uuid)).
+ forEach(k -> SplitCache.getOrCreate(ignite).put(k, new IgniteBiTuple<>(0, 0.0)));
+ updateSplitCache(0, regsCnt, featuresCnt, ig -> i -> input.affinityKey(i, ig), uuid);
+
+ // TODO: IGNITE-5893 Currently if the best split makes tree deeper than max depth process will be terminated, but actually we should
+ // only stop when *any* improving split makes tree deeper than max depth. Can be fixed if we will store which
+ // regions cannot be split more and split only those that can.
+ while (true) {
+ long before = System.currentTimeMillis();
+
+ IgniteBiTuple<Integer, IgniteBiTuple<Integer, Double>> b = findBestSplitIndexForFeatures(featuresCnt, input::affinityKey, uuid);
+
+ long findBestRegIdx = System.currentTimeMillis() - before;
+
+ Integer bestFeatureIdx = b.get1();
+
+ Integer regIdx = b.get2().get1();
+ Double bestInfoGain = b.get2().get2();
+
+ if (regIdx >= 0 && bestInfoGain > MIN_INFO_GAIN) {
+ before = System.currentTimeMillis();
+
+ SplitInfo bi = ignite.compute().affinityCall(ProjectionsCache.CACHE_NAME,
+ input.affinityKey(bestFeatureIdx, ignite),
+ () -> {
+ TrainingContext<ContinuousRegionInfo> ctx = ContextCache.getOrCreate(ignite).get(uuid);
+ Ignite ignite = Ignition.localIgnite();
+ RegionKey key = ProjectionsCache.key(bestFeatureIdx,
+ regIdx / BLOCK_SIZE,
+ input.affinityKey(bestFeatureIdx, Ignition.localIgnite()),
+ uuid);
+ RegionProjection reg = ProjectionsCache.getOrCreate(ignite).localPeek(key).get(regIdx % BLOCK_SIZE);
+ return ctx.featureProcessor(bestFeatureIdx).findBestSplit(reg, ctx.values(bestFeatureIdx, ignite), ctx.labels(), regIdx);
+ });
+
+ long findBestSplit = System.currentTimeMillis() - before;
+
+ IndexAndSplitInfo best = new IndexAndSplitInfo(bestFeatureIdx, bi);
+
+ regsCnt++;
+
+ X.println(">>> Globally best: " + best.info + " idx time: " + findBestRegIdx + ", calculate best: " + findBestSplit + " fi: " + best.featureIdx + ", regs: " + regsCnt);
+ // Request bitset for split region.
+ int ind = best.info.regionIndex();
+
+ SparseBitSet bs = ignite.compute().affinityCall(ProjectionsCache.CACHE_NAME,
+ input.affinityKey(bestFeatureIdx, ignite),
+ () -> {
+ Ignite ignite = Ignition.localIgnite();
+ IgniteCache<FeatureKey, double[]> featuresCache = FeaturesCache.getOrCreate(ignite);
+ IgniteCache<UUID, TrainingContext<D>> ctxCache = ContextCache.getOrCreate(ignite);
+ TrainingContext ctx = ctxCache.localPeek(uuid);
+
+ double[] values = featuresCache.localPeek(getFeatureCacheKey(bestFeatureIdx, uuid, input.affinityKey(bestFeatureIdx, Ignition.localIgnite())));
+ RegionKey key = ProjectionsCache.key(bestFeatureIdx,
+ regIdx / BLOCK_SIZE,
+ input.affinityKey(bestFeatureIdx, Ignition.localIgnite()),
+ uuid);
+ RegionProjection reg = ProjectionsCache.getOrCreate(ignite).localPeek(key).get(regIdx % BLOCK_SIZE);
+ return ctx.featureProcessor(bestFeatureIdx).calculateOwnershipBitSet(reg, values, best.info);
+
+ });
+
+ SplitNode sn = best.info.createSplitNode(best.featureIdx);
+
+ TreeTip tipToSplit = tips.get(ind);
+ tipToSplit.leafSetter.accept(sn);
+ tipToSplit.leafSetter = sn::setLeft;
+ int d = tipToSplit.depth++;
+ tips.add(new TreeTip(sn::setRight, d));
+
+ if (d > curDepth) {
+ curDepth = d;
+ X.println(">>> Depth: " + curDepth);
+ X.println(">>> Cache size: " + prjsCache.size(CachePeekMode.PRIMARY));
+ }
+
+ before = System.currentTimeMillis();
+ // Perform split on all feature vectors.
+ IgniteSupplier<Set<RegionKey>> bestRegsKeys = () -> IntStream.range(0, featuresCnt).
+ mapToObj(fIdx -> ProjectionsCache.key(fIdx, ind / BLOCK_SIZE, input.affinityKey(fIdx, Ignition.localIgnite()), uuid)).
+ collect(Collectors.toSet());
+
+ int rc = regsCnt;
+
+ // Perform split.
+ CacheUtils.update(prjsCache.getName(), ignite,
+ (Ignite ign, Cache.Entry<RegionKey, List<RegionProjection>> e) -> {
+ RegionKey k = e.getKey();
+
+ List<RegionProjection> leftBlock = e.getValue();
+
+ int fIdx = k.featureIdx();
+ int idxInBlock = ind % BLOCK_SIZE;
+
+ IgniteCache<UUID, TrainingContext<D>> ctxCache = ContextCache.getOrCreate(ign);
+ TrainingContext<D> ctx = ctxCache.get(uuid);
+
+ RegionProjection targetRegProj = leftBlock.get(idxInBlock);
+
+ IgniteBiTuple<RegionProjection, RegionProjection> regs = ctx.
+ performSplit(input, bs, fIdx, best.featureIdx, targetRegProj, best.info.leftData(), best.info.rightData(), ign);
+
+ RegionProjection left = regs.get1();
+ RegionProjection right = regs.get2();
+
+ leftBlock.set(idxInBlock, left);
+ RegionKey rightKey = ProjectionsCache.key(fIdx, (rc - 1) / BLOCK_SIZE, input.affinityKey(fIdx, ign), uuid);
+
+ IgniteCache<RegionKey, List<RegionProjection>> c = ProjectionsCache.getOrCreate(ign);
+
+ List<RegionProjection> rightBlock = rightKey.equals(k) ? leftBlock : c.localPeek(rightKey);
+
+ if (rightBlock == null) {
+ List<RegionProjection> newBlock = new ArrayList<>(BLOCK_SIZE);
+ newBlock.add(right);
+ return Stream.of(new CacheEntryImpl<>(k, leftBlock), new CacheEntryImpl<>(rightKey, newBlock));
+ }
+ else {
+ rightBlock.add(right);
+ return rightBlock.equals(k) ?
+ Stream.of(new CacheEntryImpl<>(k, leftBlock)) :
+ Stream.of(new CacheEntryImpl<>(k, leftBlock), new CacheEntryImpl<>(rightKey, rightBlock));
+ }
+ },
+ bestRegsKeys);
+
+ X.println(">>> Update of projs cache took " + (System.currentTimeMillis() - before));
+
+ before = System.currentTimeMillis();
+
+ updateSplitCache(ind, rc, featuresCnt, ig -> i -> input.affinityKey(i, ig), uuid);
+
+ X.println(">>> Update of split cache took " + (System.currentTimeMillis() - before));
+ }
+ else {
+ X.println(">>> Best feature index: " + bestFeatureIdx + ", best infoGain " + bestInfoGain);
+ break;
+ }
+ }
+
+ int rc = regsCnt;
+
+ IgniteSupplier<Iterable<Cache.Entry<RegionKey, List<RegionProjection>>>> featZeroRegs = () -> {
+ IgniteCache<RegionKey, List<RegionProjection>> projsCache = ProjectionsCache.getOrCreate(Ignition.localIgnite());
+
+ return () -> IntStream.range(0, (rc - 1) / BLOCK_SIZE + 1).
+ mapToObj(rBIdx -> ProjectionsCache.key(0, rBIdx, input.affinityKey(0, Ignition.localIgnite()), uuid)).
+ map(k -> (Cache.Entry<RegionKey, List<RegionProjection>>)new CacheEntryImpl<>(k, projsCache.localPeek(k))).iterator();
+ };
+
+ Map<Integer, Double> vals = CacheUtils.reduce(prjsCache.getName(), ignite,
+ (TrainingContext ctx, Cache.Entry<RegionKey, List<RegionProjection>> e, Map<Integer, Double> m) -> {
+ int regBlockIdx = e.getKey().regionBlockIndex();
+
+ if (e.getValue() != null) {
+ for (int i = 0; i < e.getValue().size(); i++) {
+ int regIdx = regBlockIdx * BLOCK_SIZE + i;
+ RegionProjection reg = e.getValue().get(i);
+
+ Double res = regCalc.apply(Arrays.stream(reg.sampleIndexes()).mapToDouble(s -> ctx.labels()[s]));
+ m.put(regIdx, res);
+ }
+ }
+
+ return m;
+ },
+ () -> ContextCache.getOrCreate(Ignition.localIgnite()).get(uuid),
+ featZeroRegs,
+ (infos, infos2) -> {
+ Map<Integer, Double> res = new HashMap<>();
+ res.putAll(infos);
+ res.putAll(infos2);
+ return res;
+ },
+ HashMap::new
+ );
+
+ int i = 0;
+ for (TreeTip tip : tips) {
+ tip.leafSetter.accept(new Leaf(vals.get(i)));
+ i++;
+ }
+
+ ProjectionsCache.clear(featuresCnt, rc, input::affinityKey, uuid, ignite);
+ ContextCache.getOrCreate(ignite).remove(uuid);
+ FeaturesCache.clear(featuresCnt, input::affinityKey, uuid, ignite);
+ SplitCache.clear(featuresCnt, input::affinityKey, uuid, ignite);
+
+ return new DecisionTreeModel(root.s);
+ }
+
+ /**
+ * Find the best split in the form (feature index, (index of region with the best split, impurity of region with the
+ * best split)).
+ *
+ * @param featuresCnt Count of features.
+ * @param affinity Affinity function.
+ * @param trainingUUID UUID of training.
+ * @return Best split in the form (feature index, (index of region with the best split, impurity of region with the
+ * best split)).
+ */
+ private IgniteBiTuple<Integer, IgniteBiTuple<Integer, Double>> findBestSplitIndexForFeatures(int featuresCnt,
+ IgniteBiFunction<Integer, Ignite, Object> affinity,
+ UUID trainingUUID) {
+ Set<Integer> featureIndexes = IntStream.range(0, featuresCnt).boxed().collect(Collectors.toSet());
+
+ return CacheUtils.reduce(SplitCache.CACHE_NAME, ignite,
+ (Object ctx, Cache.Entry<SplitKey, IgniteBiTuple<Integer, Double>> e, IgniteBiTuple<Integer, IgniteBiTuple<Integer, Double>> r) ->
+ Functions.MAX_GENERIC(new IgniteBiTuple<>(e.getKey().featureIdx(), e.getValue()), r, comparator()),
+ () -> null,
+ () -> SplitCache.localEntries(featureIndexes, affinity, trainingUUID),
+ (i1, i2) -> Functions.MAX_GENERIC(i1, i2, Comparator.comparingDouble(bt -> bt.get2().get2())),
+ () -> new IgniteBiTuple<>(-1, new IgniteBiTuple<>(-1, Double.NEGATIVE_INFINITY))
+ );
+ }
+
+ /** */
+ private static Comparator<IgniteBiTuple<Integer, IgniteBiTuple<Integer, Double>>> comparator() {
+ return Comparator.comparingDouble(bt -> bt != null && bt.get2() != null ? bt.get2().get2() : Double.NEGATIVE_INFINITY);
+ }
+
+ /**
+ * Update split cache.
+ *
+ * @param lastSplitRegionIdx Index of region which had last best split.
+ * @param regsCnt Count of regions.
+ * @param featuresCnt Count of features.
+ * @param affinity Affinity function.
+ * @param trainingUUID UUID of current training.
+ */
+ private void updateSplitCache(int lastSplitRegionIdx, int regsCnt, int featuresCnt,
+ IgniteCurriedBiFunction<Ignite, Integer, Object> affinity,
+ UUID trainingUUID) {
+ CacheUtils.update(SplitCache.CACHE_NAME, ignite,
+ (Ignite ign, Cache.Entry<SplitKey, IgniteBiTuple<Integer, Double>> e) -> {
+ Integer bestRegIdx = e.getValue().get1();
+ int fIdx = e.getKey().featureIdx();
+ TrainingContext ctx = ContextCache.getOrCreate(ign).get(trainingUUID);
+
+ Map<Integer, RegionProjection> toCompare;
+
+ // Fully recalculate best.
+ if (bestRegIdx == lastSplitRegionIdx)
+ toCompare = ProjectionsCache.projectionsOfFeature(fIdx, maxDepth, regsCnt, BLOCK_SIZE, affinity.apply(ign), trainingUUID, ign);
+ // Just compare previous best and two regions which are produced by split.
+ else
+ toCompare = ProjectionsCache.projectionsOfRegions(fIdx, maxDepth,
+ IntStream.of(bestRegIdx, lastSplitRegionIdx, regsCnt - 1), BLOCK_SIZE, affinity.apply(ign), trainingUUID, ign);
+
+ double[] values = ctx.values(fIdx, ign);
+ double[] labels = ctx.labels();
+
+ IgniteBiTuple<Integer, Double> max = toCompare.entrySet().stream().
+ map(ent -> {
+ SplitInfo bestSplit = ctx.featureProcessor(fIdx).findBestSplit(ent.getValue(), values, labels, ent.getKey());
+ return new IgniteBiTuple<>(ent.getKey(), bestSplit != null ? bestSplit.infoGain() : Double.NEGATIVE_INFINITY);
+ }).
+ max(Comparator.comparingDouble(IgniteBiTuple::get2)).
+ get();
+
+ return Stream.of(new CacheEntryImpl<>(e.getKey(), max));
+ },
+ () -> IntStream.range(0, featuresCnt).mapToObj(fIdx -> SplitCache.key(fIdx, affinity.apply(ignite).apply(fIdx), trainingUUID)).collect(Collectors.toSet())
+ );
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainerInput.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainerInput.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainerInput.java
new file mode 100644
index 0000000..94331f7
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/ColumnDecisionTreeTrainerInput.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import java.util.Map;
+import java.util.stream.Stream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.lang.IgniteBiTuple;
+
+/**
+ * Input for {@see ColumnDecisionTreeTrainer}.
+ */
+public interface ColumnDecisionTreeTrainerInput {
+ /**
+ * Projection of data on feature with the given index.
+ *
+ * @param idx Feature index.
+ * @return Projection of data on feature with the given index.
+ */
+ Stream<IgniteBiTuple<Integer, Double>> values(int idx);
+
+ /**
+ * Labels.
+ *
+ * @param ignite Ignite instance.
+ */
+ double[] labels(Ignite ignite);
+
+ /** Information about which features are categorical in the form of feature index -> number of categories. */
+ Map<Integer, Integer> catFeaturesInfo();
+
+ /** Number of features. */
+ int featuresCount();
+
+ /**
+ * Get affinity key for the given column index.
+ * Affinity key should be pure-functionally dependent from idx.
+ */
+ Object affinityKey(int idx, Ignite ignite);
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/MatrixColumnDecisionTreeTrainerInput.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/MatrixColumnDecisionTreeTrainerInput.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/MatrixColumnDecisionTreeTrainerInput.java
new file mode 100644
index 0000000..9a11902
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/MatrixColumnDecisionTreeTrainerInput.java
@@ -0,0 +1,82 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import javax.cache.Cache;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.distributed.keys.RowColMatrixKey;
+import org.apache.ignite.ml.math.distributed.keys.impl.SparseMatrixKey;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.math.impls.matrix.SparseDistributedMatrix;
+import org.apache.ignite.ml.math.impls.storage.matrix.SparseDistributedMatrixStorage;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Adapter of SparseDistributedMatrix to ColumnDecisionTreeTrainerInput.
+ * Sparse SparseDistributedMatrix should be in {@see org.apache.ignite.ml.math.StorageConstants#COLUMN_STORAGE_MODE} and
+ * should contain samples in rows last position in row being label of this sample.
+ */
+public class MatrixColumnDecisionTreeTrainerInput extends CacheColumnDecisionTreeTrainerInput<RowColMatrixKey, Map<Integer, Double>> {
+ /**
+ * @param m Sparse SparseDistributedMatrix should be in {@see org.apache.ignite.ml.math.StorageConstants#COLUMN_STORAGE_MODE}
+ * containing samples in rows last position in row being label of this sample.
+ * @param catFeaturesInfo Information about which features are categorical in form of feature index -> number of
+ * categories.
+ */
+ public MatrixColumnDecisionTreeTrainerInput(SparseDistributedMatrix m, Map<Integer, Integer> catFeaturesInfo) {
+ super(((SparseDistributedMatrixStorage)m.getStorage()).cache(),
+ () -> Stream.of(new SparseMatrixKey(m.columnSize() - 1, m.getUUID(), m.columnSize() - 1)),
+ valuesMapper(m),
+ labels(m),
+ keyMapper(m),
+ catFeaturesInfo,
+ m.columnSize() - 1,
+ m.rowSize());
+ }
+
+ /** Values mapper. See {@link CacheColumnDecisionTreeTrainerInput#valuesMapper} */
+ @NotNull
+ private static IgniteFunction<Cache.Entry<RowColMatrixKey, Map<Integer, Double>>, Stream<IgniteBiTuple<Integer, Double>>> valuesMapper(
+ SparseDistributedMatrix m) {
+ return ent -> {
+ Map<Integer, Double> map = ent.getValue() != null ? ent.getValue() : new HashMap<>();
+ return IntStream.range(0, m.rowSize()).mapToObj(k -> new IgniteBiTuple<>(k, map.getOrDefault(k, 0.0)));
+ };
+ }
+
+ /** Key mapper. See {@link CacheColumnDecisionTreeTrainerInput#keyMapper} */
+ @NotNull private static IgniteFunction<Integer, Stream<RowColMatrixKey>> keyMapper(SparseDistributedMatrix m) {
+ return i -> Stream.of(new SparseMatrixKey(i, ((SparseDistributedMatrixStorage)m.getStorage()).getUUID(), i));
+ }
+
+ /** Labels mapper. See {@link CacheColumnDecisionTreeTrainerInput#labelsMapper} */
+ @NotNull private static IgniteFunction<Map<Integer, Double>, DoubleStream> labels(SparseDistributedMatrix m) {
+ return mp -> IntStream.range(0, m.rowSize()).mapToDouble(k -> mp.getOrDefault(k, 0.0));
+ }
+
+ /** {@inheritDoc} */
+ @Override public Object affinityKey(int idx, Ignite ignite) {
+ return idx;
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/RegionProjection.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/RegionProjection.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/RegionProjection.java
new file mode 100644
index 0000000..e95f57b
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/RegionProjection.java
@@ -0,0 +1,109 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import org.apache.ignite.ml.trees.RegionInfo;
+
+/**
+ * Projection of region on given feature.
+ *
+ * @param <D> Data of region.
+ */
+public class RegionProjection<D extends RegionInfo> implements Externalizable {
+ /** Samples projections. */
+ protected Integer[] sampleIndexes;
+
+ /** Region data */
+ protected D data;
+
+ /** Depth of this region. */
+ protected int depth;
+
+ /**
+ * @param sampleIndexes Samples indexes.
+ * @param data Region data.
+ * @param depth Depth of this region.
+ */
+ public RegionProjection(Integer[] sampleIndexes, D data, int depth) {
+ this.data = data;
+ this.depth = depth;
+ this.sampleIndexes = sampleIndexes;
+ }
+
+ /**
+ * No-op constructor used for serialization/deserialization.
+ */
+ public RegionProjection() {
+ // No-op.
+ }
+
+ /**
+ * Get samples indexes.
+ *
+ * @return Samples indexes.
+ */
+ public Integer[] sampleIndexes() {
+ return sampleIndexes;
+ }
+
+ /**
+ * Get region data.
+ *
+ * @return Region data.
+ */
+ public D data() {
+ return data;
+ }
+
+ /**
+ * Get region depth.
+ *
+ * @return Region depth.
+ */
+ public int depth() {
+ return depth;
+ }
+
+ /** {@inheritDoc} */
+ @Override public void writeExternal(ObjectOutput out) throws IOException {
+ out.writeInt(sampleIndexes.length);
+
+ for (Integer sampleIndex : sampleIndexes)
+ out.writeInt(sampleIndex);
+
+ out.writeObject(data);
+ out.writeInt(depth);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+ int size = in.readInt();
+
+ sampleIndexes = new Integer[size];
+
+ for (int i = 0; i < size; i++)
+ sampleIndexes[i] = in.readInt();
+
+ data = (D)in.readObject();
+ depth = in.readInt();
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/TrainingContext.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/TrainingContext.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/TrainingContext.java
new file mode 100644
index 0000000..6415dab
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/TrainingContext.java
@@ -0,0 +1,166 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased;
+
+import com.zaxxer.sparsebits.SparseBitSet;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.DoubleStream;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.Ignition;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.ml.math.functions.IgniteFunction;
+import org.apache.ignite.ml.trees.ContinuousRegionInfo;
+import org.apache.ignite.ml.trees.ContinuousSplitCalculator;
+import org.apache.ignite.ml.trees.RegionInfo;
+import org.apache.ignite.ml.trees.trainers.columnbased.caches.FeaturesCache;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.CategoricalFeatureProcessor;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.ContinuousFeatureProcessor;
+import org.apache.ignite.ml.trees.trainers.columnbased.vectors.FeatureProcessor;
+
+import static org.apache.ignite.ml.trees.trainers.columnbased.caches.FeaturesCache.COLUMN_DECISION_TREE_TRAINER_FEATURES_CACHE_NAME;
+
+/**
+ * Context of training with {@link ColumnDecisionTreeTrainer}.
+ *
+ * @param <D> Class for storing of information used in calculation of impurity of continuous feature region.
+ */
+public class TrainingContext<D extends ContinuousRegionInfo> {
+ /** Input for training with {@link ColumnDecisionTreeTrainer}. */
+ private final ColumnDecisionTreeTrainerInput input;
+
+ /** Labels. */
+ private final double[] labels;
+
+ /** Calculator used for finding splits of region of continuous features. */
+ private final ContinuousSplitCalculator<D> continuousSplitCalculator;
+
+ /** Calculator used for finding splits of region of categorical feature. */
+ private final IgniteFunction<DoubleStream, Double> categoricalSplitCalculator;
+
+ /** UUID of current training. */
+ private final UUID trainingUUID;
+
+ /**
+ * Construct context for training with {@link ColumnDecisionTreeTrainer}.
+ *
+ * @param input Input for training.
+ * @param continuousSplitCalculator Calculator used for calculations of splits of continuous features regions.
+ * @param categoricalSplitCalculator Calculator used for calculations of splits of categorical features regions.
+ * @param trainingUUID UUID of the current training.
+ * @param ignite Ignite instance.
+ */
+ public TrainingContext(ColumnDecisionTreeTrainerInput input,
+ ContinuousSplitCalculator<D> continuousSplitCalculator,
+ IgniteFunction<DoubleStream, Double> categoricalSplitCalculator,
+ UUID trainingUUID,
+ Ignite ignite) {
+ this.input = input;
+ this.labels = input.labels(ignite);
+ this.continuousSplitCalculator = continuousSplitCalculator;
+ this.categoricalSplitCalculator = categoricalSplitCalculator;
+ this.trainingUUID = trainingUUID;
+ }
+
+ /**
+ * Get processor used for calculating splits of categorical features.
+ *
+ * @param catsCnt Count of categories.
+ * @return Processor used for calculating splits of categorical features.
+ */
+ public CategoricalFeatureProcessor categoricalFeatureProcessor(int catsCnt) {
+ return new CategoricalFeatureProcessor(categoricalSplitCalculator, catsCnt);
+ }
+
+ /**
+ * Get processor used for calculating splits of continuous features.
+ *
+ * @return Processor used for calculating splits of continuous features.
+ */
+ public ContinuousFeatureProcessor<D> continuousFeatureProcessor() {
+ return new ContinuousFeatureProcessor<>(continuousSplitCalculator);
+ }
+
+ /**
+ * Get labels.
+ *
+ * @return Labels.
+ */
+ public double[] labels() {
+ return labels;
+ }
+
+ /**
+ * Get values of feature with given index.
+ *
+ * @param featIdx Feature index.
+ * @param ignite Ignite instance.
+ * @return Values of feature with given index.
+ */
+ public double[] values(int featIdx, Ignite ignite) {
+ IgniteCache<FeaturesCache.FeatureKey, double[]> featuresCache = ignite.getOrCreateCache(COLUMN_DECISION_TREE_TRAINER_FEATURES_CACHE_NAME);
+ return featuresCache.localPeek(FeaturesCache.getFeatureCacheKey(featIdx, trainingUUID, input.affinityKey(featIdx, ignite)));
+ }
+
+ /**
+ * Perform best split on the given region projection.
+ *
+ * @param input Input of {@link ColumnDecisionTreeTrainer} performing split.
+ * @param bitSet Bit set specifying split.
+ * @param targetFeatIdx Index of feature for performing split.
+ * @param bestFeatIdx Index of feature with best split.
+ * @param targetRegionPrj Projection of region to split on feature with index {@code featureIdx}.
+ * @param leftData Data of left region of split.
+ * @param rightData Data of right region of split.
+ * @param ignite Ignite instance.
+ * @return Perform best split on the given region projection.
+ */
+ public IgniteBiTuple<RegionProjection, RegionProjection> performSplit(ColumnDecisionTreeTrainerInput input,
+ SparseBitSet bitSet, int targetFeatIdx, int bestFeatIdx, RegionProjection targetRegionPrj, RegionInfo leftData,
+ RegionInfo rightData, Ignite ignite) {
+
+ Map<Integer, Integer> catFeaturesInfo = input.catFeaturesInfo();
+
+ if (!catFeaturesInfo.containsKey(targetFeatIdx) && !catFeaturesInfo.containsKey(bestFeatIdx))
+ return continuousFeatureProcessor().performSplit(bitSet, targetRegionPrj, (D)leftData, (D)rightData);
+ else if (catFeaturesInfo.containsKey(targetFeatIdx))
+ return categoricalFeatureProcessor(catFeaturesInfo.get(targetFeatIdx)).performSplitGeneric(bitSet, values(targetFeatIdx, ignite), targetRegionPrj, leftData, rightData);
+ return continuousFeatureProcessor().performSplitGeneric(bitSet, labels, targetRegionPrj, leftData, rightData);
+ }
+
+ /**
+ * Processor used for calculating splits for feature with the given index.
+ *
+ * @param featureIdx Index of feature to process.
+ * @return Processor used for calculating splits for feature with the given index.
+ */
+ public FeatureProcessor featureProcessor(int featureIdx) {
+ return input.catFeaturesInfo().containsKey(featureIdx) ? categoricalFeatureProcessor(input.catFeaturesInfo().get(featureIdx)) : continuousFeatureProcessor();
+ }
+
+ /**
+ * Shortcut for affinity key.
+ *
+ * @param idx Feature index.
+ * @return Affinity key.
+ */
+ public Object affinityKey(int idx) {
+ return input.affinityKey(idx, Ignition.localIgnite());
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/db7697b1/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ContextCache.java
----------------------------------------------------------------------
diff --git a/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ContextCache.java b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ContextCache.java
new file mode 100644
index 0000000..51ea359
--- /dev/null
+++ b/modules/ml/src/main/java/org/apache/ignite/ml/trees/trainers/columnbased/caches/ContextCache.java
@@ -0,0 +1,68 @@
+/*
+ * 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.ignite.ml.trees.trainers.columnbased.caches;
+
+import java.util.UUID;
+import org.apache.ignite.Ignite;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.CacheAtomicityMode;
+import org.apache.ignite.cache.CacheMode;
+import org.apache.ignite.cache.CacheWriteSynchronizationMode;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.ml.trees.ContinuousRegionInfo;
+import org.apache.ignite.ml.trees.trainers.columnbased.ColumnDecisionTreeTrainer;
+import org.apache.ignite.ml.trees.trainers.columnbased.TrainingContext;
+
+/**
+ * Class for operations related to cache containing training context for {@link ColumnDecisionTreeTrainer}.
+ */
+public class ContextCache {
+ /**
+ * Name of cache containing training context for {@link ColumnDecisionTreeTrainer}.
+ */
+ public static final String COLUMN_DECISION_TREE_TRAINER_CONTEXT_CACHE_NAME = "COLUMN_DECISION_TREE_TRAINER_CONTEXT_CACHE_NAME";
+
+ /**
+ * Get or create cache for training context.
+ *
+ * @param ignite Ignite instance.
+ * @param <D> Class storing information about continuous regions.
+ * @return Cache for training context.
+ */
+ public static <D extends ContinuousRegionInfo> IgniteCache<UUID, TrainingContext<D>> getOrCreate(Ignite ignite) {
+ CacheConfiguration<UUID, TrainingContext<D>> cfg = new CacheConfiguration<>();
+
+ cfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC);
+
+ cfg.setAtomicityMode(CacheAtomicityMode.ATOMIC);
+
+ cfg.setEvictionPolicy(null);
+
+ cfg.setCopyOnRead(false);
+
+ cfg.setCacheMode(CacheMode.REPLICATED);
+
+ cfg.setOnheapCacheEnabled(true);
+
+ cfg.setReadFromBackup(true);
+
+ cfg.setName(COLUMN_DECISION_TREE_TRAINER_CONTEXT_CACHE_NAME);
+
+ return ignite.getOrCreateCache(cfg);
+ }
+}