001/*
002 * Copyright (c) 2015-2020, Oracle and/or its affiliates. All rights reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.tribuo.common.tree;
018
019import org.tribuo.Output;
020import org.tribuo.math.la.SparseVector;
021
022import java.util.List;
023import java.util.SplittableRandom;
024
025/**
026 * Base class for decision tree nodes used at training time.
027 */
028public abstract class AbstractTrainingNode<T extends Output<T>> implements Node<T> {
029
030    /**
031     * Default buffer size used in the split operation.
032     */
033    protected static final int DEFAULT_SIZE = 16;
034
035    protected final int depth;
036
037    protected final int numExamples;
038
039    protected final LeafDeterminer leafDeterminer;
040
041    protected boolean split;
042
043    protected int splitID;
044
045    protected double splitValue;
046
047    protected double impurityScore;
048    
049    protected Node<T> greaterThan;
050    
051    protected Node<T> lessThanOrEqual;
052
053    /**
054     * Builds an abstract training node.
055     * @param depth The depth of this node.
056     * @param numExamples The number of examples in this node.
057     * @param leafDeterminer The parameters which determine if the node forms a leaf.
058     */
059    protected AbstractTrainingNode(int depth, int numExamples, LeafDeterminer leafDeterminer) {
060        this.depth = depth;
061        this.numExamples = numExamples;
062        this.leafDeterminer = leafDeterminer;
063    }
064
065    /**
066     * Builds next level of a tree.
067     * @param featureIDs Indices of the features available in this split.
068     * @param rng Splittable random number generator.
069     * @param useRandomSplitPoints Whether to choose split points for features at random.
070     * @return A possibly empty list of TrainingNodes.
071     */
072    public abstract List<AbstractTrainingNode<T>> buildTree(int[] featureIDs, SplittableRandom rng,
073                                                            boolean useRandomSplitPoints);
074
075    /**
076     * Converts a tree from a training representation to the final inference time representation.
077     * @return The converted subtree.
078     */
079    public abstract Node<T> convertTree();
080
081    /**
082     * The sum of the weights associated with this node's examples.
083     * @return the sum of the weights associated with this node's examples.
084     */
085    public abstract float getWeightSum();
086
087    /**
088     * The depth of this node in the tree.
089     * @return The depth.
090     */
091    public int getDepth() {
092        return depth;
093    }
094
095    /**
096     * Determines whether the node to be created should be a {@link LeafNode}.
097     * @param impurityScore impurity score for the new node.
098     * @param weightSum total example weight for the new node.
099     * @return Whether the new node should be a {@link LeafNode}.
100     */
101    public boolean shouldMakeLeaf(double impurityScore, float weightSum) {
102        return ((Math.abs(impurityScore) < 1e-15) ||
103                (depth + 1 >= leafDeterminer.getMaxDepth()) ||
104                (weightSum < leafDeterminer.getMinChildWeight()));
105    }
106
107    /**
108     * Transforms an {@link AbstractTrainingNode} into a {@link SplitNode}
109     * @return A {@link SplitNode}
110     */
111    public SplitNode<T> createSplitNode() {
112        Node<T> newGreaterThan = greaterThan;
113        Node<T> newLessThan = lessThanOrEqual;
114
115        // split node
116        if (greaterThan instanceof AbstractTrainingNode) {
117            AbstractTrainingNode<T> abstractGreaterThan = (AbstractTrainingNode<T>) greaterThan;
118            newGreaterThan = abstractGreaterThan.convertTree();
119        }
120
121        if (lessThanOrEqual instanceof AbstractTrainingNode) {
122            AbstractTrainingNode<T> abstractLessThan = (AbstractTrainingNode<T>) lessThanOrEqual;
123            newLessThan = abstractLessThan.convertTree();
124        }
125        return new SplitNode<>(splitValue,splitID,getImpurity(),newGreaterThan,newLessThan);
126    }
127
128    @Override
129    public Node<T> getNextNode(SparseVector example) {
130        if (split) {
131            double feature = example.get(splitID);
132            if (feature > splitValue) {
133                return greaterThan;
134            } else {
135                return lessThanOrEqual;
136            }
137        } else {
138            return null;
139        }
140    }
141
142    /**
143     * The number of training examples in this node.
144     * @return The number of training examples in this node.
145     */
146    public int getNumExamples() {
147        return numExamples;
148    }
149
150    @Override
151    public boolean isLeaf() {
152        return !split;
153    }
154
155    @Override
156    public Node<T> copy() {
157        throw new UnsupportedOperationException("Copy is not supported on training nodes.");
158    }
159
160    /**
161     * Contains parameters needed to determine whether a node is a leaf.
162     */
163    // Will be a record one day.
164    public static class LeafDeterminer {
165        private final int maxDepth;
166        private final float minChildWeight;
167        private final float scaledMinImpurityDecrease;
168
169        /**
170         * Constructs a leaf determiner using the supplied parameters.
171         * @param maxDepth The maximum tree depth.
172         * @param minChildWeight The minimum example weight of each child node.
173         * @param scaledMinImpurityDecrease  The scaled minimum impurity decrease necessary to split a node.
174         */
175        public LeafDeterminer(int maxDepth, float minChildWeight, float scaledMinImpurityDecrease) {
176            this.maxDepth = maxDepth;
177            this.minChildWeight = minChildWeight;
178            this.scaledMinImpurityDecrease = scaledMinImpurityDecrease;
179        }
180
181        /**
182         * Gets the maximum tree depth.
183         * @return The maximum tree depth.
184         */
185        public int getMaxDepth() {
186            return maxDepth;
187        }
188
189        /**
190         * Gets the minimum example weight of a child node.
191         * @return The mimimum weight of a child node.
192         */
193        public float getMinChildWeight() {
194            return minChildWeight;
195        }
196
197        /**
198         * Gets the minimum impurity decrease necessary to split a node.
199         * @return The minimum impurity decrease to split a node.
200         */
201        public float getScaledMinImpurityDecrease() {
202            return scaledMinImpurityDecrease;
203        }
204    }
205
206}