1 // Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 // =============================================================================
15 #include "tensorflow/contrib/boosted_trees/lib/models/multiple_additive_trees.h"
16
17 #include "tensorflow/contrib/boosted_trees/lib/testutil/batch_features_testutil.h"
18 #include "tensorflow/contrib/boosted_trees/lib/testutil/random_tree_gen.h"
19 #include "tensorflow/contrib/boosted_trees/resources/decision_tree_ensemble_resource.h"
20 #include "tensorflow/core/framework/tensor.h"
21 #include "tensorflow/core/framework/tensor_testutil.h"
22 #include "tensorflow/core/lib/core/status.h"
23 #include "tensorflow/core/lib/core/status_test_util.h"
24 #include "tensorflow/core/lib/random/philox_random.h"
25 #include "tensorflow/core/lib/random/simple_philox.h"
26 #include "tensorflow/core/platform/env.h"
27 #include "tensorflow/core/platform/test.h"
28 #include "tensorflow/core/platform/test_benchmark.h"
29
30 namespace tensorflow {
31 using boosted_trees::trees::DecisionTreeEnsembleConfig;
32 using test::AsTensor;
33
34 namespace boosted_trees {
35 namespace models {
36 namespace {
37
38 const int32 kNumThreadsMultiThreaded = 6;
39 const int32 kNumThreadsSingleThreaded = 1;
40
41 class MultipleAdditiveTreesTest : public ::testing::Test {
42 protected:
MultipleAdditiveTreesTest()43 MultipleAdditiveTreesTest() : batch_features_(2) {
44 // Create a batch of two examples having one dense feature each.
45 // The shape of the dense matrix is therefore 2x1 as in one row per example
46 // and one column per feature per example.
47 auto dense_matrix = test::AsTensor<float>({7.0f, -2.0f}, {2, 1});
48 TF_EXPECT_OK(
49 batch_features_.Initialize({dense_matrix}, {}, {}, {}, {}, {}, {}));
50 }
51
52 boosted_trees::utils::BatchFeatures batch_features_;
53 };
54
TEST_F(MultipleAdditiveTreesTest,Empty)55 TEST_F(MultipleAdditiveTreesTest, Empty) {
56 // Create empty tree ensemble.
57 DecisionTreeEnsembleConfig tree_ensemble_config;
58 auto output_tensor = AsTensor<float>({9.0f, 23.0f}, {2, 1});
59 auto output_matrix = output_tensor.matrix<float>();
60
61 // Predict for both instances.
62 tensorflow::thread::ThreadPool threads(tensorflow::Env::Default(), "test",
63 kNumThreadsSingleThreaded);
64 MultipleAdditiveTrees::Predict(tree_ensemble_config, {}, batch_features_,
65 &threads, output_matrix,
66 /*output_leaf_index=*/nullptr);
67 EXPECT_EQ(0, output_matrix(0, 0));
68 EXPECT_EQ(0, output_matrix(1, 0));
69 }
70
TEST_F(MultipleAdditiveTreesTest,SingleClass)71 TEST_F(MultipleAdditiveTreesTest, SingleClass) {
72 // Add one bias and one stump to ensemble for a single class.
73 DecisionTreeEnsembleConfig tree_ensemble_config;
74 auto* tree1 = tree_ensemble_config.add_trees();
75 auto* bias_leaf = tree1->add_nodes()->mutable_leaf()->mutable_sparse_vector();
76 bias_leaf->add_index(0);
77 bias_leaf->add_value(-0.4f);
78 auto* tree2 = tree_ensemble_config.add_trees();
79 auto* dense_split = tree2->add_nodes()->mutable_dense_float_binary_split();
80 dense_split->set_feature_column(0);
81 dense_split->set_threshold(5.0f);
82 dense_split->set_left_id(1);
83 dense_split->set_right_id(2);
84 auto* leaf1 = tree2->add_nodes()->mutable_leaf()->mutable_sparse_vector();
85 leaf1->add_index(0);
86 leaf1->add_value(0.9f);
87 auto* leaf2 = tree2->add_nodes()->mutable_leaf()->mutable_sparse_vector();
88 leaf2->add_index(0);
89 leaf2->add_value(0.2f);
90
91 tree_ensemble_config.add_tree_weights(1.0);
92 tree_ensemble_config.add_tree_weights(1.0);
93
94 auto output_tensor = AsTensor<float>({0.0f, 0.0f}, {2, 1});
95 auto output_matrix = output_tensor.matrix<float>();
96
97 tensorflow::thread::ThreadPool threads(tensorflow::Env::Default(), "test",
98 kNumThreadsSingleThreaded);
99
100 // Normal case.
101 {
102 MultipleAdditiveTrees::Predict(tree_ensemble_config, {0, 1},
103 batch_features_, &threads, output_matrix,
104 /*output_leaf_index=*/nullptr);
105 EXPECT_FLOAT_EQ(-0.2f, output_matrix(0, 0)); // -0.4 (bias) + 0.2 (leaf 2).
106 EXPECT_FLOAT_EQ(0.5f, output_matrix(1, 0)); // -0.4 (bias) + 0.9 (leaf 1).
107 }
108 // Normal case with leaf node.
109 {
110 // Initialize output leaf index tensor, since leaf index is positive in this
111 // case, initialize with the value of -1. Since there are 2 examples and
112 // there are 2 trees, initialize leaf output index by 2 * 2.
113 Tensor output_leaf_index_tensor(DT_INT32, TensorShape({2, 2}));
114 MultipleAdditiveTrees::Predict(tree_ensemble_config, {0, 1},
115 batch_features_, &threads, output_matrix,
116 &output_leaf_index_tensor);
117 EXPECT_FLOAT_EQ(-0.2f, output_matrix(0, 0)); // -0.4 (bias) + 0.2 (leaf 2).
118 EXPECT_FLOAT_EQ(0.5f, output_matrix(1, 0)); // -0.4 (bias) + 0.9 (leaf 1).
119 EXPECT_FLOAT_EQ(0, output_leaf_index_tensor.matrix<int>()(
120 0, 0)); // 1st leaf for the first example
121 EXPECT_FLOAT_EQ(0, output_leaf_index_tensor.matrix<int>()(
122 1, 0)); // 1st leaf for the second example
123 EXPECT_FLOAT_EQ(2, output_leaf_index_tensor.matrix<int>()(
124 0, 1)); // 2nd leaf for the first example
125 EXPECT_FLOAT_EQ(1, output_leaf_index_tensor.matrix<int>()(
126 1, 1)); // 2nd leaf for the second example
127 }
128 // Weighted case
129 {
130 DecisionTreeEnsembleConfig weighted = tree_ensemble_config;
131 weighted.set_tree_weights(0, 6.0);
132 weighted.set_tree_weights(1, 3.2);
133 MultipleAdditiveTrees::Predict(weighted, {0, 1}, batch_features_, &threads,
134 output_matrix, nullptr);
135 // -0.4 (bias) + 0.2 (leaf 2).
136 EXPECT_FLOAT_EQ(-0.4f * 6 + 0.2 * 3.2, output_matrix(0, 0));
137 // -0.4 (bias) + 0.9 (leaf 1).
138 EXPECT_FLOAT_EQ(-0.4f * 6 + 0.9 * 3.2, output_matrix(1, 0));
139 }
140 // Drop first tree.
141 {
142 MultipleAdditiveTrees::Predict(tree_ensemble_config, {1}, batch_features_,
143 &threads, output_matrix, nullptr);
144 EXPECT_FLOAT_EQ(0.2f, output_matrix(0, 0)); // 0.2 (leaf 2).
145 EXPECT_FLOAT_EQ(0.9f, output_matrix(1, 0)); // 0.9 (leaf 1).
146 }
147 // Drop second tree.
148 {
149 MultipleAdditiveTrees::Predict(tree_ensemble_config, {0}, batch_features_,
150 &threads, output_matrix, nullptr);
151 EXPECT_FLOAT_EQ(-0.4f, output_matrix(0, 0)); // -0.4 (bias).
152 EXPECT_FLOAT_EQ(-0.4f, output_matrix(1, 0)); // -0.4 (bias).
153 }
154 // Drop all trees.
155 {
156 MultipleAdditiveTrees::Predict(tree_ensemble_config, {}, batch_features_,
157 &threads, output_matrix, nullptr);
158 EXPECT_FLOAT_EQ(0.0, output_matrix(0, 0));
159 EXPECT_FLOAT_EQ(0.0, output_matrix(1, 0));
160 }
161 }
162
TEST_F(MultipleAdditiveTreesTest,MultiClass)163 TEST_F(MultipleAdditiveTreesTest, MultiClass) {
164 // Add one bias and one stump to ensemble for two classes.
165 DecisionTreeEnsembleConfig tree_ensemble_config;
166 auto* tree1 = tree_ensemble_config.add_trees();
167 auto* bias_leaf = tree1->add_nodes()->mutable_leaf()->mutable_sparse_vector();
168 bias_leaf->add_index(0);
169 bias_leaf->add_value(-0.4f);
170 bias_leaf->add_index(1);
171 bias_leaf->add_value(-0.7f);
172 auto* tree2 = tree_ensemble_config.add_trees();
173 auto* dense_split = tree2->add_nodes()->mutable_dense_float_binary_split();
174 dense_split->set_feature_column(0);
175 dense_split->set_threshold(5.0f);
176 dense_split->set_left_id(1);
177 dense_split->set_right_id(2);
178 auto* leaf1 = tree2->add_nodes()->mutable_leaf()->mutable_sparse_vector();
179 leaf1->add_index(0);
180 leaf1->add_value(0.9f);
181 auto* leaf2 = tree2->add_nodes()->mutable_leaf()->mutable_sparse_vector();
182 leaf2->add_index(1);
183 leaf2->add_value(0.2f);
184
185 tree_ensemble_config.add_tree_weights(1.0);
186 tree_ensemble_config.add_tree_weights(1.0);
187
188 // Predict for both instances.
189 tensorflow::thread::ThreadPool threads(tensorflow::Env::Default(), "test",
190 kNumThreadsSingleThreaded);
191 auto output_tensor = AsTensor<float>({0.0f, 0.0f, 0.0f, 0.0f}, {2, 2});
192 auto output_matrix = output_tensor.matrix<float>();
193
194 // Normal case.
195 {
196 MultipleAdditiveTrees::Predict(tree_ensemble_config, {0, 1},
197 batch_features_, &threads, output_matrix,
198 nullptr);
199 EXPECT_FLOAT_EQ(-0.4f, output_matrix(0, 0)); // -0.4 (bias)
200 EXPECT_FLOAT_EQ(-0.5f, output_matrix(0, 1)); // -0.7 (bias) + 0.2 (leaf 2)
201 EXPECT_FLOAT_EQ(0.5f, output_matrix(1, 0)); // -0.4 (bias) + 0.9 (leaf 1)
202 EXPECT_FLOAT_EQ(-0.7f, output_matrix(1, 1)); // -0.7 (bias)
203 }
204 // Weighted case.
205 {
206 DecisionTreeEnsembleConfig weighted = tree_ensemble_config;
207 weighted.set_tree_weights(0, 6.0);
208 weighted.set_tree_weights(1, 3.2);
209 MultipleAdditiveTrees::Predict(weighted, {0, 1}, batch_features_, &threads,
210 output_matrix, nullptr);
211 // bias
212 EXPECT_FLOAT_EQ(-0.4f * 6, output_matrix(0, 0));
213 // bias + leaf 2
214 EXPECT_FLOAT_EQ(-0.7f * 6 + 0.2f * 3.2, output_matrix(0, 1));
215 // bias + leaf 2
216 EXPECT_FLOAT_EQ(-0.4f * 6 + 0.9f * 3.2f, output_matrix(1, 0));
217 // bias
218 EXPECT_FLOAT_EQ(-0.7f * 6, output_matrix(1, 1));
219 }
220 // Dropout first tree.
221 {
222 MultipleAdditiveTrees::Predict(tree_ensemble_config, {1}, batch_features_,
223 &threads, output_matrix, nullptr);
224 EXPECT_FLOAT_EQ(0.0, output_matrix(0, 0));
225 EXPECT_FLOAT_EQ(0.2f, output_matrix(0, 1)); // 0.2 (leaf 2)
226 EXPECT_FLOAT_EQ(0.9f, output_matrix(1, 0)); // 0.9 (leaf 2)
227 EXPECT_FLOAT_EQ(0.0f, output_matrix(1, 1));
228 }
229 // Dropout second tree.
230 {
231 MultipleAdditiveTrees::Predict(tree_ensemble_config, {0}, batch_features_,
232 &threads, output_matrix, nullptr);
233 EXPECT_FLOAT_EQ(-0.4f, output_matrix(0, 0)); // -0.4 (bias)
234 EXPECT_FLOAT_EQ(-0.7f, output_matrix(0, 1)); // -0.7 (bias)
235 EXPECT_FLOAT_EQ(-0.4f, output_matrix(1, 0)); // -0.4 (bias)
236 EXPECT_FLOAT_EQ(-0.7f, output_matrix(1, 1)); // -0.7 (bias)
237 }
238 // Drop both trees.
239 {
240 MultipleAdditiveTrees::Predict(tree_ensemble_config, {}, batch_features_,
241 &threads, output_matrix, nullptr);
242 EXPECT_FLOAT_EQ(0.0f, output_matrix(0, 0));
243 EXPECT_FLOAT_EQ(0.0f, output_matrix(0, 1));
244 EXPECT_FLOAT_EQ(0.0f, output_matrix(1, 0));
245 EXPECT_FLOAT_EQ(0.0f, output_matrix(1, 1));
246 }
247 }
248
TEST_F(MultipleAdditiveTreesTest,DenseLeaves)249 TEST_F(MultipleAdditiveTreesTest, DenseLeaves) {
250 DecisionTreeEnsembleConfig tree_ensemble_config;
251 auto* tree1 = tree_ensemble_config.add_trees();
252 auto* bias_leaf = tree1->add_nodes()->mutable_leaf()->mutable_vector();
253 bias_leaf->add_value(-0.4f);
254 bias_leaf->add_value(-0.7f);
255 bias_leaf->add_value(3.0f);
256 auto* tree2 = tree_ensemble_config.add_trees();
257 auto* dense_split = tree2->add_nodes()->mutable_dense_float_binary_split();
258 dense_split->set_feature_column(0);
259 dense_split->set_threshold(5.0f);
260 dense_split->set_left_id(1);
261 dense_split->set_right_id(2);
262 auto* leaf1 = tree2->add_nodes()->mutable_leaf()->mutable_vector();
263 leaf1->add_value(0.9f);
264 leaf1->add_value(0.8f);
265 leaf1->add_value(0.7f);
266 auto* leaf2 = tree2->add_nodes()->mutable_leaf()->mutable_vector();
267 leaf2->add_value(0.2f);
268 leaf2->add_value(0.3f);
269 leaf2->add_value(0.4f);
270
271 tree_ensemble_config.add_tree_weights(1.0);
272 tree_ensemble_config.add_tree_weights(1.0);
273
274 // Predict for both instances.
275 tensorflow::thread::ThreadPool threads(tensorflow::Env::Default(), "test",
276 kNumThreadsSingleThreaded);
277 auto output_tensor =
278 AsTensor<float>({0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, {2, 3});
279 auto output_matrix = output_tensor.matrix<float>();
280
281 // Normal case.
282 {
283 MultipleAdditiveTrees::Predict(tree_ensemble_config, {0, 1},
284 batch_features_, &threads, output_matrix,
285 nullptr);
286 EXPECT_FLOAT_EQ(-0.2f, output_matrix(0, 0)); // -0.4 (tree1) + 0.2 (leaf 2)
287 EXPECT_FLOAT_EQ(-0.4f, output_matrix(0, 1)); // -0.7 (tree1) + 0.3 (leaf 2)
288 EXPECT_FLOAT_EQ(3.4f, output_matrix(0, 2)); // 3.0 -(tree1) + 0.4 (leaf 2)
289 EXPECT_FLOAT_EQ(0.5f, output_matrix(1, 0)); // -0.4 (tree1) + 0.9 (leaf 1)
290 EXPECT_FLOAT_EQ(0.1f, output_matrix(1, 1)); // -0.7 (tree1) + 0.8 (leaf 1)
291 EXPECT_FLOAT_EQ(3.7f, output_matrix(1, 2)); // 3.0 (tree1) + 0.7 (leaf 1)
292 }
293 }
294
295 } // namespace
296 } // namespace models
297 } // namespace boosted_trees
298 } // namespace tensorflow
299