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