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 com.google.protobuf.Any; 020import org.tribuo.Output; 021import org.tribuo.common.tree.protos.SplitNodeProto; 022import org.tribuo.common.tree.protos.TreeNodeProto; 023import org.tribuo.math.la.SparseVector; 024 025import java.util.Objects; 026 027/** 028 * An immutable {@link Node} with a split and two child nodes. 029 */ 030public class SplitNode<T extends Output<T>> implements Node<T> { 031 private static final long serialVersionUID = 3L; 032 033 /** 034 * Protobuf serialization version. 035 */ 036 public static final int CURRENT_VERSION = 0; 037 038 private final Node<T> greaterThan; 039 040 private final Node<T> lessThanOrEqual; 041 042 private final int splitFeature; 043 044 private final double splitValue; 045 046 private final double impurity; 047 048 /** 049 * Constructs a split node with the specified split value, feature id, impurity and child nodes. 050 * @param splitValue The feature value to split on. 051 * @param featureID The feature id number. 052 * @param impurity The impurity of this node at training time. 053 * @param greaterThan The node to take if the feature value is greater than the split value. 054 * @param lessThanOrEqual The node to take if the feature value is less than or equal to the split value. 055 */ 056 public SplitNode(double splitValue, int featureID, double impurity, Node<T> greaterThan, Node<T> lessThanOrEqual) { 057 this.splitValue = splitValue; 058 this.splitFeature = featureID; 059 this.impurity = impurity; 060 this.greaterThan = greaterThan; 061 this.lessThanOrEqual = lessThanOrEqual; 062 } 063 064 /** 065 * Return the appropriate child node. If the splitFeature is not present in 066 * the example it's value is treated as zero. 067 * 068 * @param e The example to inspect. 069 * @return The corresponding child node. 070 */ 071 @Override 072 public Node<T> getNextNode(SparseVector e) { 073 double feature = e.get(splitFeature); 074 if (feature > splitValue) { 075 return greaterThan; 076 } else { 077 return lessThanOrEqual; 078 } 079 } 080 081 @Override 082 public boolean isLeaf() { 083 return false; 084 } 085 086 @Override 087 public double getImpurity() { 088 return impurity; 089 } 090 091 @Override 092 public Node<T> copy() { 093 return new SplitNode<>(splitValue,splitFeature,impurity,greaterThan.copy(),lessThanOrEqual.copy()); 094 } 095 096 /** 097 * Gets the feature ID that this node uses for splitting. 098 * @return The feature ID. 099 */ 100 public int getFeatureID() { 101 return splitFeature; 102 } 103 104 /** 105 * The threshold value. 106 * @return The threshold value. 107 */ 108 public double splitValue() { 109 return splitValue; 110 } 111 112 /** 113 * The node used if the value is greater than the splitValue. 114 * @return The greater than node. 115 */ 116 public Node<T> getGreaterThan() { 117 return greaterThan; 118 } 119 120 /** 121 * The node used if the value is less than or equal to the splitValue. 122 * @return The less than or equal to node. 123 */ 124 public Node<T> getLessThanOrEqual() { 125 return lessThanOrEqual; 126 } 127 128 @Override 129 public String toString() { 130 return "SplitNode(feature="+splitFeature+",value="+splitValue+",impurity="+impurity+",\n\t\tleft="+lessThanOrEqual.toString()+",\n\t\tright="+greaterThan.toString()+")"; 131 } 132 133 @Override 134 public boolean equals(Object o) { 135 if (this == o) return true; 136 if (o == null || getClass() != o.getClass()) return false; 137 SplitNode<?> splitNode = (SplitNode<?>) o; 138 return splitFeature == splitNode.splitFeature && 139 Double.compare(splitNode.splitValue, splitValue) == 0 && 140 Double.compare(splitNode.impurity, impurity) == 0 && 141 greaterThan.equals(splitNode.greaterThan) && 142 lessThanOrEqual.equals(splitNode.lessThanOrEqual); 143 } 144 145 @Override 146 public int hashCode() { 147 return Objects.hash(greaterThan, lessThanOrEqual, splitFeature, splitValue, impurity); 148 } 149 150 TreeNodeProto serialize(int parentIdx, int curIdx, int greaterThanIdx, int lessThanOrEqualIdx) { 151 SplitNodeProto.Builder nodeBuilder = SplitNodeProto.newBuilder(); 152 nodeBuilder.setParentIdx(parentIdx); 153 nodeBuilder.setCurIdx(curIdx); 154 nodeBuilder.setGreaterThanIdx(greaterThanIdx); 155 nodeBuilder.setLessThanOrEqualIdx(lessThanOrEqualIdx); 156 nodeBuilder.setSplitFeatureIdx(splitFeature); 157 nodeBuilder.setSplitValue(splitValue); 158 nodeBuilder.setImpurity(impurity); 159 160 TreeNodeProto.Builder builder = TreeNodeProto.newBuilder(); 161 builder.setVersion(CURRENT_VERSION); 162 builder.setClassName(LeafNode.class.getName()); 163 builder.setSerializedData(Any.pack(nodeBuilder.build())); 164 165 return builder.build(); 166 } 167 168 static final class SplitNodeBuilder<T extends Output<T>> extends TreeModel.NodeBuilder implements Node<T> { 169 170 private final int parentIdx; 171 private final int curIdx; 172 private final int greaterThanIdx; 173 private final int lessThanOrEqualIdx; 174 private final int splitFeature; 175 private final double splitValue; 176 private final double impurity; 177 178 private Node<T> greaterThan; 179 private Node<T> lessThanOrEqual; 180 181 SplitNodeBuilder(SplitNodeProto proto) { 182 this.parentIdx = proto.getParentIdx(); 183 this.curIdx = proto.getCurIdx(); 184 this.greaterThanIdx = proto.getGreaterThanIdx(); 185 this.lessThanOrEqualIdx = proto.getLessThanOrEqualIdx(); 186 this.splitFeature = proto.getSplitFeatureIdx(); 187 this.splitValue = proto.getSplitValue(); 188 this.impurity = proto.getImpurity(); 189 } 190 191 SplitNodeBuilder(int parentIdx, int curIdx, int greaterThanIdx, int lessThanOrEqualIdx, int splitFeature, double splitValue, double impurity) { 192 this.parentIdx = parentIdx; 193 this.curIdx = curIdx; 194 this.greaterThanIdx = greaterThanIdx; 195 this.lessThanOrEqualIdx = lessThanOrEqualIdx; 196 this.splitFeature = splitFeature; 197 this.splitValue = splitValue; 198 this.impurity = impurity; 199 } 200 201 @Override 202 public boolean isLeaf() { 203 return false; 204 } 205 206 @Override 207 public Node<T> getNextNode(SparseVector example) { 208 return null; 209 } 210 211 @Override 212 public double getImpurity() { 213 return impurity; 214 } 215 216 @Override 217 public SplitNodeBuilder<T> copy() { 218 return new SplitNodeBuilder<>(parentIdx, curIdx, greaterThanIdx, lessThanOrEqualIdx, splitFeature, splitValue, impurity); 219 } 220 221 boolean canBuild() { 222 return greaterThan != null && lessThanOrEqual != null; 223 } 224 225 SplitNode<T> build() { 226 if (!canBuild()) { 227 throw new IllegalStateException("Not ready to build this split node, missing the children pointers"); 228 } 229 return new SplitNode<>(splitValue,splitFeature,impurity,greaterThan,lessThanOrEqual); 230 } 231 232 void setGreaterThan(Node<T> greaterThan) { 233 if (this.greaterThan == null) { 234 this.greaterThan = greaterThan; 235 } else { 236 throw new IllegalStateException("Invalid protobuf, multiple nodes mapped to the greaterThanIdx"); 237 } 238 } 239 240 void setLessThanOrEqual(Node<T> lessThanOrEqual) { 241 if (this.lessThanOrEqual == null) { 242 this.lessThanOrEqual = lessThanOrEqual; 243 } else { 244 throw new IllegalStateException("Invalid protobuf, multiple nodes mapped to the lessThanOrEqualIdx"); 245 } 246 } 247 248 int getGreaterThanIdx() { 249 return greaterThanIdx; 250 } 251 252 int getLessThanOrEqualIdx() { 253 return lessThanOrEqualIdx; 254 } 255 256 int getParentIdx() { 257 return parentIdx; 258 } 259 260 int getCurIdx() { 261 return curIdx; 262 } 263 } 264} 265