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}