From 6e86e4ec73cedd3c77f304f54a7ffff6eb22f8cd Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Tue, 11 Jul 2023 01:43:10 -0700 Subject: [PATCH 01/50] WIP: Add SHAP contributions and interactions add weights to GbtDecisionTree Include TreeShap recursion steps fix buffer overflow in memcpy Add cover to GbtDecisionTree from model builder fix some index offsets, correct results for trees up to depth=5 fix: nodeIsDummyLeaf is supposed to check left child remove some debug statements chore: apply oneDAL code style predictContribution wrapper with template dispatching increase speed by reducing number of cache misses use thread-local result accessor backup commit with 13% speedup wrt xgboost add preShapContributions/predShapInteractions as function parameter Revert "introduce pred_contribs and pred_interactions SHAP options" This reverts commit 483aa5bfd62562e5f4bb061a8bad8721e4ffbc48. remove some debug content reset env_detect.cpp to origin/master remove std::vector test by introducing thread-local NumericTable Move treeshap into separate translation unit - caution: treeShap undefined in libonedal builds but segfaults Fix function arguments respect predShapContributions and predShapInteractions options and check for legal combinations tmp: work on pred_interactions --- .gitignore | 2 + ...sion_forest_classification_model_builder.h | 16 +- .../gbt_regression_model_builder.h | 16 +- .../gbt_regression_predict_types.h | 13 +- cpp/daal/include/services/error_indexes.h | 1 + .../src/algorithms/dtrees/dtrees_model.cpp | 13 +- .../src/algorithms/dtrees/dtrees_model_impl.h | 22 +- .../df_classification_model_builder.cpp | 12 +- .../gbt_classification_model_builder.cpp | 7 +- ...ication_predict_dense_default_batch_impl.i | 6 +- .../src/algorithms/dtrees/gbt/gbt_model.cpp | 63 ++-- .../algorithms/dtrees/gbt/gbt_model_impl.h | 82 ++++- .../gbt/gbt_predict_dense_default_impl.i | 20 +- .../gbt_regression_model_builder.cpp | 10 +- .../gbt_regression_predict_container.h | 2 +- ...ression_predict_dense_default_batch_impl.i | 301 +++++++++++++++--- .../gbt_regression_predict_kernel.h | 4 +- .../gbt_regression_predict_result_fpt.cpp | 18 +- .../gbt_regression_predict_types.cpp | 16 +- .../gbt/regression/gbt_regression_tree_impl.h | 4 +- ...gression_train_dense_default_oneapi_impl.i | 1 - .../src/algorithms/dtrees/gbt/treeshap.cpp | 92 ++++++ cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 182 +++++++++++ cpp/daal/src/services/error_handling.cpp | 1 + 24 files changed, 743 insertions(+), 161 deletions(-) create mode 100644 cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp create mode 100644 cpp/daal/src/algorithms/dtrees/gbt/treeshap.h diff --git a/.gitignore b/.gitignore index f61844615bf..68b50b090b9 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ bazel-* # CMake directories and cache CMakeFiles CMakeCache.txt + +docs/doxygen/daal/doxygen diff --git a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h index cef25464418..74f564aeac5 100644 --- a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h +++ b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h @@ -112,7 +112,8 @@ class DAAL_EXPORT ModelBuilder NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel) { NodeId resId; - _status |= addLeafNodeInternal(treeId, parentId, position, classLabel, resId); + const double cover = 0.0; // TODO: Add cover + _status |= addLeafNodeInternal(treeId, parentId, position, classLabel, cover, resId); services::throwIfPossible(_status); return resId; } @@ -128,7 +129,8 @@ class DAAL_EXPORT ModelBuilder NodeId addLeafNodeByProba(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba) { NodeId resId; - _status |= addLeafNodeByProbaInternal(treeId, parentId, position, proba, resId); + const double cover = 0.0; // TODO: Add cover + _status |= addLeafNodeByProbaInternal(treeId, parentId, position, proba, cover, resId); services::throwIfPossible(_status); return resId; } @@ -145,7 +147,8 @@ class DAAL_EXPORT ModelBuilder NodeId addSplitNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, const double featureValue) { NodeId resId; - _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, resId); + const double cover = 0.0; // TODO: Add cover + _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, cover, resId); services::throwIfPossible(_status); return resId; } @@ -184,11 +187,12 @@ class DAAL_EXPORT ModelBuilder services::Status _status; services::Status initialize(const size_t nClasses, const size_t nTrees); services::Status createTreeInternal(const size_t nNodes, TreeId & resId); - services::Status addLeafNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, NodeId & res); + services::Status addLeafNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, + const double cover, NodeId & res); services::Status addLeafNodeByProbaInternal(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba, - NodeId & res); + const double cover, NodeId & res); services::Status addSplitNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, - const double featureValue, NodeId & res); + const double featureValue, const double cover, NodeId & res); private: size_t _nClasses; diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h index 2a2dd4bcfe9..731f06567a7 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h @@ -107,12 +107,13 @@ class DAAL_EXPORT ModelBuilder * \param[in] parentId Parent node to which new node is added (use noParent for root node) * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] response Response value for leaf node to be predicted + * \param[in] cover Cover of the node (sum_hess) * \return Node identifier */ - NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response) + NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response, double cover) { NodeId resId; - _status |= addLeafNodeInternal(treeId, parentId, position, response, resId); + _status |= addLeafNodeInternal(treeId, parentId, position, response, cover, resId); services::throwIfPossible(_status); return resId; } @@ -124,13 +125,14 @@ class DAAL_EXPORT ModelBuilder * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] featureIndex Feature index for splitting * \param[in] featureValue Feature value for splitting + * \param[in] cover Cover of the node (sum_hess) * \param[in] defaultLeft Behaviour in case of missing values * \return Node identifier */ - NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, int defaultLeft = 0) + NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, double cover, int defaultLeft = 0) { NodeId resId; - _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, resId, defaultLeft); + _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, cover, resId, defaultLeft); services::throwIfPossible(_status); return resId; } @@ -157,9 +159,9 @@ class DAAL_EXPORT ModelBuilder services::Status _status; services::Status initialize(size_t nFeatures, size_t nIterations); services::Status createTreeInternal(size_t nNodes, TreeId & resId); - services::Status addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, NodeId & res); - services::Status addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, NodeId & res, - int defaultLeft); + services::Status addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, double cover, NodeId & res); + services::Status addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, double cover, + NodeId & res, int defaultLeft); services::Status convertModelInternal(); }; /** @} */ diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h index 8a9cc5da552..33691d6ac1a 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h @@ -104,9 +104,16 @@ namespace interface1 /* [Parameter source code] */ struct DAAL_EXPORT Parameter : public daal::algorithms::Parameter { - Parameter() : daal::algorithms::Parameter(), nIterations(0) {} - Parameter(const Parameter & o) : daal::algorithms::Parameter(o), nIterations(o.nIterations) {} - size_t nIterations; /*!< Number of iterations of the trained model to be uses for prediction*/ + Parameter() : daal::algorithms::Parameter(), nIterations(0), predShapContributions(false), predShapInteractions(false) {} + Parameter(const Parameter & o) + : daal::algorithms::Parameter(o), + nIterations(o.nIterations), + predShapContributions(o.predShapContributions), + predShapInteractions(o.predShapInteractions) + {} + size_t nIterations; /*!< Number of iterations of the trained model to be uses for prediction*/ + bool predShapContributions; /*!< Predict SHAP contributions */ + bool predShapInteractions; /*!< Predict SHAP interactions */ }; /* [Parameter source code] */ diff --git a/cpp/daal/include/services/error_indexes.h b/cpp/daal/include/services/error_indexes.h index 8d5ca7c79e7..9c9c634cd08 100644 --- a/cpp/daal/include/services/error_indexes.h +++ b/cpp/daal/include/services/error_indexes.h @@ -378,6 +378,7 @@ enum ErrorID // GBT error: -30000..-30099 ErrorGbtIncorrectNumberOfTrees = -30000, /*!< Number of trees in the model is not consistent with the number of classes */ ErrorGbtPredictIncorrectNumberOfIterations = -30001, /*!< Number of iterations value in GBT parameter is not consistent with the model */ + ErrorGbtPredictShapOptions = -30002, /*< For SHAP values, calculate either contributions or interactions, not both */ // Data management errors: -80001.. ErrorUserAllocatedMemory = -80001, /*!< Couldn't free memory allocated by user */ diff --git a/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp b/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp index 37a06dfe111..c2362a6f20a 100644 --- a/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp @@ -180,17 +180,19 @@ services::Status createTreeInternal(data_management::DataCollectionPtr & seriali return s; } -void setNode(DecisionTreeNode & node, int featureIndex, size_t classLabel) +void setNode(DecisionTreeNode & node, int featureIndex, size_t classLabel, double cover) { node.featureIndex = featureIndex; node.leftIndexOrClass = classLabel; + node.cover = cover; node.featureValueOrResponse = 0; } -void setNode(DecisionTreeNode & node, int featureIndex, double response) +void setNode(DecisionTreeNode & node, int featureIndex, double response, double cover) { node.featureIndex = featureIndex; node.leftIndexOrClass = 0; + node.cover = cover; node.featureValueOrResponse = response; } @@ -222,7 +224,7 @@ void setProbabilities(const size_t treeId, const size_t nodeId, const size_t res } services::Status addSplitNodeInternal(data_management::DataCollectionPtr & serializationData, size_t treeId, size_t parentId, size_t position, - size_t featureIndex, double featureValue, size_t & res, int defaultLeft) + size_t featureIndex, double featureValue, double cover, size_t & res, int defaultLeft) { const size_t noParent = static_cast(-1); services::Status s; @@ -243,6 +245,7 @@ services::Status addSplitNodeInternal(data_management::DataCollectionPtr & seria aNode[0].defaultLeft = defaultLeft; aNode[0].leftIndexOrClass = 0; aNode[0].featureValueOrResponse = featureValue; + aNode[0].cover = cover; nodeId = 0; } else if (aNode[parentId].featureIndex < 0) @@ -262,6 +265,7 @@ services::Status addSplitNodeInternal(data_management::DataCollectionPtr & seria aNode[nodeId].defaultLeft = defaultLeft; aNode[nodeId].leftIndexOrClass = 0; aNode[nodeId].featureValueOrResponse = featureValue; + aNode[nodeId].cover = cover; } } if ((aNode[parentId].leftIndexOrClass > 0) && (position == 0)) @@ -274,6 +278,7 @@ services::Status addSplitNodeInternal(data_management::DataCollectionPtr & seria aNode[nodeId].defaultLeft = defaultLeft; aNode[nodeId].leftIndexOrClass = 0; aNode[nodeId].featureValueOrResponse = featureValue; + aNode[nodeId].cover = cover; } } if ((aNode[parentId].leftIndexOrClass == 0) && (position == 0)) @@ -296,6 +301,7 @@ services::Status addSplitNodeInternal(data_management::DataCollectionPtr & seria aNode[nodeId].defaultLeft = defaultLeft; aNode[nodeId].leftIndexOrClass = 0; aNode[nodeId].featureValueOrResponse = featureValue; + aNode[nodeId].cover = cover; aNode[parentId].leftIndexOrClass = nodeId; if (((nodeId + 1) < nRows) && (aNode[nodeId + 1].featureIndex == __NODE_FREE_ID)) { @@ -332,6 +338,7 @@ services::Status addSplitNodeInternal(data_management::DataCollectionPtr & seria aNode[nodeId].defaultLeft = defaultLeft; aNode[nodeId].leftIndexOrClass = 0; aNode[nodeId].featureValueOrResponse = featureValue; + aNode[nodeId].cover = cover; } else { diff --git a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h index 0eee0e757f3..63fa01ce53b 100644 --- a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h @@ -53,6 +53,7 @@ struct DecisionTreeNode ClassIndexType leftIndexOrClass; //split: left node index, classification leaf: class index ModelFPType featureValueOrResponse; //split: feature value, regression tree leaf: response int defaultLeft; //split: if 1: go to the yes branch for missing value + double cover; //split: cover (sum_hess) of the node DAAL_FORCEINLINE bool isSplit() const { return featureIndex != -1; } ModelFPType featureValue() const { return featureValueOrResponse; } }; @@ -60,12 +61,13 @@ struct DecisionTreeNode class DecisionTreeTable : public data_management::AOSNumericTable { public: - DecisionTreeTable(size_t rowCount = 0) : data_management::AOSNumericTable(sizeof(DecisionTreeNode), 4, rowCount) + DecisionTreeTable(size_t rowCount = 0) : data_management::AOSNumericTable(sizeof(DecisionTreeNode), 5, rowCount) { setFeature(0, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, featureIndex)); setFeature(1, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, leftIndexOrClass)); setFeature(2, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, featureValueOrResponse)); setFeature(3, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, defaultLeft)); + setFeature(4, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, cover)); allocateDataMemory(); } }; @@ -339,19 +341,19 @@ ModelImplType & getModelRef(ModelTypePtr & modelPtr) services::Status createTreeInternal(data_management::DataCollectionPtr & serializationData, size_t nNodes, size_t & resId); -void setNode(DecisionTreeNode & node, int featureIndex, size_t classLabel); +void setNode(DecisionTreeNode & node, int featureIndex, size_t classLabel, double cover); -void setNode(DecisionTreeNode & node, int featureIndex, double response); +void setNode(DecisionTreeNode & node, int featureIndex, double response, double cover); services::Status addSplitNodeInternal(data_management::DataCollectionPtr & serializationData, size_t treeId, size_t parentId, size_t position, - size_t featureIndex, double featureValue, size_t & res, int defaultLeft = 0); + size_t featureIndex, double featureValue, double cover, size_t & res, int defaultLeft = 0); void setProbabilities(const size_t treeId, const size_t nodeId, const size_t response, const data_management::DataCollectionPtr probTbl, const double * const prob); template static services::Status addLeafNodeInternal(const data_management::DataCollectionPtr & serializationData, const size_t treeId, const size_t parentId, - const size_t position, ClassOrResponseType response, size_t & res, + const size_t position, ClassOrResponseType response, double cover, size_t & res, const data_management::DataCollectionPtr probTbl = data_management::DataCollectionPtr(), const double * const prob = nullptr, const size_t nClasses = 0) { @@ -373,7 +375,7 @@ static services::Status addLeafNodeInternal(const data_management::DataCollectio size_t nodeId = 0; if (parentId == noParent) { - setNode(aNode[0], -1, response); + setNode(aNode[0], -1, response, cover); setProbabilities(treeId, 0, response, probTbl, prob); nodeId = 0; } @@ -390,7 +392,7 @@ static services::Status addLeafNodeInternal(const data_management::DataCollectio nodeId = reservedId; if (aNode[reservedId].featureIndex == __NODE_RESERVED_ID) { - setNode(aNode[nodeId], -1, response); + setNode(aNode[nodeId], -1, response, cover); setProbabilities(treeId, nodeId, response, probTbl, prob); } } @@ -400,7 +402,7 @@ static services::Status addLeafNodeInternal(const data_management::DataCollectio nodeId = reservedId; if (aNode[reservedId].featureIndex == __NODE_RESERVED_ID) { - setNode(aNode[nodeId], -1, response); + setNode(aNode[nodeId], -1, response, cover); setProbabilities(treeId, nodeId, response, probTbl, prob); } } @@ -420,7 +422,7 @@ static services::Status addLeafNodeInternal(const data_management::DataCollectio { return services::Status(services::ErrorID::ErrorIncorrectParameter); } - setNode(aNode[nodeId], -1, response); + setNode(aNode[nodeId], -1, response, cover); setProbabilities(treeId, nodeId, response, probTbl, prob); aNode[parentId].leftIndexOrClass = nodeId; if (((nodeId + 1) < nRows) && (aNode[nodeId + 1].featureIndex == __NODE_FREE_ID)) @@ -454,7 +456,7 @@ static services::Status addLeafNodeInternal(const data_management::DataCollectio nodeId = leftEmptyId + 1; if (nodeId < nRows) { - setNode(aNode[nodeId], -1, response); + setNode(aNode[nodeId], -1, response, cover); setProbabilities(treeId, nodeId, response, probTbl, prob); } else diff --git a/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp index c801da2c4cb..140b5ca6790 100644 --- a/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp @@ -72,7 +72,7 @@ services::Status ModelBuilder::createTreeInternal(const size_t nNodes, TreeId & } services::Status ModelBuilder::addLeafNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, - NodeId & res) + const double cover, NodeId & res) { decision_forest::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); @@ -81,7 +81,7 @@ services::Status ModelBuilder::addLeafNodeInternal(const TreeId treeId, const No return services::Status(services::ErrorID::ErrorIncorrectParameter); } return daal::algorithms::dtrees::internal::addLeafNodeInternal(modelImplRef._serializationData, treeId, parentId, position, classLabel, - res, modelImplRef._probTbl); + cover, res, modelImplRef._probTbl); } bool checkProba(const double * const proba, const size_t nClasses) @@ -104,7 +104,7 @@ bool checkProba(const double * const proba, const size_t nClasses) } services::Status ModelBuilder::addLeafNodeByProbaInternal(const TreeId treeId, const NodeId parentId, const size_t position, - const double * const proba, NodeId & res) + const double * const proba, const double cover, NodeId & res) { decision_forest::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); @@ -112,17 +112,17 @@ services::Status ModelBuilder::addLeafNodeByProbaInternal(const TreeId treeId, c { return services::Status(services::ErrorID::ErrorIncorrectParameter); } - return daal::algorithms::dtrees::internal::addLeafNodeInternal(modelImplRef._serializationData, treeId, parentId, position, 0, res, + return daal::algorithms::dtrees::internal::addLeafNodeInternal(modelImplRef._serializationData, treeId, parentId, position, 0, cover, res, modelImplRef._probTbl, proba, _nClasses); } services::Status ModelBuilder::addSplitNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, - const double featureValue, NodeId & res) + const double featureValue, const double cover, NodeId & res) { decision_forest::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, res); + featureValue, cover, res); } } // namespace interface2 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp index 1c0462dd7a1..8b5d9afedfd 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp @@ -124,9 +124,9 @@ services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentI { gbt::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); + const double cover = 0.0; // TODO: Add cover return daal::algorithms::dtrees::internal::addLeafNodeInternal(modelImplRef._serializationData, treeId, parentId, position, response, - res); - ; + cover, res); } services::Status ModelBuilder::addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, @@ -134,8 +134,9 @@ services::Status ModelBuilder::addSplitNodeInternal(TreeId treeId, NodeId parent { gbt::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); + const double cover = 0.0; // TODO: Add cover return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, res, defaultLeft); + featureValue, cover, res, defaultLeft); } } // namespace interface1 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i index 0a79b9d040b..169a891c978 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i @@ -88,7 +88,7 @@ public: TArray expValPtr(nRows); algorithmFPType * expVal = expValPtr.get(); DAAL_CHECK_MALLOC(expVal); - s = super::runInternal(pHostApp, this->_res); + s = super::runInternal(pHostApp, this->_res, false, false); if (!s) return s; auto nBlocks = daal::threader_get_threads_number(); @@ -120,7 +120,7 @@ public: algorithmFPType * expVal = expValPtr.get(); NumericTablePtr expNT = HomogenNumericTableCPU::create(expVal, 1, nRows, &s); DAAL_CHECK_MALLOC(expVal); - s = super::runInternal(pHostApp, expNT.get()); + s = super::runInternal(pHostApp, expNT.get(), false, false); if (!s) return s; auto nBlocks = daal::threader_get_threads_number(); @@ -143,7 +143,7 @@ public: DAAL_CHECK_BLOCK_STATUS(resBD); const algorithmFPType label[2] = { algorithmFPType(1.), algorithmFPType(0.) }; algorithmFPType * res = resBD.get(); - s = super::runInternal(pHostApp, this->_res); + s = super::runInternal(pHostApp, this->_res, false, false); if (!s) return s; for (size_t iRow = 0; iRow < nRows; ++iRow) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp index 05974244d41..bb9ac86be75 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp @@ -23,7 +23,6 @@ #include "services/daal_defines.h" #include "src/algorithms/dtrees/gbt/gbt_model_impl.h" -#include "src/algorithms/dtrees/dtrees_model_impl_common.h" using namespace daal::data_management; using namespace daal::services; @@ -63,8 +62,8 @@ void ModelImpl::traverseDF(size_t iTree, algorithms::regression::TreeNodeVisitor const GbtDecisionTree & gbtTree = *at(iTree); - const gbt::prediction::internal::ModelFPType * splitPoints = gbtTree.getSplitPoints(); - const gbt::prediction::internal::FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); + const ModelFPType * splitPoints = gbtTree.getSplitPoints(); + const FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); auto onSplitNodeFunc = [&splitPoints, &splitFeatures, &visitor](size_t iRowInTable, size_t level) -> bool { return visitor.onSplitNode(level, splitFeatures[iRowInTable], splitPoints[iRowInTable]); @@ -83,8 +82,8 @@ void ModelImpl::traverseBF(size_t iTree, algorithms::regression::TreeNodeVisitor const GbtDecisionTree & gbtTree = *at(iTree); - const gbt::prediction::internal::ModelFPType * splitPoints = gbtTree.getSplitPoints(); - const gbt::prediction::internal::FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); + const ModelFPType * splitPoints = gbtTree.getSplitPoints(); + const FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); auto onSplitNodeFunc = [&splitFeatures, &splitPoints, &visitor](size_t iRowInTable, size_t level) -> bool { return visitor.onSplitNode(level, splitFeatures[iRowInTable], splitPoints[iRowInTable]); @@ -108,10 +107,10 @@ void ModelImpl::traverseBFS(size_t iTree, tree_utils::regression::TreeNodeVisito const GbtDecisionTree & gbtTree = *at(iTree); - const gbt::prediction::internal::ModelFPType * splitPoints = gbtTree.getSplitPoints(); - const gbt::prediction::internal::FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); - const int * nodeSamplesCount = getNodeSampleCount(iTree); - const double * imp = getImpVals(iTree); + const ModelFPType * splitPoints = gbtTree.getSplitPoints(); + const FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); + const int * nodeSamplesCount = getNodeSampleCount(iTree); + const double * imp = getImpVals(iTree); auto onSplitNodeFunc = [&splitFeatures, &splitPoints, &nodeSamplesCount, &imp, &visitor](size_t iRowInTable, size_t level) -> bool { tree_utils::SplitNodeDescriptor descSplit; @@ -148,10 +147,10 @@ void ModelImpl::traverseDFS(size_t iTree, tree_utils::regression::TreeNodeVisito const GbtDecisionTree & gbtTree = *at(iTree); - const gbt::prediction::internal::ModelFPType * splitPoints = gbtTree.getSplitPoints(); - const gbt::prediction::internal::FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); - const int * nodeSamplesCount = getNodeSampleCount(iTree); - const double * imp = getImpVals(iTree); + const ModelFPType * splitPoints = gbtTree.getSplitPoints(); + const FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); + const int * nodeSamplesCount = getNodeSampleCount(iTree); + const double * imp = getImpVals(iTree); auto onSplitNodeFunc = [&splitFeatures, &splitPoints, &nodeSamplesCount, &imp, &visitor](size_t iRowInTable, size_t level) -> bool { tree_utils::SplitNodeDescriptor descSplit; @@ -228,8 +227,8 @@ void ModelImpl::destroy() bool ModelImpl::nodeIsDummyLeaf(size_t idx, const GbtDecisionTree & gbtTree) { - const gbt::prediction::internal::ModelFPType * splitPoints = gbtTree.getSplitPoints(); - const gbt::prediction::internal::FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); + const ModelFPType * splitPoints = gbtTree.getSplitPoints(); + const FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); if (idx) { @@ -245,7 +244,7 @@ bool ModelImpl::nodeIsLeaf(size_t idx, const GbtDecisionTree & gbtTree, const si { return true; } - else if (nodeIsDummyLeaf(2 * idx + 1, gbtTree)) // check, that left son is dummy + else if (nodeIsDummyLeaf(2 * idx, gbtTree)) // check, that left son is dummy { return true; } @@ -270,9 +269,10 @@ void ModelImpl::decisionTreeToGbtTree(const DecisionTreeTable & tree, GbtDecisio NodeType * sons = sonsArr.data(); NodeType * parents = parentsArr.data(); - gbt::prediction::internal::ModelFPType * const spitPoints = newTree.getSplitPoints(); - gbt::prediction::internal::FeatureIndexType * const featureIndexes = newTree.getFeatureIndexesForSplit(); - int * const defaultLeft = newTree.getdefaultLeftForSplit(); + ModelFPType * const splitPoints = newTree.getSplitPoints(); + FeatureIndexType * const featureIndexes = newTree.getFeatureIndexesForSplit(); + ModelFPType * const nodeCoverValues = newTree.getNodeCoverValues(); + int * const defaultLeft = newTree.getDefaultLeftForSplit(); for (size_t i = 0; i < nSourceNodes; ++i) { @@ -293,21 +293,23 @@ void ModelImpl::decisionTreeToGbtTree(const DecisionTreeTable & tree, GbtDecisio if (p->isSplit()) { - sons[nSons++] = arr + p->leftIndexOrClass; - sons[nSons++] = arr + p->leftIndexOrClass + 1; - featureIndexes[idxInTable] = p->featureIndex; - defaultLeft[idxInTable] = p->defaultLeft; + sons[nSons++] = arr + p->leftIndexOrClass; + sons[nSons++] = arr + p->leftIndexOrClass + 1; + featureIndexes[idxInTable] = p->featureIndex; + nodeCoverValues[idxInTable] = p->cover; + defaultLeft[idxInTable] = p->defaultLeft; DAAL_ASSERT(featureIndexes[idxInTable] >= 0); - spitPoints[idxInTable] = p->featureValueOrResponse; + splitPoints[idxInTable] = p->featureValueOrResponse; } else { - sons[nSons++] = p; - sons[nSons++] = p; - featureIndexes[idxInTable] = 0; - defaultLeft[idxInTable] = 0; + sons[nSons++] = p; + sons[nSons++] = p; + featureIndexes[idxInTable] = 0; + nodeCoverValues[idxInTable] = p->cover; + defaultLeft[idxInTable] = 0; DAAL_ASSERT(featureIndexes[idxInTable] >= 0); - spitPoints[idxInTable] = p->featureValueOrResponse; + splitPoints[idxInTable] = p->featureValueOrResponse; } idxInTable++; @@ -325,6 +327,7 @@ services::Status ModelImpl::convertDecisionTreesToGbtTrees(data_management::Data DAAL_CHECK_MALLOC(newTrees) for (size_t i = 0; i < size; ++i) { + *(DecisionTreeTable *)(*(serializationData))[i].get(); const DecisionTreeTable & tree = *(DecisionTreeTable *)(*(serializationData))[i].get(); GbtDecisionTree * newTree = allocateGbtTree(tree); decisionTreeToGbtTree(tree, *newTree); @@ -351,7 +354,7 @@ void ModelImpl::getMaxLvl(const dtrees::internal::DecisionTreeNode * const arr, const GbtDecisionTree * ModelImpl::at(const size_t idx) const { - return (const GbtDecisionTree *)(*super::_serializationData)[idx].get(); + return static_cast((*super::_serializationData)[idx].get()); } } // namespace internal diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h index f56b4fc3126..eacc9d24d53 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h @@ -26,7 +26,6 @@ #include "src/algorithms/dtrees/dtrees_model_impl.h" #include "algorithms/regression/tree_traverse.h" -#include "src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i" #include "algorithms/tree_utils/tree_utils_regression.h" #include "src/algorithms/dtrees/dtrees_model_impl_common.h" #include "src/services/service_arrays.h" @@ -41,6 +40,10 @@ namespace gbt { namespace internal { +typedef uint32_t FeatureIndexType; +typedef float ModelFPType; +typedef services::Collection NodeIdxArray; + static inline size_t getNumberOfNodesByLvls(const size_t nLvls) { size_t nNodes = 2; // nNodes = pow(2, nLvl+1) - 1 @@ -61,8 +64,9 @@ class GbtDecisionTree : public SerializationIface { public: DECLARE_SERIALIZABLE(); - using SplitPointType = HomogenNumericTable; - using FeatureIndexesForSplitType = HomogenNumericTable; + using SplitPointType = HomogenNumericTable; + using NodeCoverType = HomogenNumericTable; + using FeatureIndexesForSplitType = HomogenNumericTable; using defaultLeftForSplitType = HomogenNumericTable; GbtDecisionTree(const size_t nNodes, const size_t maxLvl, const size_t sourceNumOfNodes) @@ -71,33 +75,51 @@ class GbtDecisionTree : public SerializationIface _sourceNumOfNodes(sourceNumOfNodes), _splitPoints(SplitPointType::create(1, nNodes, NumericTableIface::doAllocate)), _featureIndexes(FeatureIndexesForSplitType::create(1, nNodes, NumericTableIface::doAllocate)), - _defaultLeft(defaultLeftForSplitType::create(1, nNodes, NumericTableIface::doAllocate)) + _nodeCoverValues(NodeCoverType::create(1, nNodes, NumericTableIface::doAllocate)), + _defaultLeft(defaultLeftForSplitType::create(1, nNodes, NumericTableIface::doAllocate)), + nNodeSplitFeature(), + CoverFeature(), + GainFeature() {} - // for serailization only + // for serialization only GbtDecisionTree() : _nNodes(0), _maxLvl(0), _sourceNumOfNodes(0) {} - gbt::prediction::internal::ModelFPType * getSplitPoints() { return _splitPoints->getArray(); } + ModelFPType * getSplitPoints() { return _splitPoints->getArray(); } + + FeatureIndexType * getFeatureIndexesForSplit() { return _featureIndexes->getArray(); } + + int * getDefaultLeftForSplit() { return _defaultLeft->getArray(); } - gbt::prediction::internal::FeatureIndexType * getFeatureIndexesForSplit() { return _featureIndexes->getArray(); } + const ModelFPType * getSplitPoints() const { return _splitPoints->getArray(); } - int * getdefaultLeftForSplit() { return _defaultLeft->getArray(); } + const FeatureIndexType * getFeatureIndexesForSplit() const { return _featureIndexes->getArray(); } - const gbt::prediction::internal::ModelFPType * getSplitPoints() const { return _splitPoints->getArray(); } + ModelFPType * getNodeCoverValues() { return _nodeCoverValues->getArray(); } - const gbt::prediction::internal::FeatureIndexType * getFeatureIndexesForSplit() const { return _featureIndexes->getArray(); } + const ModelFPType * getNodeCoverValues() const { return _nodeCoverValues->getArray(); } - const int * getdefaultLeftForSplit() const { return _defaultLeft->getArray(); } + const int * getDefaultLeftForSplit() const { return _defaultLeft->getArray(); } size_t getNumberOfNodes() const { return _nNodes; } size_t * getArrayNumSplitFeature() { return nNodeSplitFeature.data(); } + const size_t * getArrayNumSplitFeature() const { return nNodeSplitFeature.data(); } + size_t * getArrayCoverFeature() { return CoverFeature.data(); } + const size_t * getArrayCoverFeature() const { return CoverFeature.data(); } + + services::Collection getCoverFeature() { return CoverFeature; } + + const services::Collection & getCoverFeature() const { return CoverFeature; } + double * getArrayGainFeature() { return GainFeature.data(); } - gbt::prediction::internal::FeatureIndexType getMaxLvl() const { return _maxLvl; } + const double * getArrayGainFeature() const { return GainFeature.data(); } + + FeatureIndexType getMaxLvl() const { return _maxLvl; } // recursive build of tree (breadth-first) template @@ -113,8 +135,8 @@ class GbtDecisionTree : public SerializationIface int result = 0; - gbt::prediction::internal::ModelFPType * const spitPoints = tree->getSplitPoints(); - gbt::prediction::internal::FeatureIndexType * const featureIndexes = tree->getFeatureIndexesForSplit(); + ModelFPType * const splitPoints = tree->getSplitPoints(); + FeatureIndexType * const featureIndexes = tree->getFeatureIndexesForSplit(); for (size_t i = 0; i < nNodes; ++i) { @@ -163,7 +185,7 @@ class GbtDecisionTree : public SerializationIface DAAL_ASSERT(featureIndexes[idxInTable] >= 0); nNodeSamplesVals[idxInTable] = (int)p->count; impVals[idxInTable] = p->impurity; - spitPoints[idxInTable] = p->featureValue; + splitPoints[idxInTable] = p->featureValue; idxInTable++; } @@ -187,6 +209,7 @@ class GbtDecisionTree : public SerializationIface arch->setSharedPtrObj(_splitPoints); arch->setSharedPtrObj(_featureIndexes); + arch->setSharedPtrObj(_nodeCoverValues); arch->setSharedPtrObj(_defaultLeft); return services::Status(); @@ -194,10 +217,11 @@ class GbtDecisionTree : public SerializationIface protected: size_t _nNodes; - gbt::prediction::internal::FeatureIndexType _maxLvl; + FeatureIndexType _maxLvl; size_t _sourceNumOfNodes; services::SharedPtr _splitPoints; services::SharedPtr _featureIndexes; + services::SharedPtr _nodeCoverValues; services::SharedPtr _defaultLeft; services::Collection nNodeSplitFeature; services::Collection CoverFeature; @@ -264,6 +288,29 @@ using TreeImpRegression = GbtTreeImpl > > using TreeImpClassification = GbtTreeImpl, Allocator>; +struct DecisionTreeNode +{ + size_t dimension; + size_t leftIndexOrClass; + double cutPointOrDependantVariable; +}; + +class DecisionTreeTable : public data_management::AOSNumericTable +{ +public: + DecisionTreeTable(size_t rowCount, services::Status & st) : data_management::AOSNumericTable(sizeof(DecisionTreeNode), 3, rowCount, st) + { + setFeature(0, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, dimension)); + setFeature(1, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, leftIndexOrClass)); + setFeature(2, DAAL_STRUCT_MEMBER_OFFSET(DecisionTreeNode, cutPointOrDependantVariable)); + st |= allocateDataMemory(); + } + DecisionTreeTable(services::Status & st) : DecisionTreeTable(0, st) {} +}; + +typedef services::SharedPtr DecisionTreeTablePtr; +typedef services::SharedPtr DecisionTreeTableConstPtr; + class ModelImpl : protected dtrees::internal::ModelImpl { public: @@ -292,9 +339,10 @@ class ModelImpl : protected dtrees::internal::ModelImpl static services::Status treeToTable(TreeType & t, gbt::internal::GbtDecisionTree ** pTbl, HomogenNumericTable ** pTblImp, HomogenNumericTable ** pTblSmplCnt, size_t nFeature); -protected: static bool nodeIsDummyLeaf(size_t idx, const GbtDecisionTree & gbtTree); static bool nodeIsLeaf(size_t idx, const GbtDecisionTree & gbtTree, const size_t lvl); + +protected: static size_t getIdxOfParent(const size_t sonIdx); static void getMaxLvl(const dtrees::internal::DecisionTreeNode * const arr, const size_t idx, size_t & maxLvl, size_t curLvl = 0); diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i index 18976987b37..6996f3e9f10 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i @@ -25,14 +25,15 @@ #ifndef __GBT_PREDICT_DENSE_DEFAULT_IMPL_I__ #define __GBT_PREDICT_DENSE_DEFAULT_IMPL_I__ +#include "data_management/data/internal/finiteness_checker.h" +#include "src/algorithms/dtrees/dtrees_feature_type_helper.h" #include "src/algorithms/dtrees/dtrees_model_impl.h" -#include "src/algorithms/dtrees/dtrees_train_data_helper.i" #include "src/algorithms/dtrees/dtrees_predict_dense_default_impl.i" -#include "src/algorithms/dtrees/dtrees_feature_type_helper.h" +#include "src/algorithms/dtrees/dtrees_train_data_helper.i" #include "src/algorithms/dtrees/gbt/gbt_internal.h" -#include "src/services/service_environment.h" +#include "src/algorithms/dtrees/gbt/gbt_model_impl.h" #include "src/services/service_defines.h" -#include "data_management/data/internal/finiteness_checker.h" +#include "src/services/service_environment.h" namespace daal { @@ -44,8 +45,9 @@ namespace prediction { namespace internal { -typedef float ModelFPType; -typedef uint32_t FeatureIndexType; +using gbt::internal::ModelFPType; +using gbt::internal::FeatureIndexType; +// typedef gbt::internal::FeatureIndexType FeatureIndexType; template struct PredictDispatcher @@ -101,7 +103,7 @@ inline void predictForTreeVector(const DecisionTreeType & t, const FeatureTypes { const ModelFPType * const values = t.getSplitPoints() - 1; const FeatureIndexType * const fIndexes = t.getFeatureIndexesForSplit() - 1; - const int * const defaultLeft = t.getdefaultLeftForSplit() - 1; + const int * const defaultLeft = t.getDefaultLeftForSplit() - 1; const FeatureIndexType nFeat = featTypes.getNumberOfFeatures(); FeatureIndexType i[vectorBlockSize]; @@ -135,7 +137,7 @@ inline algorithmFPType predictForTree(const DecisionTreeType & t, const FeatureT { const ModelFPType * const values = (const ModelFPType *)t.getSplitPoints() - 1; const FeatureIndexType * const fIndexes = t.getFeatureIndexesForSplit() - 1; - const int * const defaultLeft = t.getdefaultLeftForSplit() - 1; + const int * const defaultLeft = t.getDefaultLeftForSplit() - 1; const FeatureIndexType maxLvl = t.getMaxLvl(); @@ -167,7 +169,7 @@ struct TileDimensions static constexpr size_t minVectorBlockSizeFactor = 2; static constexpr size_t vectorBlockSizeStep = 16; // optimalBlockSizeFactor is selected from benchmarking - static constexpr size_t optimalBlockSizeFactor = 3; + static constexpr size_t optimalBlockSizeFactor = 5; TileDimensions(const NumericTable & data, size_t nTrees, size_t nNodes) : nTreesTotal(nTrees), nRowsTotal(data.getNumberOfRows()), nCols(data.getNumberOfColumns()) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp index aefc88d2ab0..7b5df3313fa 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp @@ -71,21 +71,21 @@ services::Status ModelBuilder::createTreeInternal(size_t nNodes, TreeId & resId) return daal::algorithms::dtrees::internal::createTreeInternal(modelImplRef._serializationData, nNodes, resId); } -services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, NodeId & res) -{ +services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, double cover, NodeId & res) +{ gbt::regression::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addLeafNodeInternal(modelImplRef._serializationData, treeId, parentId, position, response, - res); + cover, res); } services::Status ModelBuilder::addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, - NodeId & res, int defaultLeft) + double cover, NodeId & res, int defaultLeft) { gbt::regression::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, res, defaultLeft); + featureValue, cover, res, defaultLeft); } } // namespace interface1 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h index 8e3a231e1eb..0a63fd0aac4 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h @@ -62,7 +62,7 @@ services::Status BatchContainer::compute() daal::services::Environment::env & env = *_env; __DAAL_CALL_KERNEL(env, internal::PredictKernel, __DAAL_KERNEL_ARGUMENTS(algorithmFPType, method), compute, - daal::services::internal::hostApp(*input), a, m, r, par->nIterations); + daal::services::internal::hostApp(*input), a, m, r, par->nIterations, par->predShapContributions, par->predShapInteractions); } } // namespace prediction diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index cd2410e1692..391a733a020 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -26,15 +26,18 @@ #include "algorithms/algorithm.h" #include "data_management/data/numeric_table.h" -#include "src/algorithms/dtrees/gbt/regression/gbt_regression_predict_kernel.h" -#include "src/threading/threading.h" #include "services/daal_defines.h" +#include "src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i" #include "src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h" -#include "src/data_management/service_numeric_table.h" +#include "src/algorithms/dtrees/gbt/regression/gbt_regression_predict_kernel.h" +#include "src/algorithms/dtrees/gbt/treeshap.h" +#include "src/algorithms/dtrees/regression/dtrees_regression_predict_dense_default_impl.i" #include "src/algorithms/service_error_handling.h" +#include "src/data_management/service_numeric_table.h" #include "src/externals/service_memory.h" -#include "src/algorithms/dtrees/regression/dtrees_regression_predict_dense_default_impl.i" -#include "src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i" +#include "src/threading/threading.h" + +#include // TODO: remove using namespace daal::internal; using namespace daal::services::internal; @@ -62,40 +65,42 @@ public: typedef gbt::internal::GbtDecisionTree TreeType; typedef gbt::prediction::internal::TileDimensions DimType; PredictRegressionTask(const NumericTable * x, NumericTable * y) : _data(x), _res(y) {} - services::Status run(const gbt::regression::internal::ModelImpl * m, size_t nIterations, services::HostAppIface * pHostApp); + + services::Status run(const gbt::regression::internal::ModelImpl * m, size_t nIterations, services::HostAppIface * pHostApp, + bool predShapContributions, bool predShapInteractions); protected: template using dispatcher_t = gbt::prediction::internal::PredictDispatcher; - services::Status runInternal(services::HostAppIface * pHostApp, NumericTable * result); + services::Status runInternal(services::HostAppIface * pHostApp, NumericTable * result, bool predShapContributions, bool predShapInteractions); template algorithmFPType predictByTrees(size_t iFirstTree, size_t nTrees, const algorithmFPType * x, const dispatcher_t & dispatcher); template void predictByTreesVector(size_t iFirstTree, size_t nTrees, const algorithmFPType * x, algorithmFPType * res, - const dispatcher_t & dispatcher); + const dispatcher_t & dispatcher, size_t resIncrement); inline size_t getNumberOfNodes(size_t nTrees) { size_t nNodesTotal = 0; for (size_t iTree = 0; iTree < nTrees; ++iTree) { - nNodesTotal += this->_aTree[iTree]->getNumberOfNodes(); + nNodesTotal += _aTree[iTree]->getNumberOfNodes(); } return nNodesTotal; } - inline bool checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns) + inline bool checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns) const { size_t nLvlTotal = 0; for (size_t iTree = 0; iTree < nTrees; ++iTree) { - nLvlTotal += this->_aTree[iTree]->getMaxLvl(); + nLvlTotal += _aTree[iTree]->getMaxLvl(); } if (nLvlTotal <= nColumns) { - // Checking is compicated. Better to do it during inferense. + // Checking is complicated. Better to do it during inference return true; } else @@ -109,44 +114,118 @@ protected: } template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, + size_t resIncrement) { size_t iRow; dispatcher_t dispatcher; for (iRow = 0; iRow + vectorBlockSize <= nRows; iRow += vectorBlockSize) { - predictByTreesVector(iTree, nTrees, x + iRow * nColumns, res + iRow, dispatcher); + predictByTreesVector(iTree, nTrees, x + iRow * nColumns, res + iRow, dispatcher, + resIncrement); } for (; iRow < nRows; ++iRow) { - res[iRow] += predictByTrees(iTree, nTrees, x + iRow * nColumns, dispatcher); + // result goes into final columns of current row + const size_t lastColumn = (iRow + 1) * resIncrement - 1; + res[lastColumn] += predictByTrees(iTree, nTrees, x + iRow * nColumns, dispatcher); } } template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, + size_t resIncrement) { - if (this->_featHelper.hasUnorderedFeatures()) + if (_featHelper.hasUnorderedFeatures()) { - predict(iTree, nTrees, nRows, nColumns, x, res); + predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); } else { - predict(iTree, nTrees, nRows, nColumns, x, res); + predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); } } template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, + size_t resIncrement) { const bool hasAnyMissing = checkForMissing(x, nTrees, nRows, nColumns); if (hasAnyMissing) { - predict(iTree, nTrees, nRows, nColumns, x, res); + predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + } + else + { + predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + } + } + + template + inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim); + + template + inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim) + { + if (_featHelper.hasUnorderedFeatures()) + { + predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + } + else + { + predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + } + } + + // TODO: Add vectorBlockSize templates, similar to predict + // template + inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim) + { + const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); + if (hasAnyMissing) + { + predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + } + else + { + predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + } + } + + template + inline void predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim); + + template + inline void predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim) + { + if (_featHelper.hasUnorderedFeatures()) + { + predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + } + else + { + predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + } + } + + // TODO: Add vectorBlockSize templates, similar to predict + // template + inline void predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim) + { + const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); + if (hasAnyMissing) + { + predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); } else { - predict(iTree, nTrees, nRows, nColumns, x, res); + predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); } } @@ -159,39 +238,39 @@ protected: // Recursivelly checking template parameter until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor. template inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - const DimType & dim, BooleanConstant keepLooking) + const DimType & dim, BooleanConstant keepLooking, size_t resIncrement) { constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; if (dim.vectorBlockSizeFactor == vectorBlockSizeFactor) { - predict(iTree, nTrees, nRows, nColumns, x, res); + predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); } else { predict(iTree, nTrees, nRows, nColumns, x, res, dim, - BooleanConstant()); + BooleanConstant(), resIncrement); } } template inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - const DimType & dim, BooleanConstant keepLooking) + const DimType & dim, BooleanConstant keepLooking, size_t resIncrement) { constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; - predict(iTree, nTrees, nRows, nColumns, x, res); + predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); } inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - const DimType & dim) + const DimType & dim, size_t resIncrement) { constexpr size_t maxVectorBlockSizeFactor = DimType::maxVectorBlockSizeFactor; if (maxVectorBlockSizeFactor > 1) { - predict(iTree, nTrees, nRows, nColumns, x, res, dim, BooleanConstant()); + predict(iTree, nTrees, nRows, nColumns, x, res, dim, BooleanConstant(), resIncrement); } else { - predict(iTree, nTrees, nRows, nColumns, x, res, dim, BooleanConstant()); + predict(iTree, nTrees, nRows, nColumns, x, res, dim, BooleanConstant(), resIncrement); } } @@ -207,39 +286,127 @@ protected: ////////////////////////////////////////////////////////////////////////////////////////// template services::Status PredictKernel::compute(services::HostAppIface * pHostApp, const NumericTable * x, - const regression::Model * m, NumericTable * r, size_t nIterations) + const regression::Model * m, NumericTable * r, size_t nIterations, + bool predShapContributions, bool predShapInteractions) { const daal::algorithms::gbt::regression::internal::ModelImpl * pModel = static_cast(m); PredictRegressionTask task(x, r); - return task.run(pModel, nIterations, pHostApp); + return task.run(pModel, nIterations, pHostApp, predShapContributions, predShapInteractions); } template services::Status PredictRegressionTask::run(const gbt::regression::internal::ModelImpl * m, size_t nIterations, - services::HostAppIface * pHostApp) + services::HostAppIface * pHostApp, bool predShapContributions, + bool predShapInteractions) { DAAL_ASSERT(nIterations || nIterations <= m->size()); - DAAL_CHECK_MALLOC(this->_featHelper.init(*this->_data)); + DAAL_CHECK_MALLOC(_featHelper.init(*_data)); const auto nTreesTotal = (nIterations ? nIterations : m->size()); - this->_aTree.reset(nTreesTotal); - DAAL_CHECK_MALLOC(this->_aTree.get()); - for (size_t i = 0; i < nTreesTotal; ++i) this->_aTree[i] = m->at(i); - return runInternal(pHostApp, this->_res); + _aTree.reset(nTreesTotal); + DAAL_CHECK_MALLOC(_aTree.get()); + for (size_t i = 0; i < nTreesTotal; ++i) _aTree[i] = m->at(i); + return runInternal(pHostApp, this->_res, predShapContributions, predShapInteractions); +} + +/** + * Helper to predict SHAP contribution values + * \param[in] iTree index of start tree for the calculation + * \param[in] nTrees number of trees in block included in calculation + * \param[in] nRowsData number of rows to process + * \param[in] nColumnsData number of columns in data, i.e. features + * note: 1 SHAP value per feature + bias term + * \param[in] x pointer to the start of observation data + * \param[out] res pointer to the start of memory where results are written to + * \param[in] dim DimType helper +*/ +template +template +void PredictRegressionTask::predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, + const algorithmFPType * x, algorithmFPType * res, const DimType & dim) +{ + // TODO: Make use of vectorBlockSize, similar to predictByTreesVector + + const size_t nColumnsPhi = nColumnsData + 1; + const size_t biasTermIndex = nColumnsPhi - 1; + + for (size_t iRow = 0; iRow < nRowsData; ++iRow) + { + const algorithmFPType * currentX = x + (iRow * nColumnsData); + algorithmFPType * phi = res + (iRow * nColumnsPhi); + for (size_t currentTreeIndex = iTree; currentTreeIndex < iTree + nTrees; ++currentTreeIndex) + { + // regression model builder tree 0 contains only the base_score and must be skipped + if (currentTreeIndex == 0) continue; + + // prepare memory for unique path data + const int depth = _aTree[currentTreeIndex]->getMaxLvl() + 2; + std::vector uniquePathData((depth * (depth + 1)) / 2); + + // TODO: Not using a separate variable (test) for the phi values causes a lot of cache misses and the code + // runs 10x slower - why? + // std::vector test(nColumnsData, 0); + + const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; + gbt::internal::treeShap(currentTree, currentX, phi, nColumnsData, &_featHelper, 1, + 0, 0, uniquePathData.data(), 1, 1, -1, 0, 0, 1); + + // PRAGMA_VECTOR_ALWAYS + // PRAGMA_IVDEP + // for (int iFeature = 0; iFeature < nColumnsData; ++iFeature) + // { + // phi[iFeature] += test[iFeature]; + // } + } + + PRAGMA_VECTOR_ALWAYS + PRAGMA_IVDEP + for (int iFeature = 0; iFeature < nColumnsData; ++iFeature) + { + phi[biasTermIndex] -= phi[iFeature]; + } + } } +/** + * Helper to predict SHAP contribution interactions + * \param[in] iTree index of start tree for the calculation + * \param[in] nTrees number of trees in block included in calculation + * \param[in] nRowsData number of rows to process + * \param[in] nColumnsData number of columns in data, i.e. features + * note: 1 SHAP value per feature + bias term + * \param[in] x pointer to the start of observation data + * \param[out] res pointer to the start of memory where results are written to + * \param[in] dim DimType helper +*/ +template +template +void PredictRegressionTask::predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, + const algorithmFPType * x, algorithmFPType * res, + const DimType & dim) +{} + template -services::Status PredictRegressionTask::runInternal(services::HostAppIface * pHostApp, NumericTable * result) +services::Status PredictRegressionTask::runInternal(services::HostAppIface * pHostApp, NumericTable * result, + bool predShapContributions, bool predShapInteractions) { - const auto nTreesTotal = this->_aTree.size(); + // assert we're not requesting both contributions and interactions + DAAL_ASSERT(!(predShapContributions && predShapInteractions)); - DimType dim(*this->_data, nTreesTotal, getNumberOfNodes(nTreesTotal)); - WriteOnlyRows resBD(result, 0, 1); - DAAL_CHECK_BLOCK_STATUS(resBD); - services::internal::service_memset(resBD.get(), 0, dim.nRowsTotal); + const size_t nTreesTotal = _aTree.size(); + const int dataNColumns = _data->getNumberOfColumns(); + const size_t resultNColumns = result->getNumberOfColumns(); + const size_t resultNRows = result->getNumberOfRows(); + + DimType dim(*_data, nTreesTotal, getNumberOfNodes(nTreesTotal)); + WriteOnlyRows resMatrix(result, 0, resultNRows); // select all rows for writing + DAAL_CHECK_BLOCK_STATUS(resMatrix); + services::internal::service_memset(resMatrix.get(), 0, resultNRows * resultNColumns); // set nRows * nCols to 0 SafeStatus safeStat; services::Status s; HostAppHelper host(pHostApp, 100); + + const size_t predictionIndex = resultNColumns - 1; for (size_t iTree = 0; iTree < nTreesTotal; iTree += dim.nTreesInBlock) { if (!s || host.isCancelled(s, 1)) return s; @@ -248,11 +415,38 @@ services::Status PredictRegressionTask::runInternal(servic daal::threader_for(dim.nDataBlocks, dim.nDataBlocks, [&](size_t iBlock) { const size_t iStartRow = iBlock * dim.nRowsInBlock; const size_t nRowsToProcess = (iBlock == dim.nDataBlocks - 1) ? dim.nRowsTotal - iBlock * dim.nRowsInBlock : dim.nRowsInBlock; - ReadRows xBD(const_cast(this->_data), iStartRow, nRowsToProcess); + ReadRows xBD(const_cast(_data), iStartRow, nRowsToProcess); DAAL_CHECK_BLOCK_STATUS_THR(xBD); - algorithmFPType * res = resBD.get() + iStartRow; - predict(iTree, nTreesTotal, nRowsToProcess, dim.nCols, xBD.get(), res, dim); + if (predShapContributions) + { + // thread-local write rows into global result buffer + WriteOnlyRows resRow(result, iStartRow, nRowsToProcess); + DAAL_CHECK_BLOCK_STATUS_THR(resRow); + + // bias term: prediction - sum_i phi_i (subtraction in predictContributions) + predict(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim, resultNColumns); + + // TODO: support tree weights + predictContributions(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim); + } + else if (predShapInteractions) + { + // thread-local write rows into global result buffer + WriteOnlyRows resRow(result, iStartRow, nRowsToProcess); + DAAL_CHECK_BLOCK_STATUS_THR(resRow); + + // bias term: prediction - sum_i phi_i (subtraction in predictContributions) + predict(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim, resultNColumns); + + // TODO: support tree weights + predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim); + } + else + { + algorithmFPType * res = resMatrix.get() + iStartRow; + predict(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), res, dim, 1); + } }); s = safeStat.detach(); @@ -268,7 +462,7 @@ algorithmFPType PredictRegressionTask::predictByTrees(size { algorithmFPType val = 0; for (size_t iTree = iFirstTree, iLastTree = iFirstTree + nTrees; iTree < iLastTree; ++iTree) - val += gbt::prediction::internal::predictForTree(*this->_aTree[iTree], this->_featHelper, x, dispatcher); + val += gbt::prediction::internal::predictForTree(*_aTree[iTree], _featHelper, x, dispatcher); return val; } @@ -276,17 +470,22 @@ template template void PredictRegressionTask::predictByTreesVector(size_t iFirstTree, size_t nTrees, const algorithmFPType * x, algorithmFPType * res, - const dispatcher_t & dispatcher) + const dispatcher_t & dispatcher, + size_t resIncrement) { algorithmFPType v[vectorBlockSize]; for (size_t iTree = iFirstTree, iLastTree = iFirstTree + nTrees; iTree < iLastTree; ++iTree) { gbt::prediction::internal::predictForTreeVector( - *this->_aTree[iTree], this->_featHelper, x, v, dispatcher); + *_aTree[iTree], _featHelper, x, v, dispatcher); PRAGMA_IVDEP PRAGMA_VECTOR_ALWAYS - for (size_t j = 0; j < vectorBlockSize; ++j) res[j] += v[j]; + for (size_t row = 0; row < vectorBlockSize; ++row) + { + const size_t lastColumn = (row + 1) * resIncrement - 1; + res[lastColumn] += v[row]; + } } } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_kernel.h b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_kernel.h index 7feb478f5a2..6c2584f33f9 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_kernel.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_kernel.h @@ -55,9 +55,11 @@ class PredictKernel : public daal::algorithms::Kernel * \param m[in] gradient boosted trees model obtained on training stage * \param r[out] Prediction results * \param nIterations[in] Number of iterations to predict in gradient boosted trees algorithm parameter + * \param predShapContributions[in] Predict SHAP contributions + * \param predShapInteractions[in] Predict SHAP interactions */ services::Status compute(services::HostAppIface * pHostApp, const NumericTable * a, const regression::Model * m, NumericTable * r, - size_t nIterations); + size_t nIterations, bool predShapContributions, bool predShapInteractions); }; } // namespace internal diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp index 16dc5c51808..6ba92a2b436 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp @@ -45,8 +45,22 @@ DAAL_EXPORT services::Status Result::allocate(const daal::algorithms::Input * in DAAL_CHECK_EX(dataPtr.get(), ErrorNullInputNumericTable, ArgumentName, dataStr()); services::Status s; const size_t nVectors = dataPtr->getNumberOfRows(); - Argument::set(prediction, - data_management::HomogenNumericTable::create(1, nVectors, data_management::NumericTableIface::doAllocate, &s)); + + size_t nColumnsToAllocate = 1; + const Parameter * regressionParameter = static_cast(par); + if (regressionParameter->predShapContributions) + { + const size_t nColumns = dataPtr->getNumberOfColumns(); + nColumnsToAllocate = nColumns + 1; + } + else if (regressionParameter->predShapInteractions) + { + const size_t nColumns = dataPtr->getNumberOfColumns(); + nColumnsToAllocate = (nColumns + 1) * (nColumns + 1); + } + + Argument::set(prediction, data_management::HomogenNumericTable::create(nColumnsToAllocate, nVectors, + data_management::NumericTableIface::doAllocate, &s)); return s; } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp index 1f47216b2da..268c3dbe069 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp @@ -107,6 +107,7 @@ services::Status Input::check(const daal::algorithms::Parameter * parameter, int size_t nIterations = pPrm->nIterations; DAAL_CHECK((nIterations == 0) || (nIterations <= maxNIterations), services::ErrorGbtPredictIncorrectNumberOfIterations); + DAAL_CHECK(!(pPrm->predShapContributions && pPrm->predShapInteractions), services::ErrorGbtPredictShapOptions); return s; } @@ -142,7 +143,20 @@ services::Status Result::check(const daal::algorithms::Input * input, const daal { Status s; DAAL_CHECK_STATUS(s, algorithms::regression::prediction::Result::check(input, par, method)); - DAAL_CHECK_EX(get(prediction)->getNumberOfColumns() == 1, ErrorIncorrectNumberOfColumns, ArgumentName, predictionStr()); + const auto inputCast = static_cast(input); + const prediction::Parameter * regressionParameter = static_cast(par); + size_t expectedNColumns = 1; + if (regressionParameter->predShapContributions) + { + const size_t nColumns = inputCast->get(data)->getNumberOfColumns(); + expectedNColumns = nColumns + 1; + } + else if (regressionParameter->predShapInteractions) + { + const size_t nColumns = inputCast->get(data)->getNumberOfColumns(); + expectedNColumns = (nColumns + 1) * (nColumns + 1); + } + DAAL_CHECK_EX(get(prediction)->getNumberOfColumns() == expectedNColumns, ErrorIncorrectNumberOfColumns, ArgumentName, predictionStr()); return s; } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_tree_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_tree_impl.h index fe48d017a82..7f284b59749 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_tree_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_tree_impl.h @@ -345,8 +345,8 @@ class TreeTableConnector TableRecordType ** sons = sonsArr.data(); TableRecordType ** parents = parentsArr.data(); - gbt::prediction::internal::ModelFPType * const splitPoints = tree->getSplitPoints(); - gbt::prediction::internal::FeatureIndexType * const featureIndexes = tree->getFeatureIndexesForSplit(); + gbt::internal::ModelFPType * const splitPoints = tree->getSplitPoints(); + gbt::internal::FeatureIndexType * const featureIndexes = tree->getFeatureIndexesForSplit(); for (size_t i = 0; i < nNodes; ++i) { diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i index eac996f8796..cec2e93595a 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i @@ -41,7 +41,6 @@ #include "src/services/service_algo_utils.h" #include "services/internal/sycl/types.h" -using namespace daal::algorithms::dtrees::training::internal; using namespace daal::algorithms::gbt::internal; using namespace daal::algorithms::gbt::regression::internal; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp new file mode 100644 index 00000000000..98f35d25410 --- /dev/null +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -0,0 +1,92 @@ +#include "src/algorithms/dtrees/gbt/treeshap.h" + +namespace daal +{ +namespace algorithms +{ +namespace gbt +{ +namespace internal +{ + +// extend our decision path with a fraction of one and zero extensions +void treeShapExtendPath(PathElement * uniquePath, uint32_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex) +{ + uniquePath[uniqueDepth].featureIndex = featureIndex; + uniquePath[uniqueDepth].zeroFraction = zeroFraction; + uniquePath[uniqueDepth].oneFraction = oneFraction; + uniquePath[uniqueDepth].partialWeight = (uniqueDepth == 0 ? 1.0f : 0.0f); + for (int i = uniqueDepth - 1; i >= 0; i--) + { + uniquePath[i + 1].partialWeight += oneFraction * uniquePath[i].partialWeight * (i + 1) / static_cast(uniqueDepth + 1); + uniquePath[i].partialWeight = zeroFraction * uniquePath[i].partialWeight * (uniqueDepth - i) / static_cast(uniqueDepth + 1); + } +} + +// undo a previous extension of the decision path +void treeShapUnwindPath(PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex) +{ + const float oneFraction = uniquePath[pathIndex].oneFraction; + const float zeroFraction = uniquePath[pathIndex].zeroFraction; + float nextOnePortion = uniquePath[uniqueDepth].partialWeight; + + for (int i = uniqueDepth - 1; i >= 0; --i) + { + if (oneFraction != 0) + { + const float tmp = uniquePath[i].partialWeight; + uniquePath[i].partialWeight = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); + nextOnePortion = tmp - uniquePath[i].partialWeight * zeroFraction * (uniqueDepth - i) / static_cast(uniqueDepth + 1); + } + else + { + uniquePath[i].partialWeight = (uniquePath[i].partialWeight * (uniqueDepth + 1)) / static_cast(zeroFraction * (uniqueDepth - i)); + } + } + + for (auto i = pathIndex; i < uniqueDepth; ++i) + { + uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; + uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; + uniquePath[i].oneFraction = uniquePath[i + 1].oneFraction; + } +} + +// determine what the total permutation weight would be if we unwound a previous extension in the decision path +float treeShapUnwoundPathSum(const PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex) +{ + const float oneFraction = uniquePath[pathIndex].oneFraction; + const float zeroFraction = uniquePath[pathIndex].zeroFraction; + float total = 0; + if (oneFraction != 0) + { + float nextOnePortion = uniquePath[uniqueDepth].partialWeight; + for (int i = uniqueDepth - 1; i >= 0; --i) + { + const float tmp = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); + total += tmp; + nextOnePortion = uniquePath[i].partialWeight - tmp * zeroFraction * ((uniqueDepth - i) / static_cast(uniqueDepth + 1)); + } + } + else if (zeroFraction != 0) + { + for (int i = uniqueDepth - 1; i >= 0; --i) + { + total += uniquePath[i].partialWeight * (uniqueDepth + 1) / ((uniqueDepth - i) * zeroFraction); + } + } + else + { + for (int i = uniqueDepth - 1; i >= 0; --i) + { + DAAL_ASSERT(uniquePath[i].partialWeight == 0); + } + } + + return total; +} + +} // namespace internal +} // namespace gbt +} // namespace algorithms +} // namespace daal \ No newline at end of file diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h new file mode 100644 index 00000000000..d56bdbcb256 --- /dev/null +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -0,0 +1,182 @@ +/* file: treeshap.h */ +/******************************************************************************* +* Copyright 2023 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + +/* +//++ +// Implementation of the treeShap algorithm +//-- +*/ + +#ifndef __TREESHAP_H__ +#define __TREESHAP_H__ + +#include "services/daal_defines.h" +#include "src/algorithms/dtrees/dtrees_feature_type_helper.h" +#include "src/algorithms/dtrees/gbt/gbt_model_impl.h" +#include "stdint.h" + +namespace daal +{ +namespace algorithms +{ +namespace gbt +{ +namespace internal +{ +using gbt::internal::FeatureIndexType; +using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; + +// data we keep about our decision path +// note that partialWeight is included for convenience and is not tied with the other attributes +// the partialWeight of the i'th path element is the permutation weight of paths with i-1 ones in them +struct PathElement +{ + FeatureIndexType featureIndex; + float zeroFraction; + float oneFraction; + float partialWeight; + PathElement() = default; + PathElement(FeatureIndexType i, float z, float o, float w) : featureIndex(i), zeroFraction(z), oneFraction(o), partialWeight(w) {} +}; + +void treeShapExtendPath(PathElement * uniquePath, uint32_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex); +void treeShapUnwindPath(PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex); +float treeShapUnwoundPathSum(const PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex); + +/** + * \brief Recursive function that computes the feature attributions for a single tree. Original implementation by slundberg in https://github.com/dmlc/xgboost/pull/2438. + * \param tree current tree + * \param x dense data matrix + * \param phi dense output matrix of feature attributions + * \param nFeatures number features, i.e. length of feat and phi vectors + * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) + * \param nodeIndex the index of the current node in the tree, counted from 1 + * \param depth how deep are we in the tree + * \param uniqueDepth how many unique features are above the current node in the tree + * \param parentUniquePath a vector of statistics about our current path through the tree + * \param parentZeroFraction what fraction of the parent path weight is coming as 0 (integrated) + * \param parentOneFraction what fraction of the parent path weight is coming as 1 (fixed) + * \param parentFeatureIndex what feature the parent node used to split + * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) + * \param conditionFeature the index of the feature to fix + * \param conditionFraction what fraction of the current weight matches our conditioning feature + */ +template +void treeShap(const GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, size_t nFeatures, FeatureTypes * featureHelper, + size_t nodeIndex, size_t depth, uint32_t uniqueDepth, PathElement * parentUniquePath, float parentZeroFraction, float parentOneFraction, + FeatureIndexType parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) +{ + // stop if we have no weight coming down to us + if (conditionFraction == 0) return; + + const size_t nNodes = tree->getNumberOfNodes(); + // splitValues contain + // - the feature value that is used for the split for internal nodes + // - the tree prediction for leaf nodes + // we are accounting for the fact that nodeIndex is counted from 1 (not from 0) as required by helper functions + const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; + const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; + const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; + const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; + + // extend the unique path + PathElement * uniquePath = parentUniquePath + uniqueDepth + 1; + const size_t nBytes = uniqueDepth * sizeof(PathElement); + const int copy_status = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); + DAAL_ASSERT(copy_status == 0); + + if (condition == 0 || conditionFeature != parentFeatureIndex) + { + treeShapExtendPath(uniquePath, uniqueDepth, parentZeroFraction, parentOneFraction, parentFeatureIndex); + } + + const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); + + // leaf node + if (isLeaf) + { + for (uint32_t i = 1; i <= uniqueDepth; ++i) + { + const float w = treeShapUnwoundPathSum(uniquePath, uniqueDepth, i); + const PathElement & el = uniquePath[i]; + phi[el.featureIndex] += w * (el.oneFraction - el.zeroFraction) * splitValues[nodeIndex] * conditionFraction; + } + + return; + } + + const FeatureIndexType splitFeature = fIndexes[nodeIndex]; + const auto dataValue = x[splitFeature]; + + gbt::prediction::internal::PredictDispatcher dispatcher; + FeatureIndexType hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitFeature, dispatcher); + const FeatureIndexType coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); + + const float w = nodeCoverValues[nodeIndex]; + DAAL_ASSERT(w > 0); + const float hotZeroFraction = nodeCoverValues[hotIndex] / w; + const float coldZeroFraction = nodeCoverValues[coldIndex] / w; + float incomingZeroFraction = 1.0f; + float incomingOneFraction = 1.0f; + + DAAL_ASSERT(hotZeroFraction < 1.0f); + DAAL_ASSERT(coldZeroFraction < 1.0f); + + // see if we have already split on this feature, + // if so we undo that split so we can redo it for this node + uint32_t pathIndex = 0; + for (; pathIndex <= uniqueDepth; ++pathIndex) + { + if (uniquePath[pathIndex].featureIndex == splitFeature) break; + } + if (pathIndex != uniqueDepth + 1) + { + incomingZeroFraction = uniquePath[pathIndex].zeroFraction; + incomingOneFraction = uniquePath[pathIndex].oneFraction; + treeShapUnwindPath(uniquePath, uniqueDepth, pathIndex); + uniqueDepth -= 1; + } + + // divide up the conditionFraction among the recursive calls + float hotConditionFraction = conditionFraction; + float coldConditionFraction = conditionFraction; + if (condition > 0 && splitFeature == conditionFeature) + { + coldConditionFraction = 0; + uniqueDepth -= 1; + } + else if (condition < 0 && splitFeature == conditionFeature) + { + hotConditionFraction *= hotZeroFraction; + coldConditionFraction *= coldZeroFraction; + uniqueDepth -= 1; + } + + treeShap(tree, x, phi, nFeatures, featureHelper, hotIndex, depth + 1, uniqueDepth + 1, + uniquePath, hotZeroFraction * incomingZeroFraction, incomingOneFraction, + splitFeature, condition, conditionFeature, hotConditionFraction); + treeShap(tree, x, phi, nFeatures, featureHelper, coldIndex, depth + 1, uniqueDepth + 1, + uniquePath, coldZeroFraction * incomingZeroFraction, 0, splitFeature, condition, + conditionFeature, coldConditionFraction); +} + +} // namespace internal +} // namespace gbt +} // namespace algorithms +} // namespace daal + +#endif // __TREESHAP_H__ diff --git a/cpp/daal/src/services/error_handling.cpp b/cpp/daal/src/services/error_handling.cpp index e243239fc08..3d0d0d8a42c 100644 --- a/cpp/daal/src/services/error_handling.cpp +++ b/cpp/daal/src/services/error_handling.cpp @@ -930,6 +930,7 @@ void ErrorMessageCollection::parseResourceFile() // GBT error: -30000..-30099 add(ErrorGbtIncorrectNumberOfTrees, "Number of trees in the model is not consistent with the number of classes"); add(ErrorGbtPredictIncorrectNumberOfIterations, "Number of iterations value in GBT parameter is not consistent with the model"); + add(ErrorGbtPredictShapOptions, "Incompatible SHAP options. Can calculate either contributions or interactions, not both"); //Math errors: -90000..-90099 add(ErrorDataSourseNotAvailable, "ErrorDataSourseNotAvailable"); From a36406e0f8643556d6962053406623191cc60f63 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 14 Sep 2023 09:49:29 -0700 Subject: [PATCH 02/50] no more segfaults --- ...ression_predict_dense_default_batch_impl.i | 171 +++++++++++------- .../src/algorithms/dtrees/gbt/treeshap.cpp | 81 +++++++-- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 107 +++++++---- 3 files changed, 245 insertions(+), 114 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 391a733a020..8bc60f19307 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -54,6 +54,7 @@ namespace prediction { namespace internal { +using gbt::internal::FeatureIndexType; ////////////////////////////////////////////////////////////////////////////////////////// // PredictRegressionTask @@ -162,70 +163,72 @@ protected: } template - inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim); + inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, int condition, + FeatureIndexType conditionFeature, const DimType & dim); template - inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim) + inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, int condition, + FeatureIndexType conditionFeature, const DimType & dim) { if (_featHelper.hasUnorderedFeatures()) { - predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } else { - predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } } // TODO: Add vectorBlockSize templates, similar to predict // template - inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim) + inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, int condition, + FeatureIndexType conditionFeature, const DimType & dim) { - const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); + const size_t nColumnsData = dim.nCols; + const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); if (hasAnyMissing) { - predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } else { - predictContributions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } } template - inline void predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim); + inline services::Status predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim); template - inline void predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim) + inline services::Status predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim) { if (_featHelper.hasUnorderedFeatures()) { - predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); } else { - predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); } } // TODO: Add vectorBlockSize templates, similar to predict // template - inline void predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim) + inline services::Status predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, + algorithmFPType * res, const DimType & dim) { - const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); + const size_t nColumnsData = dim.nCols; + const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); if (hasAnyMissing) { - predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); } else { - predictContributionInteractions(iTree, nTrees, nRowsData, nColumnsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); } } @@ -235,11 +238,12 @@ protected: typedef BooleanConstant type; }; - // Recursivelly checking template parameter until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor. + // Recursively checking template parameter until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor. template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - const DimType & dim, BooleanConstant keepLooking, size_t resIncrement) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim, + BooleanConstant keepLooking, size_t resIncrement) { + const size_t nColumns = dim.nCols; constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; if (dim.vectorBlockSizeFactor == vectorBlockSizeFactor) { @@ -247,30 +251,32 @@ protected: } else { - predict(iTree, nTrees, nRows, nColumns, x, res, dim, + predict(iTree, nTrees, nRows, x, res, dim, BooleanConstant(), resIncrement); } } template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - const DimType & dim, BooleanConstant keepLooking, size_t resIncrement) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim, + BooleanConstant keepLooking, size_t resIncrement) { + const size_t nColumns = dim.nCols; constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); } - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - const DimType & dim, size_t resIncrement) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim, + size_t resIncrement) { + const size_t nColumns = dim.nCols; constexpr size_t maxVectorBlockSizeFactor = DimType::maxVectorBlockSizeFactor; if (maxVectorBlockSizeFactor > 1) { - predict(iTree, nTrees, nRows, nColumns, x, res, dim, BooleanConstant(), resIncrement); + predict(iTree, nTrees, nRows, x, res, dim, BooleanConstant(), resIncrement); } else { - predict(iTree, nTrees, nRows, nColumns, x, res, dim, BooleanConstant(), resIncrement); + predict(iTree, nTrees, nRows, x, res, dim, BooleanConstant(), resIncrement); } } @@ -314,19 +320,19 @@ services::Status PredictRegressionTask::run(const gbt::reg * \param[in] iTree index of start tree for the calculation * \param[in] nTrees number of trees in block included in calculation * \param[in] nRowsData number of rows to process - * \param[in] nColumnsData number of columns in data, i.e. features - * note: 1 SHAP value per feature + bias term * \param[in] x pointer to the start of observation data * \param[out] res pointer to the start of memory where results are written to * \param[in] dim DimType helper */ template template -void PredictRegressionTask::predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, - const algorithmFPType * x, algorithmFPType * res, const DimType & dim) +void PredictRegressionTask::predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, + algorithmFPType * res, int condition, FeatureIndexType conditionFeature, + const DimType & dim) { // TODO: Make use of vectorBlockSize, similar to predictByTreesVector + const size_t nColumnsData = dim.nCols; const size_t nColumnsPhi = nColumnsData + 1; const size_t biasTermIndex = nColumnsPhi - 1; @@ -341,26 +347,17 @@ void PredictRegressionTask::predictContributions(size_t iT // prepare memory for unique path data const int depth = _aTree[currentTreeIndex]->getMaxLvl() + 2; - std::vector uniquePathData((depth * (depth + 1)) / 2); - - // TODO: Not using a separate variable (test) for the phi values causes a lot of cache misses and the code - // runs 10x slower - why? - // std::vector test(nColumnsData, 0); + std::vector uniquePathData((depth * (depth + 1)) / 2); + std::fill(uniquePathData.begin(), uniquePathData.end(), gbt::treeshap::PathElement()); const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; - gbt::internal::treeShap(currentTree, currentX, phi, nColumnsData, &_featHelper, 1, - 0, 0, uniquePathData.data(), 1, 1, -1, 0, 0, 1); - - // PRAGMA_VECTOR_ALWAYS - // PRAGMA_IVDEP - // for (int iFeature = 0; iFeature < nColumnsData; ++iFeature) - // { - // phi[iFeature] += test[iFeature]; - // } + const void * endAddr = static_cast(&(*uniquePathData.end())); + printf("\n\n\n--> depth = %d | uniquePathData.end() = %p\n\n", depth, endAddr); + gbt::treeshap::treeShap(currentTree, currentX, phi, nColumnsData, &_featHelper, + uniquePathData.data(), condition, conditionFeature); + printf("treeShap is done\n"); } - PRAGMA_VECTOR_ALWAYS - PRAGMA_IVDEP for (int iFeature = 0; iFeature < nColumnsData; ++iFeature) { phi[biasTermIndex] -= phi[iFeature]; @@ -381,10 +378,60 @@ void PredictRegressionTask::predictContributions(size_t iT */ template template -void PredictRegressionTask::predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, size_t nColumnsData, - const algorithmFPType * x, algorithmFPType * res, - const DimType & dim) -{} +services::Status PredictRegressionTask::predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, + const algorithmFPType * x, algorithmFPType * res, + const DimType & dim) +{ + Status st; + const size_t nColumnsData = dim.nCols; + const size_t nColumnsPhi = nColumnsData + 1; + + // Allocate buffer for 3 matrices for algorithmFPType of size (nRowsData, nColumnsData) + const size_t elementsInMatrix = nRowsData * nColumnsPhi; + const size_t bufferSize = 3 * sizeof(algorithmFPType) * elementsInMatrix; + algorithmFPType * buffer = static_cast(daal_malloc(bufferSize)); + if (!buffer) + { + st.add(ErrorMemoryAllocationFailed); + return st; + } + + // Initialize buffer + service_memset_seq(buffer, algorithmFPType(0), 3 * elementsInMatrix); + + // Get pointers into the buffer for our three matrices + algorithmFPType * contribsDiag = buffer + 0 * elementsInMatrix; + algorithmFPType * contribsOff = buffer + 1 * elementsInMatrix; + algorithmFPType * contribsOn = buffer + 2 * elementsInMatrix; + + predictContributions(iTree, nTrees, nRowsData, x, contribsDiag, 0, 0, dim); + for (size_t i = 0; i < nColumnsPhi + 1; ++i) + { + predictContributions(iTree, nTrees, nRowsData, x, contribsOff, -1, i, dim); + predictContributions(iTree, nTrees, nRowsData, x, contribsOn, 1, i, dim); + + for (size_t j = 0; j < nRowsData; ++j) + { + for (size_t k = 0; k < nColumnsPhi + 1; ++k) + { + // fill in the diagonal with additive effects, and off-diagonal with the interactions + if (k == i) + { + res[i] += contribsDiag[k]; + } + else + { + res[k] = (contribsOn[k] - contribsOff[k]) / 2.0; + res[i] -= res[k]; + } + } + } + } + + daal_free(buffer); + + return st; +} template services::Status PredictRegressionTask::runInternal(services::HostAppIface * pHostApp, NumericTable * result, @@ -425,10 +472,10 @@ services::Status PredictRegressionTask::runInternal(servic DAAL_CHECK_BLOCK_STATUS_THR(resRow); // bias term: prediction - sum_i phi_i (subtraction in predictContributions) - predict(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim, resultNColumns); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), dim, resultNColumns); // TODO: support tree weights - predictContributions(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim); + predictContributions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), 0, 0, dim); } else if (predShapInteractions) { @@ -436,16 +483,16 @@ services::Status PredictRegressionTask::runInternal(servic WriteOnlyRows resRow(result, iStartRow, nRowsToProcess); DAAL_CHECK_BLOCK_STATUS_THR(resRow); - // bias term: prediction - sum_i phi_i (subtraction in predictContributions) - predict(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim, resultNColumns); + // TODO: Might need the nominal prediction to account for bias terms + // predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), dim, resultNColumns); // TODO: support tree weights - predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), resRow.get(), dim); + safeStat |= predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), dim); } else { algorithmFPType * res = resMatrix.get() + iStartRow; - predict(iTree, nTreesToUse, nRowsToProcess, dim.nCols, xBD.get(), res, dim, 1); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), res, dim, 1); } }); diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index 98f35d25410..6c5fe008d56 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -6,16 +6,19 @@ namespace algorithms { namespace gbt { +namespace treeshap +{ namespace internal { // extend our decision path with a fraction of one and zero extensions -void treeShapExtendPath(PathElement * uniquePath, uint32_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex) +void treeShapExtendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex) { uniquePath[uniqueDepth].featureIndex = featureIndex; uniquePath[uniqueDepth].zeroFraction = zeroFraction; uniquePath[uniqueDepth].oneFraction = oneFraction; uniquePath[uniqueDepth].partialWeight = (uniqueDepth == 0 ? 1.0f : 0.0f); + for (int i = uniqueDepth - 1; i >= 0; i--) { uniquePath[i + 1].partialWeight += oneFraction * uniquePath[i].partialWeight * (i + 1) / static_cast(uniqueDepth + 1); @@ -24,14 +27,24 @@ void treeShapExtendPath(PathElement * uniquePath, uint32_t uniqueDepth, float ze } // undo a previous extension of the decision path -void treeShapUnwindPath(PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex) +void treeShapUnwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex) { + printf("treeShapUnwindPath: Going through path elements\n"); + printf("uniquePath = %p\n", uniquePath); + printf("uniqueDepth = %lu\n", uniqueDepth); + printf("pathIndex = %lu\n", pathIndex); + printf("---- start\n"); + printf("%p\n", uniquePath + pathIndex); + const float oneFraction = uniquePath[pathIndex].oneFraction; const float zeroFraction = uniquePath[pathIndex].zeroFraction; - float nextOnePortion = uniquePath[uniqueDepth].partialWeight; + + printf("%p\n", uniquePath + uniqueDepth); + float nextOnePortion = uniquePath[uniqueDepth].partialWeight; for (int i = uniqueDepth - 1; i >= 0; --i) { + printf("%p\n", uniquePath + i); if (oneFraction != 0) { const float tmp = uniquePath[i].partialWeight; @@ -44,8 +57,9 @@ void treeShapUnwindPath(PathElement * uniquePath, uint32_t uniqueDepth, uint32_t } } - for (auto i = pathIndex; i < uniqueDepth; ++i) + for (size_t i = pathIndex; i < uniqueDepth; ++i) { + printf("%p <- %p\n", uniquePath + i, uniquePath + i + 1); uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; uniquePath[i].oneFraction = uniquePath[i + 1].oneFraction; @@ -53,31 +67,61 @@ void treeShapUnwindPath(PathElement * uniquePath, uint32_t uniqueDepth, uint32_t } // determine what the total permutation weight would be if we unwound a previous extension in the decision path -float treeShapUnwoundPathSum(const PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex) +float treeShapUnwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex) { + printf("treeShapUnwoundPathSum: Going through path elements\n"); + printf("uniquePath = %p\n", uniquePath); + printf("uniqueDepth = %lu\n", uniqueDepth); + printf("pathIndex = %lu\n", pathIndex); + printf("---- start\n"); + printf("%p\n", uniquePath + pathIndex); + const float oneFraction = uniquePath[pathIndex].oneFraction; const float zeroFraction = uniquePath[pathIndex].zeroFraction; - float total = 0; - if (oneFraction != 0) + + printf("%p\n", uniquePath + uniqueDepth); + float nextOnePortion = uniquePath[uniqueDepth].partialWeight; + float total = 0; + // if (oneFraction != 0) + // { + // float nextOnePortion = uniquePath[uniqueDepth].partialWeight; + // for (int i = uniqueDepth - 1; i >= 0; --i) + // { + // const float tmp = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); + // total += tmp; + // nextOnePortion = uniquePath[i].partialWeight - tmp * zeroFraction * ((uniqueDepth - i) / static_cast(uniqueDepth + 1)); + // } + // } + // else if (zeroFraction != 0) + // { + // for (int i = uniqueDepth - 1; i >= 0; --i) + // { + // total += uniquePath[i].partialWeight * (uniqueDepth + 1) / ((uniqueDepth - i) * zeroFraction); + // } + // } + // else + // { + // for (int i = uniqueDepth - 1; i >= 0; --i) + // { + // DAAL_ASSERT(uniquePath[i].partialWeight == 0); + // } + // } + + for (int i = uniqueDepth - 1; i >= 0; --i) { - float nextOnePortion = uniquePath[uniqueDepth].partialWeight; - for (int i = uniqueDepth - 1; i >= 0; --i) + printf("%p\n", uniquePath + i); + + if (oneFraction != 0) { const float tmp = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); total += tmp; nextOnePortion = uniquePath[i].partialWeight - tmp * zeroFraction * ((uniqueDepth - i) / static_cast(uniqueDepth + 1)); } - } - else if (zeroFraction != 0) - { - for (int i = uniqueDepth - 1; i >= 0; --i) + else if (zeroFraction != 0) { - total += uniquePath[i].partialWeight * (uniqueDepth + 1) / ((uniqueDepth - i) * zeroFraction); + total += (uniquePath[i].partialWeight / zeroFraction) / ((uniqueDepth - i) / static_cast(uniqueDepth + 1)); } - } - else - { - for (int i = uniqueDepth - 1; i >= 0; --i) + else { DAAL_ASSERT(uniquePath[i].partialWeight == 0); } @@ -87,6 +131,7 @@ float treeShapUnwoundPathSum(const PathElement * uniquePath, uint32_t uniqueDept } } // namespace internal +} // namespace treeshap } // namespace gbt } // namespace algorithms } // namespace daal \ No newline at end of file diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index d56bdbcb256..bba13c0dbf5 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -15,6 +15,14 @@ * limitations under the License. *******************************************************************************/ +/** + * Original TreeSHAP algorithm by Scott Lundberg, 2018 + * https://arxiv.org/abs/1802.03888 + * + * Fast TreeSHAP algorithm v1 and v2 by Jilei Yang, 2021 + * https://arxiv.org/abs/2109.09847. + */ + /* //++ // Implementation of the treeShap algorithm @@ -35,7 +43,7 @@ namespace algorithms { namespace gbt { -namespace internal +namespace treeshap { using gbt::internal::FeatureIndexType; using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; @@ -45,43 +53,41 @@ using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; // the partialWeight of the i'th path element is the permutation weight of paths with i-1 ones in them struct PathElement { - FeatureIndexType featureIndex; - float zeroFraction; - float oneFraction; - float partialWeight; - PathElement() = default; - PathElement(FeatureIndexType i, float z, float o, float w) : featureIndex(i), zeroFraction(z), oneFraction(o), partialWeight(w) {} + // featureIndex -1 underflows, we use it as a reserved value for the initial node + FeatureIndexType featureIndex = -1; + float zeroFraction = 0; + float oneFraction = 0; + float partialWeight = 0; + PathElement() = default; + PathElement(const PathElement &) = default; }; -void treeShapExtendPath(PathElement * uniquePath, uint32_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex); -void treeShapUnwindPath(PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex); -float treeShapUnwoundPathSum(const PathElement * uniquePath, uint32_t uniqueDepth, uint32_t pathIndex); +namespace internal +{ -/** - * \brief Recursive function that computes the feature attributions for a single tree. Original implementation by slundberg in https://github.com/dmlc/xgboost/pull/2438. - * \param tree current tree - * \param x dense data matrix - * \param phi dense output matrix of feature attributions - * \param nFeatures number features, i.e. length of feat and phi vectors - * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) +void treeShapExtendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex); +void treeShapUnwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); +float treeShapUnwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); + +/** Extension of * \param nodeIndex the index of the current node in the tree, counted from 1 * \param depth how deep are we in the tree * \param uniqueDepth how many unique features are above the current node in the tree - * \param parentUniquePath a vector of statistics about our current path through the tree * \param parentZeroFraction what fraction of the parent path weight is coming as 0 (integrated) * \param parentOneFraction what fraction of the parent path weight is coming as 1 (fixed) * \param parentFeatureIndex what feature the parent node used to split - * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) - * \param conditionFeature the index of the feature to fix * \param conditionFraction what fraction of the current weight matches our conditioning feature */ template -void treeShap(const GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, size_t nFeatures, FeatureTypes * featureHelper, - size_t nodeIndex, size_t depth, uint32_t uniqueDepth, PathElement * parentUniquePath, float parentZeroFraction, float parentOneFraction, - FeatureIndexType parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) +void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, size_t nFeatures, + FeatureTypes * featureHelper, size_t nodeIndex, size_t depth, size_t uniqueDepth, PathElement * parentUniquePath, + float parentZeroFraction, float parentOneFraction, FeatureIndexType parentFeatureIndex, int condition, + FeatureIndexType conditionFeature, float conditionFraction) { + DAAL_ASSERT(parentUniquePath); + // stop if we have no weight coming down to us - if (conditionFraction == 0) return; + if (conditionFraction < FLT_EPSILON) return; const size_t nNodes = tree->getNumberOfNodes(); // splitValues contain @@ -93,7 +99,6 @@ void treeShap(const GbtDecisionTree * tree, const algorithmFPType * x, algorithm const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; - // extend the unique path PathElement * uniquePath = parentUniquePath + uniqueDepth + 1; const size_t nBytes = uniqueDepth * sizeof(PathElement); const int copy_status = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); @@ -104,12 +109,18 @@ void treeShap(const GbtDecisionTree * tree, const algorithmFPType * x, algorithm treeShapExtendPath(uniquePath, uniqueDepth, parentZeroFraction, parentOneFraction, parentFeatureIndex); } + printf("--------------------------------------------------------------------------------\n"); + printf("depth = %lu\n", depth); + printf("uniqueDepth = %lu\n", uniqueDepth); + printf("uniquePath = %p\n", uniquePath); + printf("uniquePath + uniqueDepth = %p\n", uniquePath + uniqueDepth); + const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); // leaf node if (isLeaf) { - for (uint32_t i = 1; i <= uniqueDepth; ++i) + for (size_t i = 1; i <= uniqueDepth; ++i) { const float w = treeShapUnwoundPathSum(uniquePath, uniqueDepth, i); const PathElement & el = uniquePath[i]; @@ -138,16 +149,20 @@ void treeShap(const GbtDecisionTree * tree, const algorithmFPType * x, algorithm // see if we have already split on this feature, // if so we undo that split so we can redo it for this node - uint32_t pathIndex = 0; - for (; pathIndex <= uniqueDepth; ++pathIndex) + size_t previousSplitPathIndex = 0; + for (; previousSplitPathIndex <= uniqueDepth; ++previousSplitPathIndex) { - if (uniquePath[pathIndex].featureIndex == splitFeature) break; + if (uniquePath[previousSplitPathIndex].featureIndex == splitFeature) + { + break; + } } - if (pathIndex != uniqueDepth + 1) + if (previousSplitPathIndex != uniqueDepth + 1) { - incomingZeroFraction = uniquePath[pathIndex].zeroFraction; - incomingOneFraction = uniquePath[pathIndex].oneFraction; - treeShapUnwindPath(uniquePath, uniqueDepth, pathIndex); + incomingZeroFraction = uniquePath[previousSplitPathIndex].zeroFraction; + incomingOneFraction = uniquePath[previousSplitPathIndex].oneFraction; + treeShapUnwindPath(uniquePath, uniqueDepth, previousSplitPathIndex); + printf("previousSplitPathIndex != uniqueDepth + 1 : %lu != %lu\n", previousSplitPathIndex, uniqueDepth + 1); uniqueDepth -= 1; } @@ -173,8 +188,32 @@ void treeShap(const GbtDecisionTree * tree, const algorithmFPType * x, algorithm uniquePath, coldZeroFraction * incomingZeroFraction, 0, splitFeature, condition, conditionFeature, coldConditionFraction); } - } // namespace internal + +/** + * \brief Recursive function that computes the feature attributions for a single tree. + * \param tree current tree + * \param x dense data matrix + * \param phi dense output matrix of feature attributions + * \param nFeatures number features, i.e. length of feat and phi vectors + * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) + * \param parentUniquePath a vector of statistics about our current path through the tree + * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) + * \param conditionFeature the index of the feature to fix + */ +template +void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, size_t nFeatures, + FeatureTypes * featureHelper, PathElement * parentUniquePath, int condition, FeatureIndexType conditionFeature) +{ + DAAL_ASSERT(x); + DAAL_ASSERT(phi); + DAAL_ASSERT(featureHelper); + + // parentFeatureIndex -1 underflows, we use it as a reserved value for the initial node + treeshap::internal::treeShap(tree, x, phi, nFeatures, featureHelper, 1, 0, 0, + parentUniquePath, 1, 1, -1, condition, conditionFeature, 1); +} +} // namespace treeshap } // namespace gbt } // namespace algorithms } // namespace daal From 396230fa22d6d2cb6ed45dcdd51f0cf83414c25c Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 18 Sep 2023 07:30:18 -0700 Subject: [PATCH 03/50] fix pred_interactions --- ...ression_predict_dense_default_batch_impl.i | 79 +++++++++++++------ .../src/algorithms/dtrees/gbt/treeshap.cpp | 34 ++------ cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 52 ++++++------ 3 files changed, 85 insertions(+), 80 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 8bc60f19307..6e5858d19ed 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -199,36 +199,36 @@ protected: template inline services::Status predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim); + const algorithmFPType * nominal, algorithmFPType * res, const DimType & dim); template inline services::Status predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim) + const algorithmFPType * nominal, algorithmFPType * res, const DimType & dim) { if (_featHelper.hasUnorderedFeatures()) { - return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, nominal, res, dim); } else { - return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, nominal, res, dim); } } // TODO: Add vectorBlockSize templates, similar to predict // template inline services::Status predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, - algorithmFPType * res, const DimType & dim) + const algorithmFPType * nominal, algorithmFPType * res, const DimType & dim) { const size_t nColumnsData = dim.nCols; const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); if (hasAnyMissing) { - return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, nominal, res, dim); } else { - return predictContributionInteractions(iTree, nTrees, nRowsData, x, res, dim); + return predictContributionInteractions(iTree, nTrees, nRowsData, x, nominal, res, dim); } } @@ -352,15 +352,17 @@ void PredictRegressionTask::predictContributions(size_t iT const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; const void * endAddr = static_cast(&(*uniquePathData.end())); - printf("\n\n\n--> depth = %d | uniquePathData.end() = %p\n\n", depth, endAddr); gbt::treeshap::treeShap(currentTree, currentX, phi, nColumnsData, &_featHelper, uniquePathData.data(), condition, conditionFeature); - printf("treeShap is done\n"); } - for (int iFeature = 0; iFeature < nColumnsData; ++iFeature) + if (condition == 0) { - phi[biasTermIndex] -= phi[iFeature]; + // find bias term by leveraging bias = nominal - sum_i phi_i + for (int iFeature = 0; iFeature < nColumnsData; ++iFeature) + { + phi[biasTermIndex] -= phi[iFeature]; + } } } } @@ -379,12 +381,16 @@ void PredictRegressionTask::predictContributions(size_t iT template template services::Status PredictRegressionTask::predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, - const algorithmFPType * x, algorithmFPType * res, + const algorithmFPType * x, + const algorithmFPType * nominal, algorithmFPType * res, const DimType & dim) { Status st; - const size_t nColumnsData = dim.nCols; - const size_t nColumnsPhi = nColumnsData + 1; + const size_t nColumnsData = dim.nCols; + const size_t nColumnsPhi = nColumnsData + 1; + const size_t biasTermIndex = nColumnsPhi - 1; + + const size_t interactionMatrixSize = nColumnsPhi * nColumnsPhi; // Allocate buffer for 3 matrices for algorithmFPType of size (nRowsData, nColumnsData) const size_t elementsInMatrix = nRowsData * nColumnsPhi; @@ -396,33 +402,47 @@ services::Status PredictRegressionTask::predictContributio return st; } - // Initialize buffer - service_memset_seq(buffer, algorithmFPType(0), 3 * elementsInMatrix); - // Get pointers into the buffer for our three matrices algorithmFPType * contribsDiag = buffer + 0 * elementsInMatrix; algorithmFPType * contribsOff = buffer + 1 * elementsInMatrix; algorithmFPType * contribsOn = buffer + 2 * elementsInMatrix; + // Initialize nominal buffer + service_memset_seq(contribsDiag, algorithmFPType(0), elementsInMatrix); + + // Copy nominal values (for bias term) to the condition == 0 buffer + PRAGMA_IVDEP + PRAGMA_VECTOR_ALWAYS + for (size_t i = 0; i < nRowsData; ++i) + { + contribsDiag[i * nColumnsPhi + biasTermIndex] = nominal[i]; + } + predictContributions(iTree, nTrees, nRowsData, x, contribsDiag, 0, 0, dim); - for (size_t i = 0; i < nColumnsPhi + 1; ++i) + for (size_t i = 0; i < nColumnsPhi; ++i) { + // initialize/reset the on/off buffers + service_memset_seq(contribsOff, algorithmFPType(0), 2 * elementsInMatrix); + predictContributions(iTree, nTrees, nRowsData, x, contribsOff, -1, i, dim); predictContributions(iTree, nTrees, nRowsData, x, contribsOn, 1, i, dim); for (size_t j = 0; j < nRowsData; ++j) { - for (size_t k = 0; k < nColumnsPhi + 1; ++k) + const unsigned o_offset = j * interactionMatrixSize + i * nColumnsPhi; + const unsigned c_offset = j * nColumnsPhi; + res[o_offset + i] = 0; + for (size_t k = 0; k < nColumnsPhi; ++k) { // fill in the diagonal with additive effects, and off-diagonal with the interactions if (k == i) { - res[i] += contribsDiag[k]; + res[o_offset + i] += contribsDiag[c_offset + k]; } else { - res[k] = (contribsOn[k] - contribsOff[k]) / 2.0; - res[i] -= res[k]; + res[o_offset + k] = (contribsOn[c_offset + k] - contribsOff[c_offset + k]) / 2.0f; + res[o_offset + i] -= res[o_offset + k]; } } } @@ -483,11 +503,20 @@ services::Status PredictRegressionTask::runInternal(servic WriteOnlyRows resRow(result, iStartRow, nRowsToProcess); DAAL_CHECK_BLOCK_STATUS_THR(resRow); - // TODO: Might need the nominal prediction to account for bias terms - // predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), dim, resultNColumns); + // nominal values are required to calculate the correct bias term + algorithmFPType * nominal = static_cast(daal_malloc(nRowsToProcess * sizeof(algorithmFPType))); + if (!nominal) + { + safeStat.add(ErrorMemoryAllocationFailed); + return; + } + service_memset_seq(nominal, algorithmFPType(0), nRowsToProcess); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim, 1); // TODO: support tree weights - safeStat |= predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), dim); + safeStat |= predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, resRow.get(), dim); + + daal_free(nominal); } else { diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index 6c5fe008d56..85cc4c175f4 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -12,39 +12,30 @@ namespace internal { // extend our decision path with a fraction of one and zero extensions -void treeShapExtendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex) +void extendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, int featureIndex) { uniquePath[uniqueDepth].featureIndex = featureIndex; uniquePath[uniqueDepth].zeroFraction = zeroFraction; uniquePath[uniqueDepth].oneFraction = oneFraction; uniquePath[uniqueDepth].partialWeight = (uniqueDepth == 0 ? 1.0f : 0.0f); + const float constant = 1.0f / static_cast(uniqueDepth + 1); for (int i = uniqueDepth - 1; i >= 0; i--) { - uniquePath[i + 1].partialWeight += oneFraction * uniquePath[i].partialWeight * (i + 1) / static_cast(uniqueDepth + 1); - uniquePath[i].partialWeight = zeroFraction * uniquePath[i].partialWeight * (uniqueDepth - i) / static_cast(uniqueDepth + 1); + uniquePath[i + 1].partialWeight += oneFraction * uniquePath[i].partialWeight * (i + 1) * constant; + uniquePath[i].partialWeight = zeroFraction * uniquePath[i].partialWeight * (uniqueDepth - i) * constant; } } // undo a previous extension of the decision path -void treeShapUnwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex) +void unwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex) { - printf("treeShapUnwindPath: Going through path elements\n"); - printf("uniquePath = %p\n", uniquePath); - printf("uniqueDepth = %lu\n", uniqueDepth); - printf("pathIndex = %lu\n", pathIndex); - printf("---- start\n"); - printf("%p\n", uniquePath + pathIndex); - const float oneFraction = uniquePath[pathIndex].oneFraction; const float zeroFraction = uniquePath[pathIndex].zeroFraction; - - printf("%p\n", uniquePath + uniqueDepth); - float nextOnePortion = uniquePath[uniqueDepth].partialWeight; + float nextOnePortion = uniquePath[uniqueDepth].partialWeight; for (int i = uniqueDepth - 1; i >= 0; --i) { - printf("%p\n", uniquePath + i); if (oneFraction != 0) { const float tmp = uniquePath[i].partialWeight; @@ -59,7 +50,6 @@ void treeShapUnwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pat for (size_t i = pathIndex; i < uniqueDepth; ++i) { - printf("%p <- %p\n", uniquePath + i, uniquePath + i + 1); uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; uniquePath[i].oneFraction = uniquePath[i + 1].oneFraction; @@ -67,19 +57,11 @@ void treeShapUnwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pat } // determine what the total permutation weight would be if we unwound a previous extension in the decision path -float treeShapUnwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex) +float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex) { - printf("treeShapUnwoundPathSum: Going through path elements\n"); - printf("uniquePath = %p\n", uniquePath); - printf("uniqueDepth = %lu\n", uniqueDepth); - printf("pathIndex = %lu\n", pathIndex); - printf("---- start\n"); - printf("%p\n", uniquePath + pathIndex); - const float oneFraction = uniquePath[pathIndex].oneFraction; const float zeroFraction = uniquePath[pathIndex].zeroFraction; - printf("%p\n", uniquePath + uniqueDepth); float nextOnePortion = uniquePath[uniqueDepth].partialWeight; float total = 0; // if (oneFraction != 0) @@ -109,8 +91,6 @@ float treeShapUnwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, for (int i = uniqueDepth - 1; i >= 0; --i) { - printf("%p\n", uniquePath + i); - if (oneFraction != 0) { const float tmp = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index bba13c0dbf5..0235aaa6859 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -53,8 +53,7 @@ using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; // the partialWeight of the i'th path element is the permutation weight of paths with i-1 ones in them struct PathElement { - // featureIndex -1 underflows, we use it as a reserved value for the initial node - FeatureIndexType featureIndex = -1; + int featureIndex = 0xaaaaaaaa; float zeroFraction = 0; float oneFraction = 0; float partialWeight = 0; @@ -65,9 +64,9 @@ struct PathElement namespace internal { -void treeShapExtendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, FeatureIndexType featureIndex); -void treeShapUnwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); -float treeShapUnwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); +void extendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, int featureIndex); +void unwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); +float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); /** Extension of * \param nodeIndex the index of the current node in the tree, counted from 1 @@ -81,8 +80,8 @@ float treeShapUnwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, template void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, size_t nFeatures, FeatureTypes * featureHelper, size_t nodeIndex, size_t depth, size_t uniqueDepth, PathElement * parentUniquePath, - float parentZeroFraction, float parentOneFraction, FeatureIndexType parentFeatureIndex, int condition, - FeatureIndexType conditionFeature, float conditionFraction) + float parentZeroFraction, float parentOneFraction, int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, + float conditionFraction) { DAAL_ASSERT(parentUniquePath); @@ -100,21 +99,15 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; PathElement * uniquePath = parentUniquePath + uniqueDepth + 1; - const size_t nBytes = uniqueDepth * sizeof(PathElement); + const size_t nBytes = (uniqueDepth + 1) * sizeof(PathElement); const int copy_status = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); DAAL_ASSERT(copy_status == 0); - if (condition == 0 || conditionFeature != parentFeatureIndex) + if (condition == 0 || conditionFeature != static_cast(parentFeatureIndex)) { - treeShapExtendPath(uniquePath, uniqueDepth, parentZeroFraction, parentOneFraction, parentFeatureIndex); + extendPath(uniquePath, uniqueDepth, parentZeroFraction, parentOneFraction, parentFeatureIndex); } - printf("--------------------------------------------------------------------------------\n"); - printf("depth = %lu\n", depth); - printf("uniqueDepth = %lu\n", uniqueDepth); - printf("uniquePath = %p\n", uniquePath); - printf("uniquePath + uniqueDepth = %p\n", uniquePath + uniqueDepth); - const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); // leaf node @@ -122,7 +115,7 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType { for (size_t i = 1; i <= uniqueDepth; ++i) { - const float w = treeShapUnwoundPathSum(uniquePath, uniqueDepth, i); + const float w = unwoundPathSum(uniquePath, uniqueDepth, i); const PathElement & el = uniquePath[i]; phi[el.featureIndex] += w * (el.oneFraction - el.zeroFraction) * splitValues[nodeIndex] * conditionFraction; } @@ -130,11 +123,11 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType return; } - const FeatureIndexType splitFeature = fIndexes[nodeIndex]; - const auto dataValue = x[splitFeature]; + const FeatureIndexType splitIndex = fIndexes[nodeIndex]; + const algorithmFPType dataValue = x[splitIndex]; gbt::prediction::internal::PredictDispatcher dispatcher; - FeatureIndexType hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitFeature, dispatcher); + FeatureIndexType hotIndex = updateIndex(nodeIndex, x[splitIndex], splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); const FeatureIndexType coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); const float w = nodeCoverValues[nodeIndex]; @@ -152,7 +145,12 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType size_t previousSplitPathIndex = 0; for (; previousSplitPathIndex <= uniqueDepth; ++previousSplitPathIndex) { - if (uniquePath[previousSplitPathIndex].featureIndex == splitFeature) + const FeatureIndexType castIndex = static_cast(uniquePath[previousSplitPathIndex].featureIndex); + + // It cannot be that a feature that is ignored is in the uniquePath + DAAL_ASSERT((condition == 0) || (castIndex != conditionFeature)); + + if (castIndex == splitIndex) { break; } @@ -161,20 +159,19 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType { incomingZeroFraction = uniquePath[previousSplitPathIndex].zeroFraction; incomingOneFraction = uniquePath[previousSplitPathIndex].oneFraction; - treeShapUnwindPath(uniquePath, uniqueDepth, previousSplitPathIndex); - printf("previousSplitPathIndex != uniqueDepth + 1 : %lu != %lu\n", previousSplitPathIndex, uniqueDepth + 1); + unwindPath(uniquePath, uniqueDepth, previousSplitPathIndex); uniqueDepth -= 1; } // divide up the conditionFraction among the recursive calls float hotConditionFraction = conditionFraction; float coldConditionFraction = conditionFraction; - if (condition > 0 && splitFeature == conditionFeature) + if (condition > 0 && splitIndex == conditionFeature) { coldConditionFraction = 0; uniqueDepth -= 1; } - else if (condition < 0 && splitFeature == conditionFeature) + else if (condition < 0 && splitIndex == conditionFeature) { hotConditionFraction *= hotZeroFraction; coldConditionFraction *= coldZeroFraction; @@ -183,9 +180,9 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType treeShap(tree, x, phi, nFeatures, featureHelper, hotIndex, depth + 1, uniqueDepth + 1, uniquePath, hotZeroFraction * incomingZeroFraction, incomingOneFraction, - splitFeature, condition, conditionFeature, hotConditionFraction); + splitIndex, condition, conditionFeature, hotConditionFraction); treeShap(tree, x, phi, nFeatures, featureHelper, coldIndex, depth + 1, uniqueDepth + 1, - uniquePath, coldZeroFraction * incomingZeroFraction, 0, splitFeature, condition, + uniquePath, coldZeroFraction * incomingZeroFraction, 0, splitIndex, condition, conditionFeature, coldConditionFraction); } } // namespace internal @@ -209,7 +206,6 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType DAAL_ASSERT(phi); DAAL_ASSERT(featureHelper); - // parentFeatureIndex -1 underflows, we use it as a reserved value for the initial node treeshap::internal::treeShap(tree, x, phi, nFeatures, featureHelper, 1, 0, 0, parentUniquePath, 1, 1, -1, condition, conditionFeature, 1); } From a2ec071177539601850a318fd050bd2c3c2a315e Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Tue, 19 Sep 2023 07:43:10 -0700 Subject: [PATCH 04/50] add fast treeshap v1 --- ...ression_predict_dense_default_batch_impl.i | 34 +- .../src/algorithms/dtrees/gbt/treeshap.cpp | 93 +++++ cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 321 ++++++++++++++++-- 3 files changed, 401 insertions(+), 47 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 6e5858d19ed..09c9c3461b0 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -37,8 +37,6 @@ #include "src/externals/service_memory.h" #include "src/threading/threading.h" -#include // TODO: remove - using namespace daal::internal; using namespace daal::services::internal; @@ -345,15 +343,9 @@ void PredictRegressionTask::predictContributions(size_t iT // regression model builder tree 0 contains only the base_score and must be skipped if (currentTreeIndex == 0) continue; - // prepare memory for unique path data - const int depth = _aTree[currentTreeIndex]->getMaxLvl() + 2; - std::vector uniquePathData((depth * (depth + 1)) / 2); - std::fill(uniquePathData.begin(), uniquePathData.end(), gbt::treeshap::PathElement()); - const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; - const void * endAddr = static_cast(&(*uniquePathData.end())); - gbt::treeshap::treeShap(currentTree, currentX, phi, nColumnsData, &_featHelper, - uniquePathData.data(), condition, conditionFeature); + gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, condition, + conditionFeature); } if (condition == 0) @@ -395,7 +387,7 @@ services::Status PredictRegressionTask::predictContributio // Allocate buffer for 3 matrices for algorithmFPType of size (nRowsData, nColumnsData) const size_t elementsInMatrix = nRowsData * nColumnsPhi; const size_t bufferSize = 3 * sizeof(algorithmFPType) * elementsInMatrix; - algorithmFPType * buffer = static_cast(daal_malloc(bufferSize)); + algorithmFPType * buffer = static_cast(daal_calloc(bufferSize)); if (!buffer) { st.add(ErrorMemoryAllocationFailed); @@ -407,10 +399,7 @@ services::Status PredictRegressionTask::predictContributio algorithmFPType * contribsOff = buffer + 1 * elementsInMatrix; algorithmFPType * contribsOn = buffer + 2 * elementsInMatrix; - // Initialize nominal buffer - service_memset_seq(contribsDiag, algorithmFPType(0), elementsInMatrix); - - // Copy nominal values (for bias term) to the condition == 0 buffer + // Copy nominal values (for bias term) to the condition = 0 buffer PRAGMA_IVDEP PRAGMA_VECTOR_ALWAYS for (size_t i = 0; i < nRowsData; ++i) @@ -429,20 +418,20 @@ services::Status PredictRegressionTask::predictContributio for (size_t j = 0; j < nRowsData; ++j) { - const unsigned o_offset = j * interactionMatrixSize + i * nColumnsPhi; - const unsigned c_offset = j * nColumnsPhi; - res[o_offset + i] = 0; + const unsigned dataRowOffset = j * interactionMatrixSize + i * nColumnsPhi; + const unsigned columnOffset = j * nColumnsPhi; + res[dataRowOffset + i] = 0; for (size_t k = 0; k < nColumnsPhi; ++k) { // fill in the diagonal with additive effects, and off-diagonal with the interactions if (k == i) { - res[o_offset + i] += contribsDiag[c_offset + k]; + res[dataRowOffset + i] += contribsDiag[columnOffset + k]; } else { - res[o_offset + k] = (contribsOn[c_offset + k] - contribsOff[c_offset + k]) / 2.0f; - res[o_offset + i] -= res[o_offset + k]; + res[dataRowOffset + k] = (contribsOn[columnOffset + k] - contribsOff[columnOffset + k]) / 2.0f; + res[dataRowOffset + i] -= res[dataRowOffset + k]; } } } @@ -504,13 +493,12 @@ services::Status PredictRegressionTask::runInternal(servic DAAL_CHECK_BLOCK_STATUS_THR(resRow); // nominal values are required to calculate the correct bias term - algorithmFPType * nominal = static_cast(daal_malloc(nRowsToProcess * sizeof(algorithmFPType))); + algorithmFPType * nominal = static_cast(daal_calloc(nRowsToProcess * sizeof(algorithmFPType))); if (!nominal) { safeStat.add(ErrorMemoryAllocationFailed); return; } - service_memset_seq(nominal, algorithmFPType(0), nRowsToProcess); predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim, 1); // TODO: support tree weights diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index 85cc4c175f4..d63599e008e 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -10,6 +10,8 @@ namespace treeshap { namespace internal { +namespace v0 +{ // extend our decision path with a fraction of one and zero extensions void extendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, int featureIndex) @@ -110,6 +112,97 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t return total; } +} // namespace v0 + +namespace v1 +{ +void extendPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, float zeroFraction, + float oneFraction, int featureIndex) +{ + uniquePath[uniqueDepth].featureIndex = featureIndex; + uniquePath[uniqueDepth].zeroFraction = zeroFraction; + uniquePath[uniqueDepth].oneFraction = oneFraction; + if (oneFraction != 0) + { + // extend partialWeights iff the feature of the last split satisfies the threshold + partialWeights[uniqueDepthPartialWeights] = (uniqueDepthPartialWeights == 0 ? 1.0f : 0.0f); + for (int i = uniqueDepthPartialWeights - 1; i >= 0; i--) + { + partialWeights[i + 1] += partialWeights[i] * (i + 1) / static_cast(uniqueDepth + 1); + partialWeights[i] *= zeroFraction * (uniqueDepth - i) / static_cast(uniqueDepth + 1); + } + } + else + { + for (int i = uniqueDepthPartialWeights - 1; i >= 0; i--) + { + partialWeights[i] *= (uniqueDepth - i) / static_cast(uniqueDepth + 1); + } + } +} + +void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, unsigned pathIndex) +{ + const float oneFraction = uniquePath[pathIndex].oneFraction; + const float zeroFraction = uniquePath[pathIndex].zeroFraction; + float nextOnePortion = partialWeights[uniqueDepthPartialWeights]; + + if (oneFraction != 0) + { + // shrink partialWeights iff the feature satisfies the threshold + for (int i = uniqueDepthPartialWeights - 1; i >= 0; --i) + { + const float tmp = partialWeights[i]; + partialWeights[i] = nextOnePortion * (uniqueDepth + 1) / static_cast(i + 1); + nextOnePortion = tmp - partialWeights[i] * zeroFraction * (uniqueDepth - i) / static_cast(uniqueDepth + 1); + } + } + else + { + for (int i = uniqueDepthPartialWeights; i >= 0; --i) + { + partialWeights[i] *= (uniqueDepth + 1) / static_cast(uniqueDepth - i); + } + } + + for (unsigned i = pathIndex; i < uniqueDepth; ++i) + { + uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; + uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; + uniquePath[i].oneFraction = uniquePath[i + 1].oneFraction; + } +} + +// determine what the total permuation weight would be if +// we unwound a previous extension in the decision path (for feature satisfying the threshold) +float unwoundPathSum(const PathElement * uniquePath, const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, + unsigned pathIndex) +{ + float total = 0; + const float zeroFraction = uniquePath[pathIndex].zeroFraction; + float nextOnePortion = partialWeights[uniqueDepthPartialWeights]; + for (int i = uniqueDepthPartialWeights - 1; i >= 0; --i) + { + const float tmp = nextOnePortion / static_cast(i + 1); + total += tmp; + nextOnePortion = partialWeights[i] - tmp * zeroFraction * (uniqueDepth - i); + } + return total * (uniqueDepth + 1); +} + +float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights) +{ + float total = 0; + if (uniqueDepth > uniqueDepthPartialWeights) + { + for (int i = uniqueDepthPartialWeights; i >= 0; --i) + { + total += partialWeights[i] / static_cast(uniqueDepth - i); + } + } + return total * (uniqueDepth + 1); +} +} // namespace v1 } // namespace internal } // namespace treeshap } // namespace gbt diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 0235aaa6859..c183f9876d6 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -33,6 +33,7 @@ #define __TREESHAP_H__ #include "services/daal_defines.h" +#include "services/error_handling.h" #include "src/algorithms/dtrees/dtrees_feature_type_helper.h" #include "src/algorithms/dtrees/gbt/gbt_model_impl.h" #include "stdint.h" @@ -53,7 +54,7 @@ using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; // the partialWeight of the i'th path element is the permutation weight of paths with i-1 ones in them struct PathElement { - int featureIndex = 0xaaaaaaaa; + int featureIndex = 0; float zeroFraction = 0; float oneFraction = 0; float partialWeight = 0; @@ -63,36 +64,33 @@ struct PathElement namespace internal { +namespace v0 +{ void extendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction, float oneFraction, int featureIndex); void unwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex); -/** Extension of +/** Recursive treeShap function * \param nodeIndex the index of the current node in the tree, counted from 1 * \param depth how deep are we in the tree * \param uniqueDepth how many unique features are above the current node in the tree + * \param parentUniquePath a vector of statistics about our current path through the tree * \param parentZeroFraction what fraction of the parent path weight is coming as 0 (integrated) * \param parentOneFraction what fraction of the parent path weight is coming as 1 (fixed) * \param parentFeatureIndex what feature the parent node used to split * \param conditionFraction what fraction of the current weight matches our conditioning feature */ template -void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, size_t nFeatures, - FeatureTypes * featureHelper, size_t nodeIndex, size_t depth, size_t uniqueDepth, PathElement * parentUniquePath, - float parentZeroFraction, float parentOneFraction, int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, - float conditionFraction) +inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, FeatureTypes * featureHelper, + size_t nodeIndex, size_t depth, size_t uniqueDepth, PathElement * parentUniquePath, float parentZeroFraction, + float parentOneFraction, int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) { DAAL_ASSERT(parentUniquePath); // stop if we have no weight coming down to us if (conditionFraction < FLT_EPSILON) return; - const size_t nNodes = tree->getNumberOfNodes(); - // splitValues contain - // - the feature value that is used for the split for internal nodes - // - the tree prediction for leaf nodes - // we are accounting for the fact that nodeIndex is counted from 1 (not from 0) as required by helper functions const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; @@ -100,8 +98,8 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType PathElement * uniquePath = parentUniquePath + uniqueDepth + 1; const size_t nBytes = (uniqueDepth + 1) * sizeof(PathElement); - const int copy_status = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); - DAAL_ASSERT(copy_status == 0); + const int copyStatus = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); + DAAL_ASSERT(copyStatus == 0); if (condition == 0 || conditionFeature != static_cast(parentFeatureIndex)) { @@ -178,13 +176,270 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType uniqueDepth -= 1; } - treeShap(tree, x, phi, nFeatures, featureHelper, hotIndex, depth + 1, uniqueDepth + 1, - uniquePath, hotZeroFraction * incomingZeroFraction, incomingOneFraction, - splitIndex, condition, conditionFeature, hotConditionFraction); - treeShap(tree, x, phi, nFeatures, featureHelper, coldIndex, depth + 1, uniqueDepth + 1, - uniquePath, coldZeroFraction * incomingZeroFraction, 0, splitIndex, condition, + treeShap(tree, x, phi, featureHelper, hotIndex, depth + 1, uniqueDepth + 1, uniquePath, + hotZeroFraction * incomingZeroFraction, incomingOneFraction, splitIndex, condition, + conditionFeature, hotConditionFraction); + treeShap(tree, x, phi, featureHelper, coldIndex, depth + 1, uniqueDepth + 1, uniquePath, + coldZeroFraction * incomingZeroFraction, 0, splitIndex, condition, conditionFeature, coldConditionFraction); } + +/** + * \brief Version 0, i.e. the original TreeSHAP algorithm to compute feature attributions for a single tree + * \param tree current tree + * \param x dense data matrix + * \param phi dense output matrix of feature attributions + * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) + * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) + * \param conditionFeature the index of the feature to fix + */ +template +inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, + FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) +{ + services::Status st; + const int depth = tree->getMaxLvl() + 2; + const size_t bufferSize = sizeof(PathElement) * ((depth * (depth + 1)) / 2); + PathElement * uniquePathData = static_cast(daal_calloc(bufferSize)); + if (!uniquePathData) + { + st.add(ErrorMemoryAllocationFailed); + return st; + } + + treeShap(tree, x, phi, featureHelper, 1, 0, 0, uniquePathData, 1, 1, -1, condition, + conditionFeature, 1); + + daal_free(uniquePathData); + + return st; +} + +} // namespace v0 + +namespace v1 +{ + +void extendPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, float zeroFraction, + float oneFraction, int featureIndex); +void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, unsigned pathIndex); +float unwoundPathSum(const PathElement * uniquePath, const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, + unsigned pathIndex); +float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights); + +/** + * Recursive Fast TreeSHAP version 1 + * Important: nodeIndex is counted from 0 here! +*/ +template +inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, FeatureTypes * featureHelper, + size_t nodeIndex, size_t depth, size_t uniqueDepth, size_t uniqueDepthPartialWeights, PathElement * parentUniquePath, + float * parentPartialWeights, algorithmFPType partialWeightsResidual, float parentZeroFraction, float parentOneFraction, + int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) +{ + // stop if we have no weight coming down to us + if (conditionFraction < FLT_EPSILON) return; + + const size_t numOutputs = 1; // TODO: support multi-output models + const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; + const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; + const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; + const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; + + // extend the unique path + PathElement * uniquePath = parentUniquePath + uniqueDepth + 1; + size_t nBytes = (uniqueDepth + 1) * sizeof(PathElement); + int copyStatus = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); + DAAL_ASSERT(copyStatus == 0); + // extend partialWeights + float * partialWeights = parentPartialWeights + uniqueDepthPartialWeights + 1; + nBytes = (uniqueDepthPartialWeights + 1) * sizeof(float); + copyStatus = daal::services::internal::daal_memcpy_s(partialWeights, nBytes, parentPartialWeights, nBytes); + DAAL_ASSERT(copyStatus == 0); + + if (condition == 0 || conditionFeature != static_cast(parentFeatureIndex)) + { + extendPath(uniquePath, partialWeights, uniqueDepth, uniqueDepthPartialWeights, parentZeroFraction, parentOneFraction, parentFeatureIndex); + // update partialWeightsResidual if the feature of the last split does not satisfy the threshold + if (parentOneFraction != 1) + { + partialWeightsResidual *= parentZeroFraction; + uniqueDepthPartialWeights -= 1; + } + } + + const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); + + if (isLeaf) + { + // +1 to account for -1 in splitValues array + const unsigned valuesOffset = nodeIndex * numOutputs; + unsigned valuesNonZeroInd = 0; + unsigned valuesNonZeroCount = 0; + for (unsigned j = 0; j < numOutputs; ++j) + { + if (splitValues[valuesOffset + j] != 0) + { + valuesNonZeroInd = j; + valuesNonZeroCount++; + } + } + // pre-calculate wZero for all features not satisfying the thresholds + const algorithmFPType wZero = unwoundPathSumZero(partialWeights, uniqueDepth, uniqueDepthPartialWeights); + const algorithmFPType scaleZero = -wZero * partialWeightsResidual * conditionFraction; + algorithmFPType scale; + for (unsigned i = 1; i <= uniqueDepth; ++i) + { + const PathElement & el = uniquePath[i]; + const unsigned phiOffset = el.featureIndex * numOutputs; + // update contributions to SHAP values for features satisfying the thresholds and not satisfying the thresholds separately + if (el.oneFraction != 0) + { + const algorithmFPType w = unwoundPathSum(uniquePath, partialWeights, uniqueDepth, uniqueDepthPartialWeights, i); + scale = w * partialWeightsResidual * (1 - el.zeroFraction) * conditionFraction; + } + else + { + scale = scaleZero; + } + if (valuesNonZeroCount == 1) + { + phi[phiOffset + valuesNonZeroInd] += scale * splitValues[valuesOffset + valuesNonZeroInd]; + } + else + { + for (unsigned j = 0; j < numOutputs; ++j) + { + phi[phiOffset + j] += scale * splitValues[valuesOffset + j]; + } + } + } + + return; + } + + const unsigned splitIndex = fIndexes[nodeIndex]; + gbt::prediction::internal::PredictDispatcher dispatcher; + FeatureIndexType hotIndex = updateIndex(nodeIndex, x[splitIndex], splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); + const FeatureIndexType coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); + + const algorithmFPType w = nodeCoverValues[nodeIndex]; + const algorithmFPType hotZeroFraction = nodeCoverValues[hotIndex] / w; + const algorithmFPType coldZeroFraction = nodeCoverValues[coldIndex] / w; + algorithmFPType incomingZeroFraction = 1; + algorithmFPType incomingOneFraction = 1; + + // see if we have already split on this feature, + // if so we undo that split so we can redo it for this node + unsigned pathIndex = 0; + for (; pathIndex <= uniqueDepth; ++pathIndex) + { + if (static_cast(uniquePath[pathIndex].featureIndex) == splitIndex) break; + } + if (pathIndex != uniqueDepth + 1) + { + incomingZeroFraction = uniquePath[pathIndex].zeroFraction; + incomingOneFraction = uniquePath[pathIndex].oneFraction; + unwindPath(uniquePath, partialWeights, uniqueDepth, uniqueDepthPartialWeights, pathIndex); + uniqueDepth -= 1; + // update partialWeightsResidual iff the duplicated feature does not satisfy the threshold + if (incomingOneFraction != 0.) + { + uniqueDepthPartialWeights -= 1; + } + else + { + partialWeightsResidual /= incomingZeroFraction; + } + } + + // divide up the conditionFraction among the recursive calls + algorithmFPType hotConditionFraction = conditionFraction; + algorithmFPType coldConditionFraction = conditionFraction; + if (condition > 0 && splitIndex == conditionFeature) + { + coldConditionFraction = 0; + uniqueDepth -= 1; + uniqueDepthPartialWeights -= 1; + } + else if (condition < 0 && splitIndex == conditionFeature) + { + hotConditionFraction *= hotZeroFraction; + coldConditionFraction *= coldZeroFraction; + uniqueDepth -= 1; + uniqueDepthPartialWeights -= 1; + } + + treeShap(tree, x, phi, featureHelper, hotIndex, depth + 1, uniqueDepth + 1, + uniqueDepthPartialWeights + 1, uniquePath, partialWeights, partialWeightsResidual, + hotZeroFraction * incomingZeroFraction, incomingOneFraction, splitIndex, condition, + conditionFeature, hotConditionFraction); + + treeShap( + tree, x, phi, featureHelper, coldIndex, depth + 1, uniqueDepth + 1, uniqueDepthPartialWeights + 1, uniquePath, partialWeights, + partialWeightsResidual, coldZeroFraction * incomingZeroFraction, 0, splitIndex, condition, conditionFeature, coldConditionFraction); +} + +/** + * \brief Version 1, i.e. first Fast TreeSHAP algorithm + * \param tree current tree + * \param x dense data matrix + * \param phi dense output matrix of feature attributions + * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) + * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) + * \param conditionFeature the index of the feature to fix + */ +template +inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, + FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) +{ + services::Status st; + + // pre-allocate space for the unique path data and partialWeights + const int depth = tree->getMaxLvl() + 2; + const size_t nElements = (depth * (depth + 1)) / 2; + PathElement * uniquePathData = static_cast(daal_calloc(sizeof(PathElement) * nElements)); + if (!uniquePathData) + { + st.add(ErrorMemoryAllocationFailed); + return st; + } + float * partialWeights = static_cast(daal_calloc(sizeof(float) * nElements)); + if (!partialWeights) + { + st.add(ErrorMemoryAllocationFailed); + return st; + } + + treeShap(tree, x, phi, featureHelper, 1, 0, 0, 0, uniquePathData, partialWeights, 1, 1, 1, + -1, condition, conditionFeature, 1); + + daal_free(uniquePathData); + daal_free(partialWeights); + + return st; +} +} // namespace v1 + +namespace v2 +{ +/** + * \brief Version 2, i.e. second Fast TreeSHAP algorithm + * \param tree current tree + * \param x dense data matrix + * \param phi dense output matrix of feature attributions + * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) + * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) + * \param conditionFeature the index of the feature to fix + */ +template +inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, + FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) +{ + services::Status st; + return st; +} +} // namespace v2 } // namespace internal /** @@ -192,22 +447,40 @@ void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * \param tree current tree * \param x dense data matrix * \param phi dense output matrix of feature attributions - * \param nFeatures number features, i.e. length of feat and phi vectors * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) - * \param parentUniquePath a vector of statistics about our current path through the tree * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) * \param conditionFeature the index of the feature to fix */ template -void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, size_t nFeatures, - FeatureTypes * featureHelper, PathElement * parentUniquePath, int condition, FeatureIndexType conditionFeature) +inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, + FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) { DAAL_ASSERT(x); DAAL_ASSERT(phi); DAAL_ASSERT(featureHelper); - treeshap::internal::treeShap(tree, x, phi, nFeatures, featureHelper, 1, 0, 0, - parentUniquePath, 1, 1, -1, condition, conditionFeature, 1); + uint8_t shapVersion = 0; + char * val = getenv("SHAP_VERSION"); + if (val) + { + shapVersion = atoi(val); + } + + switch (shapVersion) + { + case 0: + return treeshap::internal::v0::treeShap(tree, x, phi, featureHelper, condition, + conditionFeature); + case 1: + return treeshap::internal::v1::treeShap(tree, x, phi, featureHelper, condition, + conditionFeature); + case 2: + return treeshap::internal::v2::treeShap(tree, x, phi, featureHelper, condition, + conditionFeature); + default: + return treeshap::internal::v0::treeShap(tree, x, phi, featureHelper, condition, + conditionFeature); + } } } // namespace treeshap } // namespace gbt From b9220707ba09505d32e22d5a3755509926eeac70 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 21 Sep 2023 08:53:48 -0700 Subject: [PATCH 05/50] Add combinationSum calculation for Fast TreeSHAP v2 --- ...ression_predict_dense_default_batch_impl.i | 50 +- .../src/algorithms/dtrees/gbt/treeshap.cpp | 14 + cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 459 +++++++++++++++--- 3 files changed, 440 insertions(+), 83 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 09c9c3461b0..a9f8633d103 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -161,37 +161,37 @@ protected: } template - inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, int condition, - FeatureIndexType conditionFeature, const DimType & dim); + inline services::Status predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, + int condition, FeatureIndexType conditionFeature, const DimType & dim); template - inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, int condition, - FeatureIndexType conditionFeature, const DimType & dim) + inline services::Status predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, + int condition, FeatureIndexType conditionFeature, const DimType & dim) { if (_featHelper.hasUnorderedFeatures()) { - predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); + return predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } else { - predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); + return predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } } // TODO: Add vectorBlockSize templates, similar to predict // template - inline void predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, int condition, - FeatureIndexType conditionFeature, const DimType & dim) + inline services::Status predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, + int condition, FeatureIndexType conditionFeature, const DimType & dim) { const size_t nColumnsData = dim.nCols; const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); if (hasAnyMissing) { - predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); + return predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } else { - predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); + return predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); } } @@ -320,20 +320,36 @@ services::Status PredictRegressionTask::run(const gbt::reg * \param[in] nRowsData number of rows to process * \param[in] x pointer to the start of observation data * \param[out] res pointer to the start of memory where results are written to + * \param[in] condition condition the feature to on (1) off (-1) or unconditioned (0) + * \param[in] conditionFeature index of feature that should be conditioned * \param[in] dim DimType helper */ template template -void PredictRegressionTask::predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, - algorithmFPType * res, int condition, FeatureIndexType conditionFeature, - const DimType & dim) +services::Status PredictRegressionTask::predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, + const algorithmFPType * x, algorithmFPType * res, int condition, + FeatureIndexType conditionFeature, const DimType & dim) { // TODO: Make use of vectorBlockSize, similar to predictByTreesVector + Status st; const size_t nColumnsData = dim.nCols; const size_t nColumnsPhi = nColumnsData + 1; const size_t biasTermIndex = nColumnsPhi - 1; + // some model details (populated only for Fast TreeSHAP v2) + gbt::treeshap::ModelDetails modelDetails(_aTree.get(), iTree, nTrees); + if (modelDetails.requiresPrecompute) + { + for (size_t currentTreeIndex = iTree; currentTreeIndex < iTree + nTrees; ++currentTreeIndex) + { + // regression model builder tree 0 contains only the base_score and must be skipped + if (currentTreeIndex == 0) continue; + const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; + gbt::treeshap::computeCombinationSum(currentTree, currentTreeIndex, modelDetails); + } + } + for (size_t iRow = 0; iRow < nRowsData; ++iRow) { const algorithmFPType * currentX = x + (iRow * nColumnsData); @@ -344,8 +360,8 @@ void PredictRegressionTask::predictContributions(size_t iT if (currentTreeIndex == 0) continue; const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; - gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, condition, - conditionFeature); + st |= gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, condition, + conditionFeature, modelDetails); } if (condition == 0) @@ -357,6 +373,8 @@ void PredictRegressionTask::predictContributions(size_t iT } } } + + return st; } /** @@ -484,7 +502,7 @@ services::Status PredictRegressionTask::runInternal(servic predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), dim, resultNColumns); // TODO: support tree weights - predictContributions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), 0, 0, dim); + safeStat |= predictContributions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), 0, 0, dim); } else if (predShapInteractions) { diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index d63599e008e..b0aa81081fe 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -8,8 +8,19 @@ namespace gbt { namespace treeshap { + +uint8_t getRequestedAlgorithmVersion() +{ + char * val = getenv("SHAP_VERSION"); + if (val) + { + return atoi(val); + } + return 0; +} namespace internal { + namespace v0 { @@ -203,6 +214,9 @@ float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, uns return total * (uniqueDepth + 1); } } // namespace v1 + +namespace v2 +{} } // namespace internal } // namespace treeshap } // namespace gbt diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index c183f9876d6..6560f94afee 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -36,6 +36,7 @@ #include "services/error_handling.h" #include "src/algorithms/dtrees/dtrees_feature_type_helper.h" #include "src/algorithms/dtrees/gbt/gbt_model_impl.h" +#include "src/services/service_arrays.h" #include "stdint.h" namespace daal @@ -49,9 +50,16 @@ namespace treeshap using gbt::internal::FeatureIndexType; using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; -// data we keep about our decision path -// note that partialWeight is included for convenience and is not tied with the other attributes -// the partialWeight of the i'th path element is the permutation weight of paths with i-1 ones in them +/** + * Determine the requested version of the TreeSHAP algorithm set in the + * environment variable SHAP_VERSION. + * Defaults to 0 if SHAP_VERSION is not set. +*/ +uint8_t getRequestedAlgorithmVersion(); + +/** + * Decision Path context +*/ struct PathElement { int featureIndex = 0; @@ -62,8 +70,76 @@ struct PathElement PathElement(const PathElement &) = default; }; +/** + * Model details required for TreeSHAP algorithms +*/ +template +struct ModelDetails +{ + size_t maxDepth = 0; + size_t maxLeafs = 0; + size_t maxNodes = 0; + size_t maxCombinations = 0; + size_t nTreesToUse = 0; + bool requiresPrecompute = false; + algorithmFPType * combinationSum = nullptr; + int * duplicatedNode = nullptr; + ModelDetails() = default; + ModelDetails(const gbt::internal::GbtDecisionTree ** trees, size_t firstTreeIndex, size_t nTrees) + { + const uint8_t shapVersion = getRequestedAlgorithmVersion(); + requiresPrecompute = shapVersion == 2; + if (!requiresPrecompute) + { + // only Fast TreeSHAP v2.2 requires what we do here + return; + } + + nTreesToUse = nTrees; + for (size_t i = firstTreeIndex; i < firstTreeIndex + nTreesToUse; ++i) + { + const gbt::internal::GbtDecisionTree * tree = trees[i]; + const size_t nNodes = tree->getNumberOfNodes(); + const size_t tDepth = tree->getMaxLvl(); + // this is over-estimating number of leafs, but that's okay because + // we're only reserving memory + // TODO: Add nLeafs to the tree structure + // (allocating space for the theoretical max is wasting space for sparse trees) + const size_t nLeafs = static_cast(1 << tDepth); + + maxDepth = maxDepth > tDepth ? maxDepth : tDepth; + maxLeafs = maxLeafs > nLeafs ? maxLeafs : nLeafs; + maxNodes = maxNodes > nNodes ? maxNodes : nNodes; + } + + maxCombinations = static_cast(1 << maxDepth); + + // allocate combinationSum buffer for Fast TreeSHAP v2.2 + combinationSum = static_cast(daal_calloc(sizeof(algorithmFPType) * maxLeafs * maxCombinations * nTreesToUse)); + DAAL_ASSERT(combinationSum); + + // allocate duplicatedNode buffer for Fast TreeSHAP v2.2 + duplicatedNode = static_cast(daal_calloc(sizeof(int) * maxNodes * nTreesToUse)); + DAAL_ASSERT(duplicatedNode); + } + ~ModelDetails() + { + if (combinationSum) + { + daal_free(combinationSum); + combinationSum = nullptr; + } + if (duplicatedNode) + { + daal_free(duplicatedNode); + duplicatedNode = nullptr; + } + } +}; + namespace internal { + namespace v0 { @@ -201,11 +277,7 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co const int depth = tree->getMaxLvl() + 2; const size_t bufferSize = sizeof(PathElement) * ((depth * (depth + 1)) / 2); PathElement * uniquePathData = static_cast(daal_calloc(bufferSize)); - if (!uniquePathData) - { - st.add(ErrorMemoryAllocationFailed); - return st; - } + DAAL_CHECK_MALLOC(uniquePathData) treeShap(tree, x, phi, featureHelper, 1, 0, 0, uniquePathData, 1, 1, -1, condition, conditionFeature, 1); @@ -220,12 +292,11 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co namespace v1 { -void extendPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, float zeroFraction, - float oneFraction, int featureIndex); -void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, unsigned pathIndex); -float unwoundPathSum(const PathElement * uniquePath, const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, - unsigned pathIndex); -float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights); +void extendPath(PathElement * uniquePath, float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights, float zeroFraction, float oneFraction, + int featureIndex); +void unwindPath(PathElement * uniquePath, float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights, unsigned pathIndex); +float unwoundPathSum(const PathElement * uniquePath, const float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights, unsigned pathIndex); +float unwoundPathSumZero(const float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights); /** * Recursive Fast TreeSHAP version 1 @@ -233,8 +304,8 @@ float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, uns */ template inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, FeatureTypes * featureHelper, - size_t nodeIndex, size_t depth, size_t uniqueDepth, size_t uniqueDepthPartialWeights, PathElement * parentUniquePath, - float * parentPartialWeights, algorithmFPType partialWeightsResidual, float parentZeroFraction, float parentOneFraction, + size_t nodeIndex, size_t depth, size_t uniqueDepth, size_t uniqueDepthPWeights, PathElement * parentUniquePath, + float * parentPWeights, algorithmFPType pWeightsResidual, float parentZeroFraction, float parentOneFraction, int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) { // stop if we have no weight coming down to us @@ -251,20 +322,20 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith size_t nBytes = (uniqueDepth + 1) * sizeof(PathElement); int copyStatus = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); DAAL_ASSERT(copyStatus == 0); - // extend partialWeights - float * partialWeights = parentPartialWeights + uniqueDepthPartialWeights + 1; - nBytes = (uniqueDepthPartialWeights + 1) * sizeof(float); - copyStatus = daal::services::internal::daal_memcpy_s(partialWeights, nBytes, parentPartialWeights, nBytes); + // extend pWeights + float * pWeights = parentPWeights + uniqueDepthPWeights + 1; + nBytes = (uniqueDepthPWeights + 1) * sizeof(float); + copyStatus = daal::services::internal::daal_memcpy_s(pWeights, nBytes, parentPWeights, nBytes); DAAL_ASSERT(copyStatus == 0); if (condition == 0 || conditionFeature != static_cast(parentFeatureIndex)) { - extendPath(uniquePath, partialWeights, uniqueDepth, uniqueDepthPartialWeights, parentZeroFraction, parentOneFraction, parentFeatureIndex); - // update partialWeightsResidual if the feature of the last split does not satisfy the threshold + extendPath(uniquePath, pWeights, uniqueDepth, uniqueDepthPWeights, parentZeroFraction, parentOneFraction, parentFeatureIndex); + // update pWeightsResidual if the feature of the last split does not satisfy the threshold if (parentOneFraction != 1) { - partialWeightsResidual *= parentZeroFraction; - uniqueDepthPartialWeights -= 1; + pWeightsResidual *= parentZeroFraction; + uniqueDepthPWeights -= 1; } } @@ -285,8 +356,8 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith } } // pre-calculate wZero for all features not satisfying the thresholds - const algorithmFPType wZero = unwoundPathSumZero(partialWeights, uniqueDepth, uniqueDepthPartialWeights); - const algorithmFPType scaleZero = -wZero * partialWeightsResidual * conditionFraction; + const algorithmFPType wZero = unwoundPathSumZero(pWeights, uniqueDepth, uniqueDepthPWeights); + const algorithmFPType scaleZero = -wZero * pWeightsResidual * conditionFraction; algorithmFPType scale; for (unsigned i = 1; i <= uniqueDepth; ++i) { @@ -295,8 +366,8 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith // update contributions to SHAP values for features satisfying the thresholds and not satisfying the thresholds separately if (el.oneFraction != 0) { - const algorithmFPType w = unwoundPathSum(uniquePath, partialWeights, uniqueDepth, uniqueDepthPartialWeights, i); - scale = w * partialWeightsResidual * (1 - el.zeroFraction) * conditionFraction; + const algorithmFPType w = unwoundPathSum(uniquePath, pWeights, uniqueDepth, uniqueDepthPWeights, i); + scale = w * pWeightsResidual * (1 - el.zeroFraction) * conditionFraction; } else { @@ -340,16 +411,16 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith { incomingZeroFraction = uniquePath[pathIndex].zeroFraction; incomingOneFraction = uniquePath[pathIndex].oneFraction; - unwindPath(uniquePath, partialWeights, uniqueDepth, uniqueDepthPartialWeights, pathIndex); + unwindPath(uniquePath, pWeights, uniqueDepth, uniqueDepthPWeights, pathIndex); uniqueDepth -= 1; - // update partialWeightsResidual iff the duplicated feature does not satisfy the threshold + // update pWeightsResidual iff the duplicated feature does not satisfy the threshold if (incomingOneFraction != 0.) { - uniqueDepthPartialWeights -= 1; + uniqueDepthPWeights -= 1; } else { - partialWeightsResidual /= incomingZeroFraction; + pWeightsResidual /= incomingZeroFraction; } } @@ -360,24 +431,23 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith { coldConditionFraction = 0; uniqueDepth -= 1; - uniqueDepthPartialWeights -= 1; + uniqueDepthPWeights -= 1; } else if (condition < 0 && splitIndex == conditionFeature) { hotConditionFraction *= hotZeroFraction; coldConditionFraction *= coldZeroFraction; uniqueDepth -= 1; - uniqueDepthPartialWeights -= 1; + uniqueDepthPWeights -= 1; } - treeShap(tree, x, phi, featureHelper, hotIndex, depth + 1, uniqueDepth + 1, - uniqueDepthPartialWeights + 1, uniquePath, partialWeights, partialWeightsResidual, - hotZeroFraction * incomingZeroFraction, incomingOneFraction, splitIndex, condition, - conditionFeature, hotConditionFraction); + treeShap( + tree, x, phi, featureHelper, hotIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights + 1, uniquePath, pWeights, pWeightsResidual, + hotZeroFraction * incomingZeroFraction, incomingOneFraction, splitIndex, condition, conditionFeature, hotConditionFraction); treeShap( - tree, x, phi, featureHelper, coldIndex, depth + 1, uniqueDepth + 1, uniqueDepthPartialWeights + 1, uniquePath, partialWeights, - partialWeightsResidual, coldZeroFraction * incomingZeroFraction, 0, splitIndex, condition, conditionFeature, coldConditionFraction); + tree, x, phi, featureHelper, coldIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights + 1, uniquePath, pWeights, pWeightsResidual, + coldZeroFraction * incomingZeroFraction, 0, splitIndex, condition, conditionFeature, coldConditionFraction); } /** @@ -395,27 +465,20 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co { services::Status st; - // pre-allocate space for the unique path data and partialWeights + // pre-allocate space for the unique path data and pWeights const int depth = tree->getMaxLvl() + 2; const size_t nElements = (depth * (depth + 1)) / 2; PathElement * uniquePathData = static_cast(daal_calloc(sizeof(PathElement) * nElements)); - if (!uniquePathData) - { - st.add(ErrorMemoryAllocationFailed); - return st; - } - float * partialWeights = static_cast(daal_calloc(sizeof(float) * nElements)); - if (!partialWeights) - { - st.add(ErrorMemoryAllocationFailed); - return st; - } + DAAL_CHECK_MALLOC(uniquePathData) + + float * pWeights = static_cast(daal_calloc(sizeof(float) * nElements)); + DAAL_CHECK_MALLOC(pWeights) - treeShap(tree, x, phi, featureHelper, 1, 0, 0, 0, uniquePathData, partialWeights, 1, 1, 1, - -1, condition, conditionFeature, 1); + treeShap(tree, x, phi, featureHelper, 1, 0, 0, 0, uniquePathData, pWeights, 1, 1, 1, -1, + condition, conditionFeature, 1); daal_free(uniquePathData); - daal_free(partialWeights); + daal_free(pWeights); return st; } @@ -423,6 +486,240 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co namespace v2 { +template +inline void computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, algorithmFPType * combinationSum, int * duplicatedNode, + unsigned nodeIndex, unsigned depth, unsigned uniqueDepth, int * parentUniqueDepthPWeights, + PathElement * parentUniquePath, float * parentPWeights, float parentZeroFraction, int parentFeatureIndex, + int * leafCount, size_t maxDepth) +{ + const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; + const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; + + // extend the unique path + PathElement * uniquePath = parentUniquePath + uniqueDepth; + size_t nBytes = uniqueDepth * sizeof(PathElement); + int copyStatus = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); + DAAL_ASSERT(copyStatus == 0); + + uniquePath[uniqueDepth].featureIndex = parentFeatureIndex; + uniquePath[uniqueDepth].zeroFraction = parentZeroFraction; + + unsigned l; + int * uniqueDepthPWeights; + float * pWeights; + float * tPWeights; + + // extend pWeights and update uniqueDepthPWeights + if (uniqueDepth == 0) + { + l = 1; + uniqueDepthPWeights = parentUniqueDepthPWeights; + uniqueDepthPWeights[0] = 0; + pWeights = parentPWeights; + pWeights[0] = 1.0f; + } + else + { + l = static_cast(1 << (uniqueDepth - 1)); + uniqueDepthPWeights = parentUniqueDepthPWeights + l; + nBytes = l * sizeof(int); + copyStatus = daal::services::internal::daal_memcpy_s(uniqueDepthPWeights, nBytes, parentUniqueDepthPWeights, nBytes); + DAAL_ASSERT(copyStatus == 0); + copyStatus = daal::services::internal::daal_memcpy_s(uniqueDepthPWeights + l, nBytes, parentUniqueDepthPWeights, nBytes); + DAAL_ASSERT(copyStatus == 0); + std::copy(parentUniqueDepthPWeights, parentUniqueDepthPWeights + l, uniqueDepthPWeights); + std::copy(parentUniqueDepthPWeights, parentUniqueDepthPWeights + l, uniqueDepthPWeights + l); + + pWeights = parentPWeights + l * (maxDepth + 1); + nBytes = l * (maxDepth + 1) * sizeof(float); + copyStatus = daal::services::internal::daal_memcpy_s(pWeights, nBytes, parentPWeights, nBytes); + DAAL_ASSERT(copyStatus == 0); + copyStatus = daal::services::internal::daal_memcpy_s(pWeights + l * (maxDepth + 1), nBytes, parentPWeights, nBytes); + DAAL_ASSERT(copyStatus == 0); + + for (unsigned t = 0; t < l; t++) + { + tPWeights = pWeights + t * (maxDepth + 1); + for (int i = uniqueDepthPWeights[t] - 1; i >= 0; i--) + { + tPWeights[i] *= (uniqueDepth - i) / static_cast(uniqueDepth + 1); + } + uniqueDepthPWeights[t] -= 1; + } + for (unsigned t = l; t < 2 * l; t++) + { + tPWeights = pWeights + t * (maxDepth + 1); + tPWeights[uniqueDepthPWeights[t]] = 0.0f; + for (int i = uniqueDepthPWeights[t] - 1; i >= 0; i--) + { + tPWeights[i + 1] += tPWeights[i] * (i + 1) / static_cast(uniqueDepth + 1); + tPWeights[i] *= parentZeroFraction * (uniqueDepth - i) / static_cast(uniqueDepth + 1); + } + } + } + + const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); + if (isLeaf) + { + // calculate one row of combinationSum for the current path + algorithmFPType * leafCombinationSum = combinationSum + leafCount[0] * static_cast(1 << maxDepth); + for (unsigned t = 0; t < 2 * l - 1; t++) + { + leafCombinationSum[t] = 0; + tPWeights = pWeights + t * (maxDepth + 1); + for (int i = uniqueDepthPWeights[t]; i >= 0; i--) + { + leafCombinationSum[t] += tPWeights[i] / static_cast(uniqueDepth - i); + } + leafCombinationSum[t] *= (uniqueDepth + 1); + } + leafCount[0] += 1; + + return; + } + + const FeatureIndexType splitIndex = fIndexes[nodeIndex]; + + const unsigned leftIndex = 2 * nodeIndex; + const unsigned rightIndex = 2 * nodeIndex + 1; + const algorithmFPType w = nodeCoverValues[nodeIndex]; + const algorithmFPType leftZeroFraction = nodeCoverValues[leftIndex] / w; + const algorithmFPType rightZeroFraction = nodeCoverValues[rightIndex] / w; + algorithmFPType incomingZeroFraction = 1; + + // see if we have already split on this feature, + // if so we undo that split so we can redo it for this node + unsigned pathIndex = 0; + for (; pathIndex <= uniqueDepth; ++pathIndex) + { + if (static_cast(uniquePath[pathIndex].featureIndex) == splitIndex) break; + } + if (pathIndex != uniqueDepth + 1) + { + duplicatedNode[nodeIndex] = pathIndex; // record node index of duplicated feature + incomingZeroFraction = uniquePath[pathIndex].zeroFraction; + + // shrink pWeights and uniquePath, and update uniqueDepthPWeights, given the duplicated feature + unsigned p = static_cast(1 << (pathIndex - 1)); + unsigned t = 0; + float * kPWeights; + for (unsigned j = 0; j < 2 * l; j += 2 * p) + { + for (unsigned k = j; k < j + p; k++) + { + tPWeights = pWeights + t * (maxDepth + 1); + kPWeights = pWeights + k * (maxDepth + 1); + for (int i = uniqueDepthPWeights[k]; i >= 0; i--) + { + tPWeights[i] = kPWeights[i] * (uniqueDepth + 1) / static_cast(uniqueDepth - i); + } + uniqueDepthPWeights[t] = uniqueDepthPWeights[k]; + t += 1; + } + } + for (unsigned i = pathIndex; i < uniqueDepth; ++i) + { + uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; + uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; + } + uniqueDepth -= 1; + } + else + { + duplicatedNode[nodeIndex] = -1; + } + + PRAGMA_IVDEP + PRAGMA_VECTOR_ALWAYS + for (unsigned t = 0; t < 2 * l; ++t) + { + ++(uniqueDepthPWeights[t]); + } + + computeCombinationSum(tree, combinationSum, duplicatedNode, leftIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights, + uniquePath, pWeights, incomingZeroFraction * leftZeroFraction, splitIndex, leafCount, maxDepth); + + computeCombinationSum(tree, combinationSum, duplicatedNode, rightIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights, + uniquePath, pWeights, incomingZeroFraction * rightZeroFraction, splitIndex, leafCount, maxDepth); +} + +template +inline services::Status computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, algorithmFPType * combinationSum, int * duplicatedNode, + size_t maxDepth) +{ + services::Status st; + + // const size_t maxDepth = tree->getMaxLvl(); + // const size_t maxCombinations = 1 << maxDepth; + // const size_t nuniqueDepthPWeights = 2 * maxCombinations; + // const size_t nPWeights = 2 * maxCombinations * (maxDepth + 1); + // const size_t nUniquePath = (maxDepth + 1) * (maxDepth + 2) / 2; + + // for (unsigned maxNode = 0; maxNode < tree->getNumberOfNodes(); ++maxNode) + // { + // // Pre-allocate space for the unique path data, pWeights and uniqueDepthPWeights + // int * uniqueDepthPWeights = static_cast(daal_malloc(sizeof(int) * nuniqueDepthPWeights)); + // DAAL_CHECK_MALLOC(uniqueDepthPWeights) + // for (size_t i = 0; i < nuniqueDepthPWeights; ++i) + // { + // uniqueDepthPWeights[i] = 0; + // } + // printf("Allocated uniqueDepthPWeights @ %p\n", uniqueDepthPWeights); + + // float * pWeights = static_cast(daal_malloc(sizeof(float) * nPWeights)); + // DAAL_CHECK_MALLOC(pWeights) + // for (size_t i = 0; i < nPWeights; ++i) + // { + // pWeights[i] = 0.0f; + // } + // printf("Allocated pWeights @ %p\n", pWeights); + + // PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nUniquePath)); + // DAAL_CHECK_MALLOC(uniquePathData) + // PathElement init; + // for (size_t i = 0; i < nUniquePath; ++i) + // { + // DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); + // } + // printf("Allocated uniquePathData @ %p\n", uniquePathData); + // int leafCount = 0; + + const unsigned maxCombinations = static_cast(1 << maxDepth); + int * uniqueDepthPWeights = new int[2 * maxCombinations]; + float * pWeights = new float[2 * maxCombinations * (maxDepth + 1)]; + PathElement * uniquePathData = new PathElement[(maxDepth + 1) * (maxDepth + 2) / 2]; + int * leafCount = new int[1]; + leafCount[0] = 0; + + computeCombinationSum(tree, combinationSum, duplicatedNode, 1, 0, 0, uniqueDepthPWeights, uniquePathData, pWeights, 1, -1, + leafCount, maxDepth); + + delete[] uniqueDepthPWeights; + delete[] pWeights; + delete[] uniquePathData; + delete[] leafCount; + + // printf("Free uniquePathData @ %p", uniquePathData); + // daal_free(uniquePathData); + // printf("...success\n"); + // printf("Free pWeights @ %p", pWeights); + // daal_free(pWeights); + // printf("...success\n"); + // printf("Free uniqueDepthPWeights @ %p", uniqueDepthPWeights); + // daal_free(uniqueDepthPWeights); + // printf("...success\n"); + // } + + return st; +} + +template +inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, FeatureTypes * featureHelper, + size_t nodeIndex, size_t depth, size_t uniqueDepth, size_t uniqueDepthPWeights, PathElement * parentUniquePath, + float * parentPWeights, algorithmFPType pWeightsResidual, float parentZeroFraction, float parentOneFraction, + int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) +{} + /** * \brief Version 2, i.e. second Fast TreeSHAP algorithm * \param tree current tree @@ -434,14 +731,47 @@ namespace v2 */ template inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, - FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) + FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, + const ModelDetails & modelDetails) { services::Status st; + const int depth = tree->getMaxLvl() + 2; + const size_t nElements = (depth * (depth + 1)) / 2; + PathElement * uniquePathData = static_cast(daal_calloc(sizeof(PathElement) * nElements)); + DAAL_CHECK_MALLOC(uniquePathData) + int leafCount = 0; + + // treeShap(tree, combinationSum, duplicatedNode, data.X, data.X_missing, out_contribs, 0, 0, uniquePathData, 1, 1, 1, -1, leafCount); + + daal_free(uniquePathData); return st; } } // namespace v2 } // namespace internal +/** + * \brief Return the combination sum, required for Fast TreeSHAP v2 +*/ +template +services::Status computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, const size_t treeIndex, + const ModelDetails & modelDetails) +{ + if (!modelDetails.requiresPrecompute) + { + // nothing to be done + return services::Status(); + } + if (!modelDetails.combinationSum || !modelDetails.duplicatedNode) + { + // buffer wasn't properly allocated + return services::Status(ErrorMemoryAllocationFailed); + } + + algorithmFPType * combinationSum = modelDetails.combinationSum + treeIndex * modelDetails.maxLeafs * modelDetails.maxCombinations; + int * duplicatedNode = modelDetails.duplicatedNode + treeIndex * modelDetails.maxNodes; + return treeshap::internal::v2::computeCombinationSum(tree, combinationSum, duplicatedNode, modelDetails.maxDepth); +} + /** * \brief Recursive function that computes the feature attributions for a single tree. * \param tree current tree @@ -453,18 +783,14 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co */ template inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, - FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) + FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, + const ModelDetails & modelDetails) { DAAL_ASSERT(x); DAAL_ASSERT(phi); DAAL_ASSERT(featureHelper); - uint8_t shapVersion = 0; - char * val = getenv("SHAP_VERSION"); - if (val) - { - shapVersion = atoi(val); - } + uint8_t shapVersion = getRequestedAlgorithmVersion(); switch (shapVersion) { @@ -476,12 +802,11 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co conditionFeature); case 2: return treeshap::internal::v2::treeShap(tree, x, phi, featureHelper, condition, - conditionFeature); - default: - return treeshap::internal::v0::treeShap(tree, x, phi, featureHelper, condition, - conditionFeature); + conditionFeature, modelDetails); + default: return services::Status(ErrorMethodNotImplemented); } } + } // namespace treeshap } // namespace gbt } // namespace algorithms From d31a577fa62333a2c9e28aa14522d87083d5779b Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 25 Sep 2023 01:45:32 -0700 Subject: [PATCH 06/50] daal_calloc -> daal_malloc --- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 170 ++++++++++-------- 1 file changed, 91 insertions(+), 79 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 6560f94afee..7cd8a7739de 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -115,12 +115,22 @@ struct ModelDetails maxCombinations = static_cast(1 << maxDepth); // allocate combinationSum buffer for Fast TreeSHAP v2.2 - combinationSum = static_cast(daal_calloc(sizeof(algorithmFPType) * maxLeafs * maxCombinations * nTreesToUse)); + const size_t nCombinationSum = maxLeafs * maxCombinations * nTreesToUse; + combinationSum = static_cast(daal_malloc(sizeof(algorithmFPType) * nCombinationSum)); DAAL_ASSERT(combinationSum); + for (size_t i = 0; i < nCombinationSum; ++i) + { + combinationSum[i] = 0.0; + } // allocate duplicatedNode buffer for Fast TreeSHAP v2.2 - duplicatedNode = static_cast(daal_calloc(sizeof(int) * maxNodes * nTreesToUse)); + const size_t nDuplicatedNode = maxNodes * nTreesToUse; + duplicatedNode = static_cast(daal_malloc(sizeof(int) * nDuplicatedNode)); DAAL_ASSERT(duplicatedNode); + for (size_t i = 0; i < nDuplicatedNode; ++i) + { + duplicatedNode[i] = 0; + } } ~ModelDetails() { @@ -275,9 +285,14 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co { services::Status st; const int depth = tree->getMaxLvl() + 2; - const size_t bufferSize = sizeof(PathElement) * ((depth * (depth + 1)) / 2); - PathElement * uniquePathData = static_cast(daal_calloc(bufferSize)); + const size_t nUniquePath = ((depth * (depth + 1)) / 2); + PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nUniquePath)); DAAL_CHECK_MALLOC(uniquePathData) + PathElement init; + for (size_t i = 0; i < nUniquePath; ++i) + { + DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); + } treeShap(tree, x, phi, featureHelper, 1, 0, 0, uniquePathData, 1, 1, -1, condition, conditionFeature, 1); @@ -468,11 +483,20 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co // pre-allocate space for the unique path data and pWeights const int depth = tree->getMaxLvl() + 2; const size_t nElements = (depth * (depth + 1)) / 2; - PathElement * uniquePathData = static_cast(daal_calloc(sizeof(PathElement) * nElements)); + PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nElements)); DAAL_CHECK_MALLOC(uniquePathData) + PathElement init; + for (size_t i = 0; i < nElements; ++i) + { + DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); + } - float * pWeights = static_cast(daal_calloc(sizeof(float) * nElements)); + float * pWeights = static_cast(daal_malloc(sizeof(float) * nElements)); DAAL_CHECK_MALLOC(pWeights) + for (size_t i = 0; i < nElements; ++i) + { + pWeights[i] = 0.0f; + } treeShap(tree, x, phi, featureHelper, 1, 0, 0, 0, uniquePathData, pWeights, 1, 1, 1, -1, condition, conditionFeature, 1); @@ -488,9 +512,9 @@ namespace v2 { template inline void computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, algorithmFPType * combinationSum, int * duplicatedNode, - unsigned nodeIndex, unsigned depth, unsigned uniqueDepth, int * parentUniqueDepthPWeights, + size_t maxDepth, unsigned nodeIndex, unsigned depth, unsigned uniqueDepth, int * parentUniqueDepthPWeights, PathElement * parentUniquePath, float * parentPWeights, float parentZeroFraction, int parentFeatureIndex, - int * leafCount, size_t maxDepth) + int * leafCount) { const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; @@ -527,8 +551,6 @@ inline void computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, a DAAL_ASSERT(copyStatus == 0); copyStatus = daal::services::internal::daal_memcpy_s(uniqueDepthPWeights + l, nBytes, parentUniqueDepthPWeights, nBytes); DAAL_ASSERT(copyStatus == 0); - std::copy(parentUniqueDepthPWeights, parentUniqueDepthPWeights + l, uniqueDepthPWeights); - std::copy(parentUniqueDepthPWeights, parentUniqueDepthPWeights + l, uniqueDepthPWeights + l); pWeights = parentPWeights + l * (maxDepth + 1); nBytes = l * (maxDepth + 1) * sizeof(float); @@ -636,11 +658,12 @@ inline void computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, a ++(uniqueDepthPWeights[t]); } - computeCombinationSum(tree, combinationSum, duplicatedNode, leftIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights, - uniquePath, pWeights, incomingZeroFraction * leftZeroFraction, splitIndex, leafCount, maxDepth); + computeCombinationSum(tree, combinationSum, duplicatedNode, maxDepth, leftIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights, + uniquePath, pWeights, incomingZeroFraction * leftZeroFraction, splitIndex, leafCount); - computeCombinationSum(tree, combinationSum, duplicatedNode, rightIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights, - uniquePath, pWeights, incomingZeroFraction * rightZeroFraction, splitIndex, leafCount, maxDepth); + computeCombinationSum(tree, combinationSum, duplicatedNode, maxDepth, rightIndex, depth + 1, uniqueDepth + 1, + uniqueDepthPWeights, uniquePath, pWeights, incomingZeroFraction * rightZeroFraction, splitIndex, + leafCount); } template @@ -649,75 +672,51 @@ inline services::Status computeCombinationSum(const gbt::internal::GbtDecisionTr { services::Status st; - // const size_t maxDepth = tree->getMaxLvl(); - // const size_t maxCombinations = 1 << maxDepth; - // const size_t nuniqueDepthPWeights = 2 * maxCombinations; - // const size_t nPWeights = 2 * maxCombinations * (maxDepth + 1); - // const size_t nUniquePath = (maxDepth + 1) * (maxDepth + 2) / 2; + const size_t maxCombinations = 1 << maxDepth; + const size_t nUniqueDepthPWeights = 2 * maxCombinations; + const size_t nPWeights = 2 * maxCombinations * (maxDepth + 1); + const size_t nUniquePath = (maxDepth + 1) * (maxDepth + 2) / 2; - // for (unsigned maxNode = 0; maxNode < tree->getNumberOfNodes(); ++maxNode) - // { - // // Pre-allocate space for the unique path data, pWeights and uniqueDepthPWeights - // int * uniqueDepthPWeights = static_cast(daal_malloc(sizeof(int) * nuniqueDepthPWeights)); - // DAAL_CHECK_MALLOC(uniqueDepthPWeights) - // for (size_t i = 0; i < nuniqueDepthPWeights; ++i) - // { - // uniqueDepthPWeights[i] = 0; - // } - // printf("Allocated uniqueDepthPWeights @ %p\n", uniqueDepthPWeights); + // Pre-allocate space for the unique path data, pWeights and uniqueDepthPWeights + int * uniqueDepthPWeights = static_cast(daal_malloc(sizeof(int) * nUniqueDepthPWeights)); + DAAL_CHECK_MALLOC(uniqueDepthPWeights) + for (size_t i = 0; i < nUniqueDepthPWeights; ++i) + { + uniqueDepthPWeights[i] = 0; + } - // float * pWeights = static_cast(daal_malloc(sizeof(float) * nPWeights)); - // DAAL_CHECK_MALLOC(pWeights) - // for (size_t i = 0; i < nPWeights; ++i) - // { - // pWeights[i] = 0.0f; - // } - // printf("Allocated pWeights @ %p\n", pWeights); + float * pWeights = static_cast(daal_malloc(sizeof(float) * nPWeights)); + DAAL_CHECK_MALLOC(pWeights) + for (size_t i = 0; i < nPWeights; ++i) + { + pWeights[i] = 0.0f; + } - // PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nUniquePath)); - // DAAL_CHECK_MALLOC(uniquePathData) - // PathElement init; - // for (size_t i = 0; i < nUniquePath; ++i) - // { - // DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); - // } - // printf("Allocated uniquePathData @ %p\n", uniquePathData); - // int leafCount = 0; - - const unsigned maxCombinations = static_cast(1 << maxDepth); - int * uniqueDepthPWeights = new int[2 * maxCombinations]; - float * pWeights = new float[2 * maxCombinations * (maxDepth + 1)]; - PathElement * uniquePathData = new PathElement[(maxDepth + 1) * (maxDepth + 2) / 2]; - int * leafCount = new int[1]; - leafCount[0] = 0; - - computeCombinationSum(tree, combinationSum, duplicatedNode, 1, 0, 0, uniqueDepthPWeights, uniquePathData, pWeights, 1, -1, - leafCount, maxDepth); - - delete[] uniqueDepthPWeights; - delete[] pWeights; - delete[] uniquePathData; - delete[] leafCount; - - // printf("Free uniquePathData @ %p", uniquePathData); - // daal_free(uniquePathData); - // printf("...success\n"); - // printf("Free pWeights @ %p", pWeights); - // daal_free(pWeights); - // printf("...success\n"); - // printf("Free uniqueDepthPWeights @ %p", uniqueDepthPWeights); - // daal_free(uniqueDepthPWeights); - // printf("...success\n"); - // } + PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nUniquePath)); + DAAL_CHECK_MALLOC(uniquePathData) + PathElement init; + for (size_t i = 0; i < nUniquePath; ++i) + { + DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); + } + + int leafCount = 0; + + computeCombinationSum(tree, combinationSum, duplicatedNode, maxDepth, 1, 0, 0, uniqueDepthPWeights, uniquePathData, pWeights, 1, + -1, &leafCount); + + daal_free(uniquePathData); + daal_free(pWeights); + daal_free(uniqueDepthPWeights); return st; } template -inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, FeatureTypes * featureHelper, - size_t nodeIndex, size_t depth, size_t uniqueDepth, size_t uniqueDepthPWeights, PathElement * parentUniquePath, - float * parentPWeights, algorithmFPType pWeightsResidual, float parentZeroFraction, float parentOneFraction, - int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) +inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * combinationSum, const int * duplicatedNode, + const int maxDepth, const algorithmFPType * x, algorithmFPType * phi, unsigned nodeIndex, unsigned uniqueDepth, + PathElement * parentUniquePath, float pWeightsResidual, float parentZeroFraction, float parentOneFraction, + int parentFeatureIndex, int * leaf_count) {} /** @@ -735,13 +734,26 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co const ModelDetails & modelDetails) { services::Status st; - const int depth = tree->getMaxLvl() + 2; - const size_t nElements = (depth * (depth + 1)) / 2; - PathElement * uniquePathData = static_cast(daal_calloc(sizeof(PathElement) * nElements)); + + // // update the reference value with the expected value of the tree's predictions + // for (unsigned j = 0; j < tree.num_outputs; ++j) + // { + // phi[data.M * tree.num_outputs + j] += tree.values[j]; + // } + + const int depth = tree->getMaxLvl(); + const size_t nElements = (depth + 1) * (depth + 2) / 2; + PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nElements)); DAAL_CHECK_MALLOC(uniquePathData) + PathElement init; + for (size_t i = 0; i < nElements; ++i) + { + DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); + } int leafCount = 0; - // treeShap(tree, combinationSum, duplicatedNode, data.X, data.X_missing, out_contribs, 0, 0, uniquePathData, 1, 1, 1, -1, leafCount); + treeShap(tree, modelDetails.combinationSum, modelDetails.duplicatedNode, + modelDetails.maxDepth, x, phi, 0, 0, uniquePathData, 1, 1, 1, -1, &leafCount); daal_free(uniquePathData); return st; From b7389dae89c2190d94a897e0b476cef783e10086 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Tue, 26 Sep 2023 05:34:30 -0700 Subject: [PATCH 07/50] support shap contribution calculation with Fast TreeSHAP v2 --- ...ression_predict_dense_default_batch_impl.i | 4 +- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 210 ++++++++++++++---- 2 files changed, 169 insertions(+), 45 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index a9f8633d103..0d9d06b564b 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -360,8 +360,8 @@ services::Status PredictRegressionTask::predictContributio if (currentTreeIndex == 0) continue; const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; - st |= gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, condition, - conditionFeature, modelDetails); + st |= gbt::treeshap::treeShap( + currentTree, currentTreeIndex, currentX, phi, &_featHelper, condition, conditionFeature, modelDetails); } if (condition == 0) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 7cd8a7739de..8f76159bd54 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -129,7 +129,7 @@ struct ModelDetails DAAL_ASSERT(duplicatedNode); for (size_t i = 0; i < nDuplicatedNode; ++i) { - duplicatedNode[i] = 0; + duplicatedNode[i] = -1; } } ~ModelDetails() @@ -168,9 +168,10 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t * \param conditionFraction what fraction of the current weight matches our conditioning feature */ template -inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, FeatureTypes * featureHelper, - size_t nodeIndex, size_t depth, size_t uniqueDepth, PathElement * parentUniquePath, float parentZeroFraction, - float parentOneFraction, int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) +inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, + const FeatureTypes * featureHelper, size_t nodeIndex, size_t depth, size_t uniqueDepth, PathElement * parentUniquePath, + float parentZeroFraction, float parentOneFraction, int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, + float conditionFraction) { DAAL_ASSERT(parentUniquePath); @@ -211,7 +212,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith const algorithmFPType dataValue = x[splitIndex]; gbt::prediction::internal::PredictDispatcher dispatcher; - FeatureIndexType hotIndex = updateIndex(nodeIndex, x[splitIndex], splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); + FeatureIndexType hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); const FeatureIndexType coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); const float w = nodeCoverValues[nodeIndex]; @@ -281,7 +282,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith */ template inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, - FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) + const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) { services::Status st; const int depth = tree->getMaxLvl() + 2; @@ -318,10 +319,10 @@ float unwoundPathSumZero(const float * pWeights, unsigned uniqueDepth, unsigned * Important: nodeIndex is counted from 0 here! */ template -inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, FeatureTypes * featureHelper, - size_t nodeIndex, size_t depth, size_t uniqueDepth, size_t uniqueDepthPWeights, PathElement * parentUniquePath, - float * parentPWeights, algorithmFPType pWeightsResidual, float parentZeroFraction, float parentOneFraction, - int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) +inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, + const FeatureTypes * featureHelper, size_t nodeIndex, size_t depth, size_t uniqueDepth, size_t uniqueDepthPWeights, + PathElement * parentUniquePath, float * parentPWeights, algorithmFPType pWeightsResidual, float parentZeroFraction, + float parentOneFraction, int parentFeatureIndex, int condition, FeatureIndexType conditionFeature, float conditionFraction) { // stop if we have no weight coming down to us if (conditionFraction < FLT_EPSILON) return; @@ -404,9 +405,11 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith return; } - const unsigned splitIndex = fIndexes[nodeIndex]; + const unsigned splitIndex = fIndexes[nodeIndex]; + const algorithmFPType dataValue = x[splitIndex]; + gbt::prediction::internal::PredictDispatcher dispatcher; - FeatureIndexType hotIndex = updateIndex(nodeIndex, x[splitIndex], splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); + FeatureIndexType hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); const FeatureIndexType coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); const algorithmFPType w = nodeCoverValues[nodeIndex]; @@ -476,7 +479,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith */ template inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, - FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) + const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) { services::Status st; @@ -514,11 +517,8 @@ template inline void computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, algorithmFPType * combinationSum, int * duplicatedNode, size_t maxDepth, unsigned nodeIndex, unsigned depth, unsigned uniqueDepth, int * parentUniqueDepthPWeights, PathElement * parentUniquePath, float * parentPWeights, float parentZeroFraction, int parentFeatureIndex, - int * leafCount) + int & leafCount) { - const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; - const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; - // extend the unique path PathElement * uniquePath = parentUniquePath + uniqueDepth; size_t nBytes = uniqueDepth * sizeof(PathElement); @@ -584,30 +584,32 @@ inline void computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, a if (isLeaf) { // calculate one row of combinationSum for the current path - algorithmFPType * leafCombinationSum = combinationSum + leafCount[0] * static_cast(1 << maxDepth); + algorithmFPType * leafCombinationSum = combinationSum + leafCount * static_cast(1 << maxDepth); for (unsigned t = 0; t < 2 * l - 1; t++) { leafCombinationSum[t] = 0; tPWeights = pWeights + t * (maxDepth + 1); for (int i = uniqueDepthPWeights[t]; i >= 0; i--) { - leafCombinationSum[t] += tPWeights[i] / static_cast(uniqueDepth - i); + float value = tPWeights[i] / static_cast(uniqueDepth - i); + leafCombinationSum[t] += value; } leafCombinationSum[t] *= (uniqueDepth + 1); } - leafCount[0] += 1; + ++leafCount; return; } - const FeatureIndexType splitIndex = fIndexes[nodeIndex]; - - const unsigned leftIndex = 2 * nodeIndex; - const unsigned rightIndex = 2 * nodeIndex + 1; - const algorithmFPType w = nodeCoverValues[nodeIndex]; - const algorithmFPType leftZeroFraction = nodeCoverValues[leftIndex] / w; - const algorithmFPType rightZeroFraction = nodeCoverValues[rightIndex] / w; - algorithmFPType incomingZeroFraction = 1; + const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; + const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; + const FeatureIndexType splitIndex = fIndexes[nodeIndex]; + const unsigned leftIndex = 2 * nodeIndex; + const unsigned rightIndex = 2 * nodeIndex + 1; + const algorithmFPType w = nodeCoverValues[nodeIndex]; + const algorithmFPType leftZeroFraction = nodeCoverValues[leftIndex] / w; + const algorithmFPType rightZeroFraction = nodeCoverValues[rightIndex] / w; + algorithmFPType incomingZeroFraction = 1; // see if we have already split on this feature, // if so we undo that split so we can redo it for this node @@ -703,7 +705,7 @@ inline services::Status computeCombinationSum(const gbt::internal::GbtDecisionTr int leafCount = 0; computeCombinationSum(tree, combinationSum, duplicatedNode, maxDepth, 1, 0, 0, uniqueDepthPWeights, uniquePathData, pWeights, 1, - -1, &leafCount); + -1, leafCount); daal_free(uniquePathData); daal_free(pWeights); @@ -712,16 +714,134 @@ inline services::Status computeCombinationSum(const gbt::internal::GbtDecisionTr return st; } +/** + * Recursive part of Fast TreeSHAP v2 +*/ template inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * combinationSum, const int * duplicatedNode, - const int maxDepth, const algorithmFPType * x, algorithmFPType * phi, unsigned nodeIndex, unsigned uniqueDepth, + const int maxDepth, const algorithmFPType * x, algorithmFPType * phi, unsigned nodeIndex, size_t depth, unsigned uniqueDepth, PathElement * parentUniquePath, float pWeightsResidual, float parentZeroFraction, float parentOneFraction, - int parentFeatureIndex, int * leaf_count) -{} + int parentFeatureIndex, int & leafCount) +{ + // TODO: Add support for multi-class output + const size_t numOutputs = 1; + + const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; + const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; + const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; + const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; + + // extend the unique path + PathElement * uniquePath = parentUniquePath + uniqueDepth; + const size_t nBytes = uniqueDepth * sizeof(PathElement); + const int copyStatus = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); + DAAL_ASSERT(copyStatus == 0); + + uniquePath[uniqueDepth].featureIndex = parentFeatureIndex; + uniquePath[uniqueDepth].zeroFraction = parentZeroFraction; + uniquePath[uniqueDepth].oneFraction = parentOneFraction; + // update pWeightsResidual if the feature of the last split does not satisfy the threshold + if (parentOneFraction != 1) + { + pWeightsResidual *= parentZeroFraction; + } + + const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); + // leaf node + if (isLeaf) + { + const algorithmFPType * leafCombinationSum = combinationSum + leafCount * (1 << maxDepth); + // use combinationSumInd to search in the row of combinationSum corresponding to the current path + unsigned combinationSumInd = 0; + for (unsigned i = 1; i <= uniqueDepth; ++i) + { + if (uniquePath[i].oneFraction != 0) + { + combinationSumInd += 1 << (i - 1); + } + } + // update contributions to SHAP values for features satisfying the thresholds and not satisfying the thresholds separately + const unsigned valuesOffset = nodeIndex * numOutputs; + unsigned valuesNonZeroInd = 0; + unsigned valuesNonZeroCount = 0; + for (unsigned j = 0; j < numOutputs; ++j) + { + if (splitValues[valuesOffset + j] != 0) + { + valuesNonZeroInd = j; + valuesNonZeroCount++; + } + } + const algorithmFPType scaleZero = -leafCombinationSum[combinationSumInd] * pWeightsResidual; + for (unsigned i = 1; i <= uniqueDepth; ++i) + { + const PathElement & el = uniquePath[i]; + const unsigned phiOffset = el.featureIndex * numOutputs; + const algorithmFPType scale = + (el.oneFraction != 0) ? leafCombinationSum[combinationSumInd - (1 << (i - 1))] * pWeightsResidual * (1 - el.zeroFraction) : scaleZero; + if (valuesNonZeroCount == 1) + { + phi[phiOffset + valuesNonZeroInd] += scale * splitValues[valuesOffset + valuesNonZeroInd]; + } + else + { + for (unsigned j = 0; j < numOutputs; ++j) + { + phi[phiOffset + j] += scale * splitValues[valuesOffset + j]; + } + } + } + ++leafCount; + + return; + } + + const unsigned leftIndex = 2 * nodeIndex; + const unsigned rightIndex = 2 * nodeIndex + 1; + const algorithmFPType w = nodeCoverValues[nodeIndex]; + const algorithmFPType leftZeroFraction = nodeCoverValues[leftIndex] / w; + const algorithmFPType rightZeroFraction = nodeCoverValues[rightIndex] / w; + algorithmFPType incomingZeroFraction = 1; + algorithmFPType incomingOneFraction = 1; + + // see if we have already split on this feature, + // if so we undo that split so we can redo it for this node + const int pathIndex = duplicatedNode[nodeIndex]; + if (pathIndex >= 0) + { + incomingZeroFraction = uniquePath[pathIndex].zeroFraction; + incomingOneFraction = uniquePath[pathIndex].oneFraction; + + for (unsigned i = pathIndex; i < uniqueDepth; ++i) + { + uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; + uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; + uniquePath[i].oneFraction = uniquePath[i + 1].oneFraction; + } + --uniqueDepth; + // update pWeightsResidual iff the duplicated feature does not satisfy the threshold + if (incomingOneFraction != 1.) + { + pWeightsResidual /= incomingZeroFraction; + } + } + + const FeatureIndexType splitIndex = fIndexes[nodeIndex]; + bool isLeftSplit = x[splitIndex] <= splitValues[nodeIndex]; + + treeShap( + tree, combinationSum, duplicatedNode, maxDepth, x, phi, leftIndex, depth + 1, uniqueDepth + 1, uniquePath, pWeightsResidual, + leftZeroFraction * incomingZeroFraction, incomingOneFraction * isLeftSplit, splitIndex, leafCount); + + treeShap( + tree, combinationSum, duplicatedNode, maxDepth, x, phi, rightIndex, depth + 1, uniqueDepth + 1, uniquePath, pWeightsResidual, + rightZeroFraction * incomingZeroFraction, incomingOneFraction * (!isLeftSplit), splitIndex, leafCount); +} /** * \brief Version 2, i.e. second Fast TreeSHAP algorithm * \param tree current tree + * \param treeIndex index of current tree * \param x dense data matrix * \param phi dense output matrix of feature attributions * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) @@ -729,16 +849,16 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith * \param conditionFeature the index of the feature to fix */ template -inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, - FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, +inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, size_t treeIndex, const algorithmFPType * x, algorithmFPType * phi, + const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, const ModelDetails & modelDetails) { services::Status st; // // update the reference value with the expected value of the tree's predictions - // for (unsigned j = 0; j < tree.num_outputs; ++j) + // for (unsigned j = 0; j < tree.numOutputs; ++j) // { - // phi[data.M * tree.num_outputs + j] += tree.values[j]; + // phi[data.M * tree.numOutputs + j] += tree.values[j]; // } const int depth = tree->getMaxLvl(); @@ -752,8 +872,11 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co } int leafCount = 0; - treeShap(tree, modelDetails.combinationSum, modelDetails.duplicatedNode, - modelDetails.maxDepth, x, phi, 0, 0, uniquePathData, 1, 1, 1, -1, &leafCount); + algorithmFPType * combinationSum = modelDetails.combinationSum + treeIndex * modelDetails.maxLeafs * modelDetails.maxCombinations; + int * duplicatedNode = modelDetails.duplicatedNode + treeIndex * modelDetails.maxNodes; + // hand over duplicatedNode - 1 because we use 1-based node indexing + treeShap(tree, combinationSum, duplicatedNode - 1, modelDetails.maxDepth, x, phi, 1, 0, 0, + uniquePathData, 1, 1, 1, -1, leafCount); daal_free(uniquePathData); return st; @@ -781,7 +904,8 @@ services::Status computeCombinationSum(const gbt::internal::GbtDecisionTree * tr algorithmFPType * combinationSum = modelDetails.combinationSum + treeIndex * modelDetails.maxLeafs * modelDetails.maxCombinations; int * duplicatedNode = modelDetails.duplicatedNode + treeIndex * modelDetails.maxNodes; - return treeshap::internal::v2::computeCombinationSum(tree, combinationSum, duplicatedNode, modelDetails.maxDepth); + // hand over duplicatedNode - 1 because we use 1-based node indexing + return treeshap::internal::v2::computeCombinationSum(tree, combinationSum, duplicatedNode - 1, modelDetails.maxDepth); } /** @@ -794,8 +918,8 @@ services::Status computeCombinationSum(const gbt::internal::GbtDecisionTree * tr * \param conditionFeature the index of the feature to fix */ template -inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, - FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, +inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, size_t treeIndex, const algorithmFPType * x, algorithmFPType * phi, + const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, const ModelDetails & modelDetails) { DAAL_ASSERT(x); @@ -813,8 +937,8 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co return treeshap::internal::v1::treeShap(tree, x, phi, featureHelper, condition, conditionFeature); case 2: - return treeshap::internal::v2::treeShap(tree, x, phi, featureHelper, condition, - conditionFeature, modelDetails); + return treeshap::internal::v2::treeShap(tree, treeIndex, x, phi, featureHelper, + condition, conditionFeature, modelDetails); default: return services::Status(ErrorMethodNotImplemented); } } From 0b45b88d67e591a018309b7265b4c963563a2ea4 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 27 Sep 2023 04:50:47 -0700 Subject: [PATCH 08/50] Consistently add cover to daaal APIs, add output parameters to end of function arguments --- ...sion_forest_classification_model_builder.h | 12 +- .../gbt_classification_model_builder.h | 16 +- .../gbt_regression_model_builder.h | 2 +- .../src/algorithms/dtrees/dtrees_model.cpp | 2 +- .../src/algorithms/dtrees/dtrees_model_impl.h | 2 +- .../df_classification_model_builder.cpp | 4 +- .../gbt_classification_model_builder.cpp | 8 +- .../gbt_regression_model_builder.cpp | 6 +- ...ression_predict_dense_default_batch_impl.i | 17 +- .../src/algorithms/dtrees/gbt/treeshap.cpp | 2 - cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 490 +----------------- 11 files changed, 34 insertions(+), 527 deletions(-) diff --git a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h index 74f564aeac5..f656885c538 100644 --- a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h +++ b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h @@ -109,10 +109,9 @@ class DAAL_EXPORT ModelBuilder * \param[in] classLabel Class label to be predicted * \return Node identifier */ - NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel) + NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, const double cover) { NodeId resId; - const double cover = 0.0; // TODO: Add cover _status |= addLeafNodeInternal(treeId, parentId, position, classLabel, cover, resId); services::throwIfPossible(_status); return resId; @@ -126,10 +125,9 @@ class DAAL_EXPORT ModelBuilder * \param[in] proba Array with probability values for each class * \return Node identifier */ - NodeId addLeafNodeByProba(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba) + NodeId addLeafNodeByProba(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba, const double cover) { NodeId resId; - const double cover = 0.0; // TODO: Add cover _status |= addLeafNodeByProbaInternal(treeId, parentId, position, proba, cover, resId); services::throwIfPossible(_status); return resId; @@ -144,10 +142,10 @@ class DAAL_EXPORT ModelBuilder * \param[in] featureValue Feature value for splitting * \return Node identifier */ - NodeId addSplitNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, const double featureValue) + NodeId addSplitNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, const double featureValue, + const double cover) { NodeId resId; - const double cover = 0.0; // TODO: Add cover _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, cover, resId); services::throwIfPossible(_status); return resId; @@ -192,7 +190,7 @@ class DAAL_EXPORT ModelBuilder services::Status addLeafNodeByProbaInternal(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba, const double cover, NodeId & res); services::Status addSplitNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, - const double featureValue, const double cover, NodeId & res); + const double featureValue, const double cover, const int defaultLeft, NodeId & res); private: size_t _nClasses; diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h index d72cff6e9f4..71dfdbfb3a5 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h @@ -109,12 +109,13 @@ class DAAL_EXPORT ModelBuilder * \param[in] parentId Parent node to which new node is added (use noParent for root node) * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] response Response value for leaf node to be predicted + * \param[in] cover Cover (Hessian sum) of the node * \return Node identifier */ - NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response) + NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response, double cover) { NodeId resId; - _status |= addLeafNodeInternal(treeId, parentId, position, response, resId); + _status |= addLeafNodeInternal(treeId, parentId, position, response, cover, resId); services::throwIfPossible(_status); return resId; } @@ -127,12 +128,13 @@ class DAAL_EXPORT ModelBuilder * \param[in] featureIndex Feature index for splitting * \param[in] featureValue Feature value for splitting * \param[in] defaultLeft Behaviour in case of missing values + * \param[in] cover Cover (Hessian sum) of the node * \return Node identifier */ - NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, int defaultLeft = 0) + NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, int defaultLeft, double cover) { NodeId resId; - _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, resId, defaultLeft); + _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, defaultLeft, cover, resId); services::throwIfPossible(_status); return resId; } @@ -159,9 +161,9 @@ class DAAL_EXPORT ModelBuilder services::Status _status; services::Status initialize(size_t nFeatures, size_t nIterations, size_t nClasses); services::Status createTreeInternal(size_t nNodes, size_t classLabel, TreeId & resId); - services::Status addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, NodeId & res); - services::Status addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, NodeId & res, - int defaultLeft); + services::Status addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, const double cover, NodeId & res); + services::Status addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, int defaultLeft, + const double cover, NodeId & res); services::Status convertModelInternal(); size_t _nClasses; size_t _nIterations; diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h index 731f06567a7..bcde75d847a 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h @@ -161,7 +161,7 @@ class DAAL_EXPORT ModelBuilder services::Status createTreeInternal(size_t nNodes, TreeId & resId); services::Status addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, double cover, NodeId & res); services::Status addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, double cover, - NodeId & res, int defaultLeft); + int defaultLeft, NodeId & res); services::Status convertModelInternal(); }; /** @} */ diff --git a/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp b/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp index c2362a6f20a..a7d5286933c 100644 --- a/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp @@ -224,7 +224,7 @@ void setProbabilities(const size_t treeId, const size_t nodeId, const size_t res } services::Status addSplitNodeInternal(data_management::DataCollectionPtr & serializationData, size_t treeId, size_t parentId, size_t position, - size_t featureIndex, double featureValue, double cover, size_t & res, int defaultLeft) + size_t featureIndex, double featureValue, double cover, int defaultLeft, size_t & res) { const size_t noParent = static_cast(-1); services::Status s; diff --git a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h index 63fa01ce53b..d383a4ddc7b 100644 --- a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h @@ -346,7 +346,7 @@ void setNode(DecisionTreeNode & node, int featureIndex, size_t classLabel, doubl void setNode(DecisionTreeNode & node, int featureIndex, double response, double cover); services::Status addSplitNodeInternal(data_management::DataCollectionPtr & serializationData, size_t treeId, size_t parentId, size_t position, - size_t featureIndex, double featureValue, double cover, size_t & res, int defaultLeft = 0); + size_t featureIndex, double featureValue, double cover, int defaultLeft, size_t & res); void setProbabilities(const size_t treeId, const size_t nodeId, const size_t response, const data_management::DataCollectionPtr probTbl, const double * const prob); diff --git a/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp index 140b5ca6790..fffc3dadd70 100644 --- a/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp @@ -117,12 +117,12 @@ services::Status ModelBuilder::addLeafNodeByProbaInternal(const TreeId treeId, c } services::Status ModelBuilder::addSplitNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, - const double featureValue, const double cover, NodeId & res) + const double featureValue, const double cover, const int defaultLeft, NodeId & res) { decision_forest::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, cover, res); + featureValue, cover, defaultLeft, res); } } // namespace interface2 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp index 8b5d9afedfd..d13f4e665d0 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp @@ -120,23 +120,21 @@ services::Status ModelBuilder::createTreeInternal(size_t nNodes, size_t clasLabe } } -services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, NodeId & res) +services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, double cover, NodeId & res) { gbt::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); - const double cover = 0.0; // TODO: Add cover return daal::algorithms::dtrees::internal::addLeafNodeInternal(modelImplRef._serializationData, treeId, parentId, position, response, cover, res); } services::Status ModelBuilder::addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, - NodeId & res, int defaultLeft) + int defaultLeft, const double cover, NodeId & res) { gbt::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); - const double cover = 0.0; // TODO: Add cover return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, cover, res, defaultLeft); + featureValue, cover, defaultLeft, res); } } // namespace interface1 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp index 7b5df3313fa..90d66ec943a 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp @@ -72,7 +72,7 @@ services::Status ModelBuilder::createTreeInternal(size_t nNodes, TreeId & resId) } services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, double cover, NodeId & res) -{ +{ gbt::regression::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addLeafNodeInternal(modelImplRef._serializationData, treeId, parentId, position, response, @@ -80,12 +80,12 @@ services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentI } services::Status ModelBuilder::addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, - double cover, NodeId & res, int defaultLeft) + double cover, int defaultLeft, NodeId & res) { gbt::regression::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, cover, res, defaultLeft); + featureValue, cover, defaultLeft, res); } } // namespace interface1 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 0d9d06b564b..fd7e49bf77c 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -337,19 +337,6 @@ services::Status PredictRegressionTask::predictContributio const size_t nColumnsPhi = nColumnsData + 1; const size_t biasTermIndex = nColumnsPhi - 1; - // some model details (populated only for Fast TreeSHAP v2) - gbt::treeshap::ModelDetails modelDetails(_aTree.get(), iTree, nTrees); - if (modelDetails.requiresPrecompute) - { - for (size_t currentTreeIndex = iTree; currentTreeIndex < iTree + nTrees; ++currentTreeIndex) - { - // regression model builder tree 0 contains only the base_score and must be skipped - if (currentTreeIndex == 0) continue; - const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; - gbt::treeshap::computeCombinationSum(currentTree, currentTreeIndex, modelDetails); - } - } - for (size_t iRow = 0; iRow < nRowsData; ++iRow) { const algorithmFPType * currentX = x + (iRow * nColumnsData); @@ -360,8 +347,8 @@ services::Status PredictRegressionTask::predictContributio if (currentTreeIndex == 0) continue; const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; - st |= gbt::treeshap::treeShap( - currentTree, currentTreeIndex, currentX, phi, &_featHelper, condition, conditionFeature, modelDetails); + st |= gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, condition, + conditionFeature); } if (condition == 0) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index b0aa81081fe..e8d5390af3c 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -215,8 +215,6 @@ float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, uns } } // namespace v1 -namespace v2 -{} } // namespace internal } // namespace treeshap } // namespace gbt diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 8f76159bd54..762db0de73b 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -70,83 +70,6 @@ struct PathElement PathElement(const PathElement &) = default; }; -/** - * Model details required for TreeSHAP algorithms -*/ -template -struct ModelDetails -{ - size_t maxDepth = 0; - size_t maxLeafs = 0; - size_t maxNodes = 0; - size_t maxCombinations = 0; - size_t nTreesToUse = 0; - bool requiresPrecompute = false; - algorithmFPType * combinationSum = nullptr; - int * duplicatedNode = nullptr; - ModelDetails() = default; - ModelDetails(const gbt::internal::GbtDecisionTree ** trees, size_t firstTreeIndex, size_t nTrees) - { - const uint8_t shapVersion = getRequestedAlgorithmVersion(); - requiresPrecompute = shapVersion == 2; - if (!requiresPrecompute) - { - // only Fast TreeSHAP v2.2 requires what we do here - return; - } - - nTreesToUse = nTrees; - for (size_t i = firstTreeIndex; i < firstTreeIndex + nTreesToUse; ++i) - { - const gbt::internal::GbtDecisionTree * tree = trees[i]; - const size_t nNodes = tree->getNumberOfNodes(); - const size_t tDepth = tree->getMaxLvl(); - // this is over-estimating number of leafs, but that's okay because - // we're only reserving memory - // TODO: Add nLeafs to the tree structure - // (allocating space for the theoretical max is wasting space for sparse trees) - const size_t nLeafs = static_cast(1 << tDepth); - - maxDepth = maxDepth > tDepth ? maxDepth : tDepth; - maxLeafs = maxLeafs > nLeafs ? maxLeafs : nLeafs; - maxNodes = maxNodes > nNodes ? maxNodes : nNodes; - } - - maxCombinations = static_cast(1 << maxDepth); - - // allocate combinationSum buffer for Fast TreeSHAP v2.2 - const size_t nCombinationSum = maxLeafs * maxCombinations * nTreesToUse; - combinationSum = static_cast(daal_malloc(sizeof(algorithmFPType) * nCombinationSum)); - DAAL_ASSERT(combinationSum); - for (size_t i = 0; i < nCombinationSum; ++i) - { - combinationSum[i] = 0.0; - } - - // allocate duplicatedNode buffer for Fast TreeSHAP v2.2 - const size_t nDuplicatedNode = maxNodes * nTreesToUse; - duplicatedNode = static_cast(daal_malloc(sizeof(int) * nDuplicatedNode)); - DAAL_ASSERT(duplicatedNode); - for (size_t i = 0; i < nDuplicatedNode; ++i) - { - duplicatedNode[i] = -1; - } - } - ~ModelDetails() - { - if (combinationSum) - { - daal_free(combinationSum); - combinationSum = nullptr; - } - if (duplicatedNode) - { - daal_free(duplicatedNode); - duplicatedNode = nullptr; - } - } -}; - namespace internal { @@ -430,7 +353,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith incomingZeroFraction = uniquePath[pathIndex].zeroFraction; incomingOneFraction = uniquePath[pathIndex].oneFraction; unwindPath(uniquePath, pWeights, uniqueDepth, uniqueDepthPWeights, pathIndex); - uniqueDepth -= 1; + --uniqueDepth; // update pWeightsResidual iff the duplicated feature does not satisfy the threshold if (incomingOneFraction != 0.) { @@ -448,15 +371,15 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith if (condition > 0 && splitIndex == conditionFeature) { coldConditionFraction = 0; - uniqueDepth -= 1; - uniqueDepthPWeights -= 1; + --uniqueDepth; + --uniqueDepthPWeights; } else if (condition < 0 && splitIndex == conditionFeature) { hotConditionFraction *= hotZeroFraction; coldConditionFraction *= coldZeroFraction; - uniqueDepth -= 1; - uniqueDepthPWeights -= 1; + --uniqueDepth; + --uniqueDepthPWeights; } treeShap( @@ -511,403 +434,8 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co } } // namespace v1 -namespace v2 -{ -template -inline void computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, algorithmFPType * combinationSum, int * duplicatedNode, - size_t maxDepth, unsigned nodeIndex, unsigned depth, unsigned uniqueDepth, int * parentUniqueDepthPWeights, - PathElement * parentUniquePath, float * parentPWeights, float parentZeroFraction, int parentFeatureIndex, - int & leafCount) -{ - // extend the unique path - PathElement * uniquePath = parentUniquePath + uniqueDepth; - size_t nBytes = uniqueDepth * sizeof(PathElement); - int copyStatus = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); - DAAL_ASSERT(copyStatus == 0); - - uniquePath[uniqueDepth].featureIndex = parentFeatureIndex; - uniquePath[uniqueDepth].zeroFraction = parentZeroFraction; - - unsigned l; - int * uniqueDepthPWeights; - float * pWeights; - float * tPWeights; - - // extend pWeights and update uniqueDepthPWeights - if (uniqueDepth == 0) - { - l = 1; - uniqueDepthPWeights = parentUniqueDepthPWeights; - uniqueDepthPWeights[0] = 0; - pWeights = parentPWeights; - pWeights[0] = 1.0f; - } - else - { - l = static_cast(1 << (uniqueDepth - 1)); - uniqueDepthPWeights = parentUniqueDepthPWeights + l; - nBytes = l * sizeof(int); - copyStatus = daal::services::internal::daal_memcpy_s(uniqueDepthPWeights, nBytes, parentUniqueDepthPWeights, nBytes); - DAAL_ASSERT(copyStatus == 0); - copyStatus = daal::services::internal::daal_memcpy_s(uniqueDepthPWeights + l, nBytes, parentUniqueDepthPWeights, nBytes); - DAAL_ASSERT(copyStatus == 0); - - pWeights = parentPWeights + l * (maxDepth + 1); - nBytes = l * (maxDepth + 1) * sizeof(float); - copyStatus = daal::services::internal::daal_memcpy_s(pWeights, nBytes, parentPWeights, nBytes); - DAAL_ASSERT(copyStatus == 0); - copyStatus = daal::services::internal::daal_memcpy_s(pWeights + l * (maxDepth + 1), nBytes, parentPWeights, nBytes); - DAAL_ASSERT(copyStatus == 0); - - for (unsigned t = 0; t < l; t++) - { - tPWeights = pWeights + t * (maxDepth + 1); - for (int i = uniqueDepthPWeights[t] - 1; i >= 0; i--) - { - tPWeights[i] *= (uniqueDepth - i) / static_cast(uniqueDepth + 1); - } - uniqueDepthPWeights[t] -= 1; - } - for (unsigned t = l; t < 2 * l; t++) - { - tPWeights = pWeights + t * (maxDepth + 1); - tPWeights[uniqueDepthPWeights[t]] = 0.0f; - for (int i = uniqueDepthPWeights[t] - 1; i >= 0; i--) - { - tPWeights[i + 1] += tPWeights[i] * (i + 1) / static_cast(uniqueDepth + 1); - tPWeights[i] *= parentZeroFraction * (uniqueDepth - i) / static_cast(uniqueDepth + 1); - } - } - } - - const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); - if (isLeaf) - { - // calculate one row of combinationSum for the current path - algorithmFPType * leafCombinationSum = combinationSum + leafCount * static_cast(1 << maxDepth); - for (unsigned t = 0; t < 2 * l - 1; t++) - { - leafCombinationSum[t] = 0; - tPWeights = pWeights + t * (maxDepth + 1); - for (int i = uniqueDepthPWeights[t]; i >= 0; i--) - { - float value = tPWeights[i] / static_cast(uniqueDepth - i); - leafCombinationSum[t] += value; - } - leafCombinationSum[t] *= (uniqueDepth + 1); - } - ++leafCount; - - return; - } - - const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; - const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; - const FeatureIndexType splitIndex = fIndexes[nodeIndex]; - const unsigned leftIndex = 2 * nodeIndex; - const unsigned rightIndex = 2 * nodeIndex + 1; - const algorithmFPType w = nodeCoverValues[nodeIndex]; - const algorithmFPType leftZeroFraction = nodeCoverValues[leftIndex] / w; - const algorithmFPType rightZeroFraction = nodeCoverValues[rightIndex] / w; - algorithmFPType incomingZeroFraction = 1; - - // see if we have already split on this feature, - // if so we undo that split so we can redo it for this node - unsigned pathIndex = 0; - for (; pathIndex <= uniqueDepth; ++pathIndex) - { - if (static_cast(uniquePath[pathIndex].featureIndex) == splitIndex) break; - } - if (pathIndex != uniqueDepth + 1) - { - duplicatedNode[nodeIndex] = pathIndex; // record node index of duplicated feature - incomingZeroFraction = uniquePath[pathIndex].zeroFraction; - - // shrink pWeights and uniquePath, and update uniqueDepthPWeights, given the duplicated feature - unsigned p = static_cast(1 << (pathIndex - 1)); - unsigned t = 0; - float * kPWeights; - for (unsigned j = 0; j < 2 * l; j += 2 * p) - { - for (unsigned k = j; k < j + p; k++) - { - tPWeights = pWeights + t * (maxDepth + 1); - kPWeights = pWeights + k * (maxDepth + 1); - for (int i = uniqueDepthPWeights[k]; i >= 0; i--) - { - tPWeights[i] = kPWeights[i] * (uniqueDepth + 1) / static_cast(uniqueDepth - i); - } - uniqueDepthPWeights[t] = uniqueDepthPWeights[k]; - t += 1; - } - } - for (unsigned i = pathIndex; i < uniqueDepth; ++i) - { - uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; - uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; - } - uniqueDepth -= 1; - } - else - { - duplicatedNode[nodeIndex] = -1; - } - - PRAGMA_IVDEP - PRAGMA_VECTOR_ALWAYS - for (unsigned t = 0; t < 2 * l; ++t) - { - ++(uniqueDepthPWeights[t]); - } - - computeCombinationSum(tree, combinationSum, duplicatedNode, maxDepth, leftIndex, depth + 1, uniqueDepth + 1, uniqueDepthPWeights, - uniquePath, pWeights, incomingZeroFraction * leftZeroFraction, splitIndex, leafCount); - - computeCombinationSum(tree, combinationSum, duplicatedNode, maxDepth, rightIndex, depth + 1, uniqueDepth + 1, - uniqueDepthPWeights, uniquePath, pWeights, incomingZeroFraction * rightZeroFraction, splitIndex, - leafCount); -} - -template -inline services::Status computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, algorithmFPType * combinationSum, int * duplicatedNode, - size_t maxDepth) -{ - services::Status st; - - const size_t maxCombinations = 1 << maxDepth; - const size_t nUniqueDepthPWeights = 2 * maxCombinations; - const size_t nPWeights = 2 * maxCombinations * (maxDepth + 1); - const size_t nUniquePath = (maxDepth + 1) * (maxDepth + 2) / 2; - - // Pre-allocate space for the unique path data, pWeights and uniqueDepthPWeights - int * uniqueDepthPWeights = static_cast(daal_malloc(sizeof(int) * nUniqueDepthPWeights)); - DAAL_CHECK_MALLOC(uniqueDepthPWeights) - for (size_t i = 0; i < nUniqueDepthPWeights; ++i) - { - uniqueDepthPWeights[i] = 0; - } - - float * pWeights = static_cast(daal_malloc(sizeof(float) * nPWeights)); - DAAL_CHECK_MALLOC(pWeights) - for (size_t i = 0; i < nPWeights; ++i) - { - pWeights[i] = 0.0f; - } - - PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nUniquePath)); - DAAL_CHECK_MALLOC(uniquePathData) - PathElement init; - for (size_t i = 0; i < nUniquePath; ++i) - { - DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); - } - - int leafCount = 0; - - computeCombinationSum(tree, combinationSum, duplicatedNode, maxDepth, 1, 0, 0, uniqueDepthPWeights, uniquePathData, pWeights, 1, - -1, leafCount); - - daal_free(uniquePathData); - daal_free(pWeights); - daal_free(uniqueDepthPWeights); - - return st; -} - -/** - * Recursive part of Fast TreeSHAP v2 -*/ -template -inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * combinationSum, const int * duplicatedNode, - const int maxDepth, const algorithmFPType * x, algorithmFPType * phi, unsigned nodeIndex, size_t depth, unsigned uniqueDepth, - PathElement * parentUniquePath, float pWeightsResidual, float parentZeroFraction, float parentOneFraction, - int parentFeatureIndex, int & leafCount) -{ - // TODO: Add support for multi-class output - const size_t numOutputs = 1; - - const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; - const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; - const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; - const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; - - // extend the unique path - PathElement * uniquePath = parentUniquePath + uniqueDepth; - const size_t nBytes = uniqueDepth * sizeof(PathElement); - const int copyStatus = daal::services::internal::daal_memcpy_s(uniquePath, nBytes, parentUniquePath, nBytes); - DAAL_ASSERT(copyStatus == 0); - - uniquePath[uniqueDepth].featureIndex = parentFeatureIndex; - uniquePath[uniqueDepth].zeroFraction = parentZeroFraction; - uniquePath[uniqueDepth].oneFraction = parentOneFraction; - // update pWeightsResidual if the feature of the last split does not satisfy the threshold - if (parentOneFraction != 1) - { - pWeightsResidual *= parentZeroFraction; - } - - const bool isLeaf = gbt::internal::ModelImpl::nodeIsLeaf(nodeIndex, *tree, depth); - // leaf node - if (isLeaf) - { - const algorithmFPType * leafCombinationSum = combinationSum + leafCount * (1 << maxDepth); - // use combinationSumInd to search in the row of combinationSum corresponding to the current path - unsigned combinationSumInd = 0; - for (unsigned i = 1; i <= uniqueDepth; ++i) - { - if (uniquePath[i].oneFraction != 0) - { - combinationSumInd += 1 << (i - 1); - } - } - // update contributions to SHAP values for features satisfying the thresholds and not satisfying the thresholds separately - const unsigned valuesOffset = nodeIndex * numOutputs; - unsigned valuesNonZeroInd = 0; - unsigned valuesNonZeroCount = 0; - for (unsigned j = 0; j < numOutputs; ++j) - { - if (splitValues[valuesOffset + j] != 0) - { - valuesNonZeroInd = j; - valuesNonZeroCount++; - } - } - const algorithmFPType scaleZero = -leafCombinationSum[combinationSumInd] * pWeightsResidual; - for (unsigned i = 1; i <= uniqueDepth; ++i) - { - const PathElement & el = uniquePath[i]; - const unsigned phiOffset = el.featureIndex * numOutputs; - const algorithmFPType scale = - (el.oneFraction != 0) ? leafCombinationSum[combinationSumInd - (1 << (i - 1))] * pWeightsResidual * (1 - el.zeroFraction) : scaleZero; - if (valuesNonZeroCount == 1) - { - phi[phiOffset + valuesNonZeroInd] += scale * splitValues[valuesOffset + valuesNonZeroInd]; - } - else - { - for (unsigned j = 0; j < numOutputs; ++j) - { - phi[phiOffset + j] += scale * splitValues[valuesOffset + j]; - } - } - } - ++leafCount; - - return; - } - - const unsigned leftIndex = 2 * nodeIndex; - const unsigned rightIndex = 2 * nodeIndex + 1; - const algorithmFPType w = nodeCoverValues[nodeIndex]; - const algorithmFPType leftZeroFraction = nodeCoverValues[leftIndex] / w; - const algorithmFPType rightZeroFraction = nodeCoverValues[rightIndex] / w; - algorithmFPType incomingZeroFraction = 1; - algorithmFPType incomingOneFraction = 1; - - // see if we have already split on this feature, - // if so we undo that split so we can redo it for this node - const int pathIndex = duplicatedNode[nodeIndex]; - if (pathIndex >= 0) - { - incomingZeroFraction = uniquePath[pathIndex].zeroFraction; - incomingOneFraction = uniquePath[pathIndex].oneFraction; - - for (unsigned i = pathIndex; i < uniqueDepth; ++i) - { - uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; - uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; - uniquePath[i].oneFraction = uniquePath[i + 1].oneFraction; - } - --uniqueDepth; - // update pWeightsResidual iff the duplicated feature does not satisfy the threshold - if (incomingOneFraction != 1.) - { - pWeightsResidual /= incomingZeroFraction; - } - } - - const FeatureIndexType splitIndex = fIndexes[nodeIndex]; - bool isLeftSplit = x[splitIndex] <= splitValues[nodeIndex]; - - treeShap( - tree, combinationSum, duplicatedNode, maxDepth, x, phi, leftIndex, depth + 1, uniqueDepth + 1, uniquePath, pWeightsResidual, - leftZeroFraction * incomingZeroFraction, incomingOneFraction * isLeftSplit, splitIndex, leafCount); - - treeShap( - tree, combinationSum, duplicatedNode, maxDepth, x, phi, rightIndex, depth + 1, uniqueDepth + 1, uniquePath, pWeightsResidual, - rightZeroFraction * incomingZeroFraction, incomingOneFraction * (!isLeftSplit), splitIndex, leafCount); -} - -/** - * \brief Version 2, i.e. second Fast TreeSHAP algorithm - * \param tree current tree - * \param treeIndex index of current tree - * \param x dense data matrix - * \param phi dense output matrix of feature attributions - * \param featureHelper pointer to a FeatureTypes object (required to traverse tree) - * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) - * \param conditionFeature the index of the feature to fix - */ -template -inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, size_t treeIndex, const algorithmFPType * x, algorithmFPType * phi, - const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, - const ModelDetails & modelDetails) -{ - services::Status st; - - // // update the reference value with the expected value of the tree's predictions - // for (unsigned j = 0; j < tree.numOutputs; ++j) - // { - // phi[data.M * tree.numOutputs + j] += tree.values[j]; - // } - - const int depth = tree->getMaxLvl(); - const size_t nElements = (depth + 1) * (depth + 2) / 2; - PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nElements)); - DAAL_CHECK_MALLOC(uniquePathData) - PathElement init; - for (size_t i = 0; i < nElements; ++i) - { - DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); - } - int leafCount = 0; - - algorithmFPType * combinationSum = modelDetails.combinationSum + treeIndex * modelDetails.maxLeafs * modelDetails.maxCombinations; - int * duplicatedNode = modelDetails.duplicatedNode + treeIndex * modelDetails.maxNodes; - // hand over duplicatedNode - 1 because we use 1-based node indexing - treeShap(tree, combinationSum, duplicatedNode - 1, modelDetails.maxDepth, x, phi, 1, 0, 0, - uniquePathData, 1, 1, 1, -1, leafCount); - - daal_free(uniquePathData); - return st; -} -} // namespace v2 } // namespace internal -/** - * \brief Return the combination sum, required for Fast TreeSHAP v2 -*/ -template -services::Status computeCombinationSum(const gbt::internal::GbtDecisionTree * tree, const size_t treeIndex, - const ModelDetails & modelDetails) -{ - if (!modelDetails.requiresPrecompute) - { - // nothing to be done - return services::Status(); - } - if (!modelDetails.combinationSum || !modelDetails.duplicatedNode) - { - // buffer wasn't properly allocated - return services::Status(ErrorMemoryAllocationFailed); - } - - algorithmFPType * combinationSum = modelDetails.combinationSum + treeIndex * modelDetails.maxLeafs * modelDetails.maxCombinations; - int * duplicatedNode = modelDetails.duplicatedNode + treeIndex * modelDetails.maxNodes; - // hand over duplicatedNode - 1 because we use 1-based node indexing - return treeshap::internal::v2::computeCombinationSum(tree, combinationSum, duplicatedNode - 1, modelDetails.maxDepth); -} - /** * \brief Recursive function that computes the feature attributions for a single tree. * \param tree current tree @@ -918,9 +446,8 @@ services::Status computeCombinationSum(const gbt::internal::GbtDecisionTree * tr * \param conditionFeature the index of the feature to fix */ template -inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, size_t treeIndex, const algorithmFPType * x, algorithmFPType * phi, - const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, - const ModelDetails & modelDetails) +inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, + const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) { DAAL_ASSERT(x); DAAL_ASSERT(phi); @@ -936,9 +463,6 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, si case 1: return treeshap::internal::v1::treeShap(tree, x, phi, featureHelper, condition, conditionFeature); - case 2: - return treeshap::internal::v2::treeShap(tree, treeIndex, x, phi, featureHelper, - condition, conditionFeature, modelDetails); default: return services::Status(ErrorMethodNotImplemented); } } From d3968422d20cb099912619547f6257e493df3b96 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 27 Sep 2023 09:24:42 -0700 Subject: [PATCH 09/50] align tree cfl/reg APIs --- .../decision_forest_classification_model_builder.h | 14 ++++++++++---- .../gbt_regression_model_builder.h | 10 +++++----- cpp/daal/src/algorithms/dtrees/dtrees_model.cpp | 2 +- cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h | 2 +- .../df_classification_model_builder.cpp | 4 ++-- .../gbt_classification_model_builder.cpp | 2 +- .../regression/gbt_regression_model_builder.cpp | 4 ++-- 7 files changed, 22 insertions(+), 16 deletions(-) diff --git a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h index f656885c538..6a5907d7365 100644 --- a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h +++ b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h @@ -107,9 +107,12 @@ class DAAL_EXPORT ModelBuilder * \param[in] parentId Parent node to which new node is added (use noParent for root node) * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] classLabel Class label to be predicted + * \param[in] defaultLeft Behaviour in case of missing values + * \param[in] cover Cover (Hessian sum) of the node * \return Node identifier */ - NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, const double cover) + NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, const int defaultLeft, + const double cover) { NodeId resId; _status |= addLeafNodeInternal(treeId, parentId, position, classLabel, cover, resId); @@ -123,6 +126,7 @@ class DAAL_EXPORT ModelBuilder * \param[in] parentId Parent node to which new node is added (use noParent for root node) * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] proba Array with probability values for each class + * \param[in] cover Cover (Hessian sum) of the node * \return Node identifier */ NodeId addLeafNodeByProba(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba, const double cover) @@ -140,13 +144,15 @@ class DAAL_EXPORT ModelBuilder * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] featureIndex Feature index for splitting * \param[in] featureValue Feature value for splitting + * \param[in] defaultLeft Behaviour in case of missing values + * \param[in] cover Cover (Hessian sum) of the node * \return Node identifier */ NodeId addSplitNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, const double featureValue, - const double cover) + const int defaultLeft, const double cover) { NodeId resId; - _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, cover, resId); + _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, defaultLeft, cover, resId); services::throwIfPossible(_status); return resId; } @@ -190,7 +196,7 @@ class DAAL_EXPORT ModelBuilder services::Status addLeafNodeByProbaInternal(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba, const double cover, NodeId & res); services::Status addSplitNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, - const double featureValue, const double cover, const int defaultLeft, NodeId & res); + const double featureValue, const int defaultLeft, const double cover, NodeId & res); private: size_t _nClasses; diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h index bcde75d847a..56ac526437e 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h @@ -125,14 +125,14 @@ class DAAL_EXPORT ModelBuilder * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] featureIndex Feature index for splitting * \param[in] featureValue Feature value for splitting - * \param[in] cover Cover of the node (sum_hess) * \param[in] defaultLeft Behaviour in case of missing values + * \param[in] cover Cover of the node (sum_hess) * \return Node identifier */ - NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, double cover, int defaultLeft = 0) + NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, int defaultLeft, double cover) { NodeId resId; - _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, cover, resId, defaultLeft); + _status |= addSplitNodeInternal(treeId, parentId, position, featureIndex, featureValue, defaultLeft, cover, resId); services::throwIfPossible(_status); return resId; } @@ -160,8 +160,8 @@ class DAAL_EXPORT ModelBuilder services::Status initialize(size_t nFeatures, size_t nIterations); services::Status createTreeInternal(size_t nNodes, TreeId & resId); services::Status addLeafNodeInternal(TreeId treeId, NodeId parentId, size_t position, double response, double cover, NodeId & res); - services::Status addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, double cover, - int defaultLeft, NodeId & res); + services::Status addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, int defaultLeft, + double cover, NodeId & res); services::Status convertModelInternal(); }; /** @} */ diff --git a/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp b/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp index a7d5286933c..32a298dc34b 100644 --- a/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/dtrees_model.cpp @@ -224,7 +224,7 @@ void setProbabilities(const size_t treeId, const size_t nodeId, const size_t res } services::Status addSplitNodeInternal(data_management::DataCollectionPtr & serializationData, size_t treeId, size_t parentId, size_t position, - size_t featureIndex, double featureValue, double cover, int defaultLeft, size_t & res) + size_t featureIndex, double featureValue, int defaultLeft, double cover, size_t & res) { const size_t noParent = static_cast(-1); services::Status s; diff --git a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h index d383a4ddc7b..15cb6a92dbe 100644 --- a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h @@ -346,7 +346,7 @@ void setNode(DecisionTreeNode & node, int featureIndex, size_t classLabel, doubl void setNode(DecisionTreeNode & node, int featureIndex, double response, double cover); services::Status addSplitNodeInternal(data_management::DataCollectionPtr & serializationData, size_t treeId, size_t parentId, size_t position, - size_t featureIndex, double featureValue, double cover, int defaultLeft, size_t & res); + size_t featureIndex, double featureValue, int defaultLeft, double cover, size_t & res); void setProbabilities(const size_t treeId, const size_t nodeId, const size_t response, const data_management::DataCollectionPtr probTbl, const double * const prob); diff --git a/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp index fffc3dadd70..ad53f087081 100644 --- a/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/forest/classification/df_classification_model_builder.cpp @@ -117,12 +117,12 @@ services::Status ModelBuilder::addLeafNodeByProbaInternal(const TreeId treeId, c } services::Status ModelBuilder::addSplitNodeInternal(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, - const double featureValue, const double cover, const int defaultLeft, NodeId & res) + const double featureValue, const int defaultLeft, const double cover, NodeId & res) { decision_forest::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, cover, defaultLeft, res); + featureValue, defaultLeft, cover, res); } } // namespace interface2 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp index d13f4e665d0..e830520cb54 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp @@ -134,7 +134,7 @@ services::Status ModelBuilder::addSplitNodeInternal(TreeId treeId, NodeId parent gbt::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, cover, defaultLeft, res); + featureValue, defaultLeft, cover, res); } } // namespace interface1 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp index 90d66ec943a..7c1a1d10bf2 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_builder.cpp @@ -80,12 +80,12 @@ services::Status ModelBuilder::addLeafNodeInternal(TreeId treeId, NodeId parentI } services::Status ModelBuilder::addSplitNodeInternal(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue, - double cover, int defaultLeft, NodeId & res) + int defaultLeft, double cover, NodeId & res) { gbt::regression::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); return daal::algorithms::dtrees::internal::addSplitNodeInternal(modelImplRef._serializationData, treeId, parentId, position, featureIndex, - featureValue, cover, defaultLeft, res); + featureValue, defaultLeft, cover, res); } } // namespace interface1 From 63da2124d5d55955cab8ae9f05b99a40ea4a71b3 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 27 Sep 2023 09:26:51 -0700 Subject: [PATCH 10/50] restore .gitignore from master --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 68b50b090b9..f61844615bf 100644 --- a/.gitignore +++ b/.gitignore @@ -24,5 +24,3 @@ bazel-* # CMake directories and cache CMakeFiles CMakeCache.txt - -docs/doxygen/daal/doxygen From 9032573b9d5e941e3ee7bb1419e8b2676daf8132 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 28 Sep 2023 03:31:09 -0700 Subject: [PATCH 11/50] cleanup for review --- .../src/algorithms/dtrees/gbt/treeshap.cpp | 44 ++++++++----------- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 8 ++-- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index e8d5390af3c..928ec7e253b 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -1,3 +1,19 @@ +/******************************************************************************* +* Copyright 2023 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + #include "src/algorithms/dtrees/gbt/treeshap.h" namespace daal @@ -9,14 +25,14 @@ namespace gbt namespace treeshap { -uint8_t getRequestedAlgorithmVersion() +uint8_t getRequestedAlgorithmVersion(uint8_t fallback) { char * val = getenv("SHAP_VERSION"); if (val) { return atoi(val); } - return 0; + return fallback; } namespace internal { @@ -77,30 +93,6 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t float nextOnePortion = uniquePath[uniqueDepth].partialWeight; float total = 0; - // if (oneFraction != 0) - // { - // float nextOnePortion = uniquePath[uniqueDepth].partialWeight; - // for (int i = uniqueDepth - 1; i >= 0; --i) - // { - // const float tmp = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); - // total += tmp; - // nextOnePortion = uniquePath[i].partialWeight - tmp * zeroFraction * ((uniqueDepth - i) / static_cast(uniqueDepth + 1)); - // } - // } - // else if (zeroFraction != 0) - // { - // for (int i = uniqueDepth - 1; i >= 0; --i) - // { - // total += uniquePath[i].partialWeight * (uniqueDepth + 1) / ((uniqueDepth - i) * zeroFraction); - // } - // } - // else - // { - // for (int i = uniqueDepth - 1; i >= 0; --i) - // { - // DAAL_ASSERT(uniquePath[i].partialWeight == 0); - // } - // } for (int i = uniqueDepth - 1; i >= 0; --i) { diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 762db0de73b..087cdd87308 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -53,9 +53,9 @@ using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; /** * Determine the requested version of the TreeSHAP algorithm set in the * environment variable SHAP_VERSION. - * Defaults to 0 if SHAP_VERSION is not set. + * Returns fallback if SHAP_VERSION is not set. */ -uint8_t getRequestedAlgorithmVersion(); +uint8_t getRequestedAlgorithmVersion(uint8_t fallback); /** * Decision Path context @@ -250,7 +250,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith // stop if we have no weight coming down to us if (conditionFraction < FLT_EPSILON) return; - const size_t numOutputs = 1; // TODO: support multi-output models + const size_t numOutputs = 1; // currently only support single-output models const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; @@ -453,7 +453,7 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co DAAL_ASSERT(phi); DAAL_ASSERT(featureHelper); - uint8_t shapVersion = getRequestedAlgorithmVersion(); + uint8_t shapVersion = getRequestedAlgorithmVersion(1); switch (shapVersion) { From 2cb96e120336668abe519284d83950071afcc37b Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 28 Sep 2023 03:57:33 -0700 Subject: [PATCH 12/50] add newline --- cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index 928ec7e253b..4904a4b542f 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -211,4 +211,4 @@ float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, uns } // namespace treeshap } // namespace gbt } // namespace algorithms -} // namespace daal \ No newline at end of file +} // namespace daal From b873df40d38d3aaf077104c0c6fef0de21b66ee4 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 28 Sep 2023 07:38:27 -0700 Subject: [PATCH 13/50] remove defaultLeft value that's not needed --- .../decision_forest_classification_model_builder.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h index 6a5907d7365..57c02325ac7 100644 --- a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h +++ b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h @@ -107,12 +107,10 @@ class DAAL_EXPORT ModelBuilder * \param[in] parentId Parent node to which new node is added (use noParent for root node) * \param[in] position Position in parent (e.g. 0 for left and 1 for right child in a binary tree) * \param[in] classLabel Class label to be predicted - * \param[in] defaultLeft Behaviour in case of missing values * \param[in] cover Cover (Hessian sum) of the node * \return Node identifier */ - NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, const int defaultLeft, - const double cover) + NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel, const double cover) { NodeId resId; _status |= addLeafNodeInternal(treeId, parentId, position, classLabel, cover, resId); From 7cf533fbd2a50a2250651b62019fb77f8ca5d213 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 28 Sep 2023 07:39:01 -0700 Subject: [PATCH 14/50] Update model builder examples --- .../df_cls_dense_batch_model_builder.cpp | 26 ++++++++++++------- .../df_cls_traversed_model_builder.cpp | 18 ++++++++++--- .../gbt_cls_traversed_model_builder.cpp | 18 ++++++++++--- .../gbt_reg_traversed_model_builder.cpp | 14 +++++++--- 4 files changed, 56 insertions(+), 20 deletions(-) diff --git a/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp b/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp index dab3de1ceb4..ccd32013df5 100644 --- a/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp +++ b/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp @@ -64,23 +64,31 @@ decision_forest::classification::ModelPtr buildModel() { ModelBuilder modelBuilder(nClasses, nTrees); ModelBuilder::TreeId tree1 = modelBuilder.createTree(nNodes); ModelBuilder::NodeId root1 = - modelBuilder.addSplitNode(tree1, ModelBuilder::noParent, 0, 0, 0.174108); - /* ModelBuilder::NodeId child12 = */ modelBuilder.addLeafNode(tree1, root1, 1, 4); + modelBuilder.addSplitNode(tree1, ModelBuilder::noParent, 0, 0, 0.174108, 0, 1.0); + /* ModelBuilder::NodeId child12 = */ modelBuilder.addLeafNode(tree1, root1, 1, 4, 0.5); double proba11[] = { 0.8, 0.1, 0.0, 0.1, 0.0 }; - /* ModelBuilder::NodeId child11 = */ modelBuilder.addLeafNodeByProba(tree1, root1, 0, proba11); + /* ModelBuilder::NodeId child11 = */ modelBuilder.addLeafNodeByProba(tree1, + root1, + 0, + proba11, + 0.5); ModelBuilder::TreeId tree2 = modelBuilder.createTree(nNodes); ModelBuilder::NodeId root2 = - modelBuilder.addSplitNode(tree2, ModelBuilder::noParent, 0, 1, 0.571184); - /* ModelBuilder::NodeId child22 = */ modelBuilder.addLeafNode(tree2, root2, 1, 4); - /* ModelBuilder::NodeId child21 = */ modelBuilder.addLeafNode(tree2, root2, 0, 2); + modelBuilder.addSplitNode(tree2, ModelBuilder::noParent, 0, 1, 0.571184, 0, 1.0); + /* ModelBuilder::NodeId child22 = */ modelBuilder.addLeafNode(tree2, root2, 1, 4, 0.5); + /* ModelBuilder::NodeId child21 = */ modelBuilder.addLeafNode(tree2, root2, 0, 2, 0.5); ModelBuilder::TreeId tree3 = modelBuilder.createTree(nNodes); ModelBuilder::NodeId root3 = - modelBuilder.addSplitNode(tree3, ModelBuilder::noParent, 0, 0, 0.303995); + modelBuilder.addSplitNode(tree3, ModelBuilder::noParent, 0, 0, 0.303995, 0, 1.0); double proba32[] = { 0.05, 0.1, 0.0, 0.1, 0.75 }; - /* ModelBuilder::NodeId child32 = */ modelBuilder.addLeafNodeByProba(tree3, root3, 1, proba32); - /* ModelBuilder::NodeId child31 = */ modelBuilder.addLeafNode(tree3, root3, 0, 2); + /* ModelBuilder::NodeId child32 = */ modelBuilder.addLeafNodeByProba(tree3, + root3, + 1, + proba32, + 0.5); + /* ModelBuilder::NodeId child31 = */ modelBuilder.addLeafNode(tree3, root3, 0, 2, 0.5); modelBuilder.setNFeatures(nFeatures); return modelBuilder.getModel(); } diff --git a/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp b/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp index 2e02b25c7bb..dcbd7a3d985 100644 --- a/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp +++ b/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp @@ -138,13 +138,17 @@ bool buildTree(size_t treeId, bool &isRoot, ModelBuilder &builder, std::map &parentMap) { + const int dummyDefaultLeft = 0; // default behaviour for missing values + const double dummyCoverValue = 1.0f; // ignoring cover here, should be extracted from input node if (node->left != NULL && node->right != NULL) { if (isRoot) { ModelBuilder::NodeId parent = builder.addSplitNode(treeId, ModelBuilder::noParent, 0, node->featureIndex, - node->featureValue); + node->featureValue, + dummyDefaultLeft, + dummyCoverValue); parentMap[node->left] = ParentPlace(parent, 0); ; @@ -158,7 +162,9 @@ bool buildTree(size_t treeId, p.parentId, p.place, node->featureIndex, - node->featureValue); + node->featureValue, + dummyDefaultLeft, + dummyCoverValue); parentMap[node->left] = ParentPlace(parent, 0); ; @@ -168,12 +174,16 @@ bool buildTree(size_t treeId, } else { if (isRoot) { - builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->classLabel); + builder.addLeafNode(treeId, + ModelBuilder::noParent, + 0, + node->classLabel, + dummyCoverValue); isRoot = false; } else { ParentPlace p = parentMap[node]; - builder.addLeafNode(treeId, p.parentId, p.place, node->classLabel); + builder.addLeafNode(treeId, p.parentId, p.place, node->classLabel, dummyCoverValue); } return true; } diff --git a/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp b/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp index 71d1c22d6d4..e4192d4185b 100644 --- a/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp +++ b/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp @@ -218,13 +218,17 @@ bool buildTree(size_t treeId, bool &isRoot, ModelBuilder &builder, const ParentPlace &parentPlace) { + const int dummyDefaultLeft = 0; // default behaviour for missing values + const double dummyCoverValue = 1.0f; // ignoring cover here, should be extracted from input node if (node->left != NULL && node->right != NULL) { if (isRoot) { ModelBuilder::NodeId parent = builder.addSplitNode(treeId, ModelBuilder::noParent, 0, node->featureIndex, - node->featureValue); + node->featureValue, + dummyDefaultLeft, + dummyCoverValue); isRoot = false; buildTree(treeId, node->left, isRoot, builder, ParentPlace(parent, 0)); @@ -235,7 +239,9 @@ bool buildTree(size_t treeId, parentPlace.parentId, parentPlace.place, node->featureIndex, - node->featureValue); + node->featureValue, + dummyDefaultLeft, + dummyCoverValue); buildTree(treeId, node->left, isRoot, builder, ParentPlace(parent, 0)); buildTree(treeId, node->right, isRoot, builder, ParentPlace(parent, 1)); @@ -243,11 +249,15 @@ bool buildTree(size_t treeId, } else { if (isRoot) { - builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response); + builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response, dummyCoverValue); isRoot = false; } else { - builder.addLeafNode(treeId, parentPlace.parentId, parentPlace.place, node->response); + builder.addLeafNode(treeId, + parentPlace.parentId, + parentPlace.place, + node->response, + dummyCoverValue); } } diff --git a/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp b/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp index c8bb826f398..f4ad3675f26 100644 --- a/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp +++ b/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp @@ -216,6 +216,8 @@ bool buildTree(size_t treeId, bool &isRoot, ModelBuilder &builder, const ParentPlace &parentPlace) { + const int dummyDefaultLeft = 0; // default behaviour for missing values + const double dummyCoverValue = 1.0f; // ignoring cover here, should be extracted from input node if (node->left != NULL && node->right != NULL) { if (isRoot) { ModelBuilder::NodeId parent = builder.addSplitNode(treeId, @@ -233,7 +235,9 @@ bool buildTree(size_t treeId, parentPlace.parentId, parentPlace.place, node->featureIndex, - node->featureValue); + node->featureValue, + dummyDefaultLeft, + dummyCoverValue); buildTree(treeId, node->left, isRoot, builder, ParentPlace(parent, 0)); buildTree(treeId, node->right, isRoot, builder, ParentPlace(parent, 1)); @@ -241,11 +245,15 @@ bool buildTree(size_t treeId, } else { if (isRoot) { - builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response); + builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response, dummyCoverValue); isRoot = false; } else { - builder.addLeafNode(treeId, parentPlace.parentId, parentPlace.place, node->response); + builder.addLeafNode(treeId, + parentPlace.parentId, + parentPlace.place, + node->response, + dummyCoverValue); } } From 55f3cb28e9172ba819c9760ccdcde3a62a009caf Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 28 Sep 2023 08:37:00 -0700 Subject: [PATCH 15/50] Add backwards-compatible model builder API & deprecate decls --- ...sion_forest_classification_model_builder.h | 25 ++++++++++++++++++ .../gbt_classification_model_builder.h | 16 ++++++++++++ .../gbt_regression_model_builder.h | 16 ++++++++++++ .../df_cls_dense_batch_model_builder.cpp | 26 +++++++------------ .../df_cls_traversed_model_builder.cpp | 18 +++---------- .../gbt_cls_traversed_model_builder.cpp | 18 +++---------- .../gbt_reg_traversed_model_builder.cpp | 14 +++------- 7 files changed, 77 insertions(+), 56 deletions(-) diff --git a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h index 57c02325ac7..cf1041c25db 100644 --- a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h +++ b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h @@ -118,6 +118,14 @@ class DAAL_EXPORT ModelBuilder return resId; } + /** + * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + */ + DAAL_DEPRECATED NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel) + { + return addLeafNode(treeId, parentId, position, classLabel, 0); + } + /** * Create Leaf node and add it to certain tree * \param[in] treeId Tree to which new node is added @@ -135,6 +143,14 @@ class DAAL_EXPORT ModelBuilder return resId; } + /** + * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + */ + DAAL_DEPRECATED NodeId addLeafNodeByProba(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba) + { + return addLeafNodeByProba(treeId, parentId, position, proba, 0); + } + /** * Create Split node and add it to certain tree * \param[in] treeId Tree to which new node is added @@ -155,6 +171,15 @@ class DAAL_EXPORT ModelBuilder return resId; } + /** + * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + */ + DAAL_DEPRECATED NodeId addSplitNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, + const double featureValue) + { + return addSplitNode(treeId, parentId, position, featureIndex, featureValue, 0, 0); + } + void setNFeatures(size_t nFeatures) { if (!_model.get()) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h index 71dfdbfb3a5..e0f8b00f855 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h @@ -120,6 +120,14 @@ class DAAL_EXPORT ModelBuilder return resId; } + /** + * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + */ + DAAL_DEPRECATED NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response) + { + return addLeafNode(treeId, parentId, position, response, 0); + } + /** * Create Split node and add it to certain tree * \param[in] treeId Tree to which new node is added @@ -139,6 +147,14 @@ class DAAL_EXPORT ModelBuilder return resId; } + /** + * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + */ + DAAL_DEPRECATED NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue) + { + return addSplitNode(treeId, parentId, position, featureIndex, featureValue, 0, 0); + } + /** * Get built model * \return Model pointer diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h index 56ac526437e..23dbe6846d4 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h @@ -118,6 +118,14 @@ class DAAL_EXPORT ModelBuilder return resId; } + /** + * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + */ + DAAL_DEPRECATED NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response) + { + return addLeafNode(treeId, parentId, position, response, 0); + } + /** * Create Split node and add it to certain tree * \param[in] treeId Tree to which new node is added @@ -137,6 +145,14 @@ class DAAL_EXPORT ModelBuilder return resId; } + /** + * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + */ + DAAL_DEPRECATED NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue) + { + return addSplitNode(treeId, parentId, position, featureIndex, featureValue, 0, 0); + } + /** * Get built model * \return Model pointer diff --git a/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp b/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp index ccd32013df5..dab3de1ceb4 100644 --- a/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp +++ b/examples/daal/cpp/source/decision_forest/df_cls_dense_batch_model_builder.cpp @@ -64,31 +64,23 @@ decision_forest::classification::ModelPtr buildModel() { ModelBuilder modelBuilder(nClasses, nTrees); ModelBuilder::TreeId tree1 = modelBuilder.createTree(nNodes); ModelBuilder::NodeId root1 = - modelBuilder.addSplitNode(tree1, ModelBuilder::noParent, 0, 0, 0.174108, 0, 1.0); - /* ModelBuilder::NodeId child12 = */ modelBuilder.addLeafNode(tree1, root1, 1, 4, 0.5); + modelBuilder.addSplitNode(tree1, ModelBuilder::noParent, 0, 0, 0.174108); + /* ModelBuilder::NodeId child12 = */ modelBuilder.addLeafNode(tree1, root1, 1, 4); double proba11[] = { 0.8, 0.1, 0.0, 0.1, 0.0 }; - /* ModelBuilder::NodeId child11 = */ modelBuilder.addLeafNodeByProba(tree1, - root1, - 0, - proba11, - 0.5); + /* ModelBuilder::NodeId child11 = */ modelBuilder.addLeafNodeByProba(tree1, root1, 0, proba11); ModelBuilder::TreeId tree2 = modelBuilder.createTree(nNodes); ModelBuilder::NodeId root2 = - modelBuilder.addSplitNode(tree2, ModelBuilder::noParent, 0, 1, 0.571184, 0, 1.0); - /* ModelBuilder::NodeId child22 = */ modelBuilder.addLeafNode(tree2, root2, 1, 4, 0.5); - /* ModelBuilder::NodeId child21 = */ modelBuilder.addLeafNode(tree2, root2, 0, 2, 0.5); + modelBuilder.addSplitNode(tree2, ModelBuilder::noParent, 0, 1, 0.571184); + /* ModelBuilder::NodeId child22 = */ modelBuilder.addLeafNode(tree2, root2, 1, 4); + /* ModelBuilder::NodeId child21 = */ modelBuilder.addLeafNode(tree2, root2, 0, 2); ModelBuilder::TreeId tree3 = modelBuilder.createTree(nNodes); ModelBuilder::NodeId root3 = - modelBuilder.addSplitNode(tree3, ModelBuilder::noParent, 0, 0, 0.303995, 0, 1.0); + modelBuilder.addSplitNode(tree3, ModelBuilder::noParent, 0, 0, 0.303995); double proba32[] = { 0.05, 0.1, 0.0, 0.1, 0.75 }; - /* ModelBuilder::NodeId child32 = */ modelBuilder.addLeafNodeByProba(tree3, - root3, - 1, - proba32, - 0.5); - /* ModelBuilder::NodeId child31 = */ modelBuilder.addLeafNode(tree3, root3, 0, 2, 0.5); + /* ModelBuilder::NodeId child32 = */ modelBuilder.addLeafNodeByProba(tree3, root3, 1, proba32); + /* ModelBuilder::NodeId child31 = */ modelBuilder.addLeafNode(tree3, root3, 0, 2); modelBuilder.setNFeatures(nFeatures); return modelBuilder.getModel(); } diff --git a/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp b/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp index dcbd7a3d985..2e02b25c7bb 100644 --- a/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp +++ b/examples/daal/cpp/source/decision_forest/df_cls_traversed_model_builder.cpp @@ -138,17 +138,13 @@ bool buildTree(size_t treeId, bool &isRoot, ModelBuilder &builder, std::map &parentMap) { - const int dummyDefaultLeft = 0; // default behaviour for missing values - const double dummyCoverValue = 1.0f; // ignoring cover here, should be extracted from input node if (node->left != NULL && node->right != NULL) { if (isRoot) { ModelBuilder::NodeId parent = builder.addSplitNode(treeId, ModelBuilder::noParent, 0, node->featureIndex, - node->featureValue, - dummyDefaultLeft, - dummyCoverValue); + node->featureValue); parentMap[node->left] = ParentPlace(parent, 0); ; @@ -162,9 +158,7 @@ bool buildTree(size_t treeId, p.parentId, p.place, node->featureIndex, - node->featureValue, - dummyDefaultLeft, - dummyCoverValue); + node->featureValue); parentMap[node->left] = ParentPlace(parent, 0); ; @@ -174,16 +168,12 @@ bool buildTree(size_t treeId, } else { if (isRoot) { - builder.addLeafNode(treeId, - ModelBuilder::noParent, - 0, - node->classLabel, - dummyCoverValue); + builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->classLabel); isRoot = false; } else { ParentPlace p = parentMap[node]; - builder.addLeafNode(treeId, p.parentId, p.place, node->classLabel, dummyCoverValue); + builder.addLeafNode(treeId, p.parentId, p.place, node->classLabel); } return true; } diff --git a/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp b/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp index e4192d4185b..71d1c22d6d4 100644 --- a/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp +++ b/examples/daal/cpp/source/gradient_boosted_trees/gbt_cls_traversed_model_builder.cpp @@ -218,17 +218,13 @@ bool buildTree(size_t treeId, bool &isRoot, ModelBuilder &builder, const ParentPlace &parentPlace) { - const int dummyDefaultLeft = 0; // default behaviour for missing values - const double dummyCoverValue = 1.0f; // ignoring cover here, should be extracted from input node if (node->left != NULL && node->right != NULL) { if (isRoot) { ModelBuilder::NodeId parent = builder.addSplitNode(treeId, ModelBuilder::noParent, 0, node->featureIndex, - node->featureValue, - dummyDefaultLeft, - dummyCoverValue); + node->featureValue); isRoot = false; buildTree(treeId, node->left, isRoot, builder, ParentPlace(parent, 0)); @@ -239,9 +235,7 @@ bool buildTree(size_t treeId, parentPlace.parentId, parentPlace.place, node->featureIndex, - node->featureValue, - dummyDefaultLeft, - dummyCoverValue); + node->featureValue); buildTree(treeId, node->left, isRoot, builder, ParentPlace(parent, 0)); buildTree(treeId, node->right, isRoot, builder, ParentPlace(parent, 1)); @@ -249,15 +243,11 @@ bool buildTree(size_t treeId, } else { if (isRoot) { - builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response, dummyCoverValue); + builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response); isRoot = false; } else { - builder.addLeafNode(treeId, - parentPlace.parentId, - parentPlace.place, - node->response, - dummyCoverValue); + builder.addLeafNode(treeId, parentPlace.parentId, parentPlace.place, node->response); } } diff --git a/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp b/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp index f4ad3675f26..c8bb826f398 100644 --- a/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp +++ b/examples/daal/cpp/source/gradient_boosted_trees/gbt_reg_traversed_model_builder.cpp @@ -216,8 +216,6 @@ bool buildTree(size_t treeId, bool &isRoot, ModelBuilder &builder, const ParentPlace &parentPlace) { - const int dummyDefaultLeft = 0; // default behaviour for missing values - const double dummyCoverValue = 1.0f; // ignoring cover here, should be extracted from input node if (node->left != NULL && node->right != NULL) { if (isRoot) { ModelBuilder::NodeId parent = builder.addSplitNode(treeId, @@ -235,9 +233,7 @@ bool buildTree(size_t treeId, parentPlace.parentId, parentPlace.place, node->featureIndex, - node->featureValue, - dummyDefaultLeft, - dummyCoverValue); + node->featureValue); buildTree(treeId, node->left, isRoot, builder, ParentPlace(parent, 0)); buildTree(treeId, node->right, isRoot, builder, ParentPlace(parent, 1)); @@ -245,15 +241,11 @@ bool buildTree(size_t treeId, } else { if (isRoot) { - builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response, dummyCoverValue); + builder.addLeafNode(treeId, ModelBuilder::noParent, 0, node->response); isRoot = false; } else { - builder.addLeafNode(treeId, - parentPlace.parentId, - parentPlace.place, - node->response, - dummyCoverValue); + builder.addLeafNode(treeId, parentPlace.parentId, parentPlace.place, node->response); } } From c130bb052b792226a91a3bdbde032c31124180bb Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 4 Oct 2023 08:37:33 -0700 Subject: [PATCH 16/50] fix: remove dead code --- cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp index bb9ac86be75..4626adc2e35 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp @@ -327,7 +327,6 @@ services::Status ModelImpl::convertDecisionTreesToGbtTrees(data_management::Data DAAL_CHECK_MALLOC(newTrees) for (size_t i = 0; i < size; ++i) { - *(DecisionTreeTable *)(*(serializationData))[i].get(); const DecisionTreeTable & tree = *(DecisionTreeTable *)(*(serializationData))[i].get(); GbtDecisionTree * newTree = allocateGbtTree(tree); decisionTreeToGbtTree(tree, *newTree); From 22b4fb9d7d45c1a30361758cda026d83da6b5032 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 4 Oct 2023 08:38:17 -0700 Subject: [PATCH 17/50] fix: simplify number of nodes calculation --- cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h index eacc9d24d53..c2868272311 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h @@ -46,10 +46,7 @@ typedef services::Collection NodeIdxArray; static inline size_t getNumberOfNodesByLvls(const size_t nLvls) { - size_t nNodes = 2; // nNodes = pow(2, nLvl+1) - 1 - for (size_t i = 0; i < nLvls; ++i) nNodes *= 2; - nNodes--; - return nNodes; + return (1 << (nLvls + 1)) - 1; } template From dbe0af3f1aa6660745d3764e12f475cf46b937a5 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 4 Oct 2023 08:38:35 -0700 Subject: [PATCH 18/50] chore: typos and code style --- .../gbt_classification_model_builder.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp index e830520cb54..6476e20e1e4 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp @@ -68,7 +68,7 @@ services::Status ModelBuilder::initialize(size_t nFeatures, size_t nIterations, return s; } -services::Status ModelBuilder::createTreeInternal(size_t nNodes, size_t clasLabel, TreeId & resId) +services::Status ModelBuilder::createTreeInternal(size_t nNodes, size_t classLabel, TreeId & resId) { gbt::classification::internal::ModelImpl & modelImplRef = daal::algorithms::dtrees::internal::getModelRef(_model); @@ -83,19 +83,22 @@ services::Status ModelBuilder::createTreeInternal(size_t nNodes, size_t clasLabe { return Status(ErrorID::ErrorIncorrectParameter); } - if (clasLabel > (_nClasses - 1)) + if (classLabel > (_nClasses - 1)) { return Status(ErrorID::ErrorIncorrectParameter); } - TreeId treeId = clasLabel * _nIterations; + TreeId treeId = classLabel * _nIterations; const SerializationIface * isEmptyTreeTable = (*(modelImplRef._serializationData))[treeId].get(); - const size_t nTrees = (clasLabel + 1) * _nIterations; + const size_t nTrees = (classLabel + 1) * _nIterations; while (isEmptyTreeTable && treeId < nTrees) { treeId++; isEmptyTreeTable = (*(modelImplRef._serializationData))[treeId].get(); } - if (treeId == nTrees) return Status(ErrorID::ErrorIncorrectParameter); + if (treeId == nTrees) + { + return Status(ErrorID::ErrorIncorrectParameter); + } services::SharedPtr treeTablePtr( new DecisionTreeTable(nNodes)); //DecisionTreeTable* const treeTable = new DecisionTreeTable(nNodes); From e63583135ff62956ca4b5373e967006ba2beb158 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 5 Oct 2023 03:22:01 -0700 Subject: [PATCH 19/50] Fix bazel build --- .../gbt/gbt_predict_dense_default_impl.i | 1 - cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 21 ++++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i index 6996f3e9f10..fd76f8da721 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i @@ -47,7 +47,6 @@ namespace internal { using gbt::internal::ModelFPType; using gbt::internal::FeatureIndexType; -// typedef gbt::internal::FeatureIndexType FeatureIndexType; template struct PredictDispatcher diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 087cdd87308..04540bf1f70 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -37,7 +37,7 @@ #include "src/algorithms/dtrees/dtrees_feature_type_helper.h" #include "src/algorithms/dtrees/gbt/gbt_model_impl.h" #include "src/services/service_arrays.h" -#include "stdint.h" +#include "src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i" namespace daal { @@ -48,6 +48,7 @@ namespace gbt namespace treeshap { using gbt::internal::FeatureIndexType; +using gbt::internal::ModelFPType; using FeatureTypes = algorithms::dtrees::internal::FeatureTypes; /** @@ -101,10 +102,10 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith // stop if we have no weight coming down to us if (conditionFraction < FLT_EPSILON) return; - const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; - const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; - const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; - const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; + const ModelFPType * const splitValues = tree->getSplitPoints() - 1; + const FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; + const ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; + const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; PathElement * uniquePath = parentUniquePath + uniqueDepth + 1; const size_t nBytes = (uniqueDepth + 1) * sizeof(PathElement); @@ -250,11 +251,11 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith // stop if we have no weight coming down to us if (conditionFraction < FLT_EPSILON) return; - const size_t numOutputs = 1; // currently only support single-output models - const gbt::prediction::internal::ModelFPType * const splitValues = tree->getSplitPoints() - 1; - const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; - const gbt::prediction::internal::FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; - const gbt::prediction::internal::ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; + const size_t numOutputs = 1; // currently only support single-output models + const ModelFPType * const splitValues = tree->getSplitPoints() - 1; + const int * const defaultLeft = tree->getDefaultLeftForSplit() - 1; + const FeatureIndexType * const fIndexes = tree->getFeatureIndexesForSplit() - 1; + const ModelFPType * const nodeCoverValues = tree->getNodeCoverValues() - 1; // extend the unique path PathElement * uniquePath = parentUniquePath + uniqueDepth + 1; From 060042c180ff4e356c9d93386bea8a068d5fcca4 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 6 Oct 2023 02:42:58 -0700 Subject: [PATCH 20/50] fix: remove dead member variable in GbtDecisionTree --- cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h | 11 ++++------- .../gbt_regression_train_dense_default_oneapi_impl.i | 5 ++--- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h index c2868272311..63f39e4110c 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h @@ -66,10 +66,9 @@ class GbtDecisionTree : public SerializationIface using FeatureIndexesForSplitType = HomogenNumericTable; using defaultLeftForSplitType = HomogenNumericTable; - GbtDecisionTree(const size_t nNodes, const size_t maxLvl, const size_t sourceNumOfNodes) + GbtDecisionTree(const size_t nNodes, const size_t maxLvl) : _nNodes(nNodes), _maxLvl(maxLvl), - _sourceNumOfNodes(sourceNumOfNodes), _splitPoints(SplitPointType::create(1, nNodes, NumericTableIface::doAllocate)), _featureIndexes(FeatureIndexesForSplitType::create(1, nNodes, NumericTableIface::doAllocate)), _nodeCoverValues(NodeCoverType::create(1, nNodes, NumericTableIface::doAllocate)), @@ -80,7 +79,7 @@ class GbtDecisionTree : public SerializationIface {} // for serialization only - GbtDecisionTree() : _nNodes(0), _maxLvl(0), _sourceNumOfNodes(0) {} + GbtDecisionTree() : _nNodes(0), _maxLvl(0) {} ModelFPType * getSplitPoints() { return _splitPoints->getArray(); } @@ -202,7 +201,6 @@ class GbtDecisionTree : public SerializationIface { arch->set(_nNodes); arch->set(_maxLvl); - arch->set(_sourceNumOfNodes); arch->setSharedPtrObj(_splitPoints); arch->setSharedPtrObj(_featureIndexes); @@ -215,7 +213,6 @@ class GbtDecisionTree : public SerializationIface protected: size_t _nNodes; FeatureIndexType _maxLvl; - size_t _sourceNumOfNodes; services::SharedPtr _splitPoints; services::SharedPtr _featureIndexes; services::SharedPtr _nodeCoverValues; @@ -243,7 +240,7 @@ class GbtTreeImpl : public dtrees::internal::TreeImpl getMaxLvl(*super::top(), nLvls, static_cast(-1)); const size_t nNodes = getNumberOfNodesByLvls(nLvls); - *pTbl = new GbtDecisionTree(nNodes, nLvls, super::top()->numChildren() + 1); + *pTbl = new GbtDecisionTree(nNodes, nLvls); *pTblImp = new HomogenNumericTable(1, nNodes, NumericTable::doAllocate); *pTblSmplCnt = new HomogenNumericTable(1, nNodes, NumericTable::doAllocate); @@ -351,7 +348,7 @@ class ModelImpl : protected dtrees::internal::ModelImpl getMaxLvl(arr, 0, nLvls, static_cast(-1)); const size_t nNodes = getNumberOfNodesByLvls(nLvls); - return new GbtDecisionTree(nNodes, nLvls, tree.getNumberOfRows()); + return new GbtDecisionTree(nNodes, nLvls); } template diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i index cec2e93595a..35f68ee6f58 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/oneapi/gbt_regression_train_dense_default_oneapi_impl.i @@ -1107,10 +1107,9 @@ services::Status RegressionTrainBatchKernelOneAPI::comp connector.getMaxLevel(0, maxLevel); DAAL_ASSERT(maxLevel + 1 <= 63); DAAL_ASSERT(((size_t)1 << (maxLevel + 1)) > 0 && ((size_t)1 << (maxLevel + 1)) < static_cast(UINT_MAX)); - const uint32_t nNodes = ((size_t)1 << (maxLevel + 1)) - 1; - const uint32_t nNodesPresent = connector.getNNodes(0); + const uint32_t nNodes = ((size_t)1 << (maxLevel + 1)) - 1; - gbt::internal::GbtDecisionTree * pTbl = new gbt::internal::GbtDecisionTree(nNodes, maxLevel, nNodesPresent); + gbt::internal::GbtDecisionTree * pTbl = new gbt::internal::GbtDecisionTree(nNodes, maxLevel); DAAL_CHECK_MALLOC(pTbl); HomogenNumericTable * pTblImp = new HomogenNumericTable(1, nNodes, NumericTable::doAllocate); From 13bb5529d27980e22547fdc80c06d0f21d2dac24 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 6 Oct 2023 05:48:54 -0700 Subject: [PATCH 21/50] feat: add first unit tests for model builders --- .../src/algorithms/dtrees/gbt/gbt_model.cpp | 40 ++++-- .../algorithms/dtrees/gbt/regression/BUILD | 24 +++- .../gbt_regression_model_builder_unit.cpp | 133 ++++++++++++++++++ 3 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp index 4626adc2e35..976f5f2e64f 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp @@ -225,19 +225,38 @@ void ModelImpl::destroy() super::destroy(); } -bool ModelImpl::nodeIsDummyLeaf(size_t idx, const GbtDecisionTree & gbtTree) +/** + * \brief Returns true if a node is a dummy leaf. A dummy leaf contains the same split feature & value as the parent + * + * \param nodeIndex 1-based index to the node array + * \param gbtTree tree containing nodes + * \param lvl current level in the tree + * \return true if the node is a dummy leaf, false otherwise + */ +bool ModelImpl::nodeIsDummyLeaf(size_t nodeIndex, const GbtDecisionTree & gbtTree) { + const size_t childArrayIndex = nodeIndex - 1; const ModelFPType * splitPoints = gbtTree.getSplitPoints(); const FeatureIndexType * splitFeatures = gbtTree.getFeatureIndexesForSplit(); - if (idx) + if (childArrayIndex) { - const size_t parent = getIdxOfParent(idx); - return splitPoints[parent] == splitPoints[idx] && splitFeatures[parent] == splitFeatures[idx]; + // check if child node has same split feature and split value as parent + const size_t parent = getIdxOfParent(nodeIndex); + const size_t parentArrayIndex = parent - 1; + return splitPoints[parentArrayIndex] == splitPoints[childArrayIndex] && splitFeatures[parentArrayIndex] == splitFeatures[childArrayIndex]; } return false; } +/** + * \brief Return true if a node is leaf + * + * \param idx 1-based index to the node array + * \param gbtTree tree containing nodes + * \param lvl current level in the tree + * \return true if the node is a leaf, false otherwise + */ bool ModelImpl::nodeIsLeaf(size_t idx, const GbtDecisionTree & gbtTree, const size_t lvl) { if (lvl == gbtTree.getMaxLvl()) @@ -251,9 +270,15 @@ bool ModelImpl::nodeIsLeaf(size_t idx, const GbtDecisionTree & gbtTree, const si return false; } -size_t ModelImpl::getIdxOfParent(const size_t sonIdx) +/** + * \brief Return the node index of the provided node's parent + * + * \param childIdx 1-based node index of the child + * \return size_t 1-based node index of the parent + */ +size_t ModelImpl::getIdxOfParent(const size_t childIdx) { - return sonIdx ? (sonIdx - 1) / 2 : 0; + return childIdx / 2; } void ModelImpl::decisionTreeToGbtTree(const DecisionTreeTable & tree, GbtDecisionTree & newTree) @@ -308,8 +333,7 @@ void ModelImpl::decisionTreeToGbtTree(const DecisionTreeTable & tree, GbtDecisio featureIndexes[idxInTable] = 0; nodeCoverValues[idxInTable] = p->cover; defaultLeft[idxInTable] = 0; - DAAL_ASSERT(featureIndexes[idxInTable] >= 0); - splitPoints[idxInTable] = p->featureValueOrResponse; + splitPoints[idxInTable] = p->featureValueOrResponse; } idxInTable++; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD index 7780e5acaa6..3d1dbd611f2 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD @@ -1,14 +1,30 @@ package(default_visibility = ["//visibility:public"]) -load("@onedal//dev/bazel:daal.bzl", "daal_module") +load("@onedal//dev/bazel:dal.bzl", + "dal_module", + "dal_test_suite", +) -daal_module( +dal_module( name = "kernel", auto = True, - opencl = True, - deps = [ + dal_deps = [ "@onedal//cpp/daal:core", "@onedal//cpp/daal:sycl", "@onedal//cpp/daal/src/algorithms/regression:kernel", "@onedal//cpp/daal/src/algorithms/dtrees/gbt:kernel", ], ) + +dal_test_suite( + name = "unit_tests", + framework = "catch2", + compile_as = [ "c++" ], + private = True, + srcs = glob([ + "test/*unit.cpp", + ]), + dal_deps = [ + "@onedal//cpp/daal/src/algorithms/regression:kernel", + "@onedal//cpp/daal/src/algorithms/dtrees/gbt:kernel", + ] +) \ No newline at end of file diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp new file mode 100644 index 00000000000..6c1d9bceb09 --- /dev/null +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp @@ -0,0 +1,133 @@ +#include "oneapi/dal/test/engine/common.hpp" +#include "src/algorithms/dtrees/gbt/gbt_model_impl.h" + +namespace daal::algorithms::gbt::internal +{ +GbtDecisionTree prepareThreeNodeTree() +{ + // create a tree with 3 nodes, 1 root (split), 2 leaves + // ROOT (level 1) + // / \ + // L L (level 2) + GbtDecisionTree tree = GbtDecisionTree(3, 2); + + ModelFPType * splitPoints = tree.getSplitPoints(); + FeatureIndexType * splitIndices = tree.getFeatureIndexesForSplit(); + int * defaultLeft = tree.getDefaultLeftForSplit(); + ModelFPType * coverValues = tree.getNodeCoverValues(); + + splitPoints[0] = 1; + splitIndices[0] = 0; + defaultLeft[0] = 1; + coverValues[0] = 1; + + splitPoints[1] = 10; + splitIndices[1] = 0; + defaultLeft[1] = 0; + coverValues[1] = 0.5; + + splitPoints[2] = 11; + splitIndices[2] = 0; + defaultLeft[2] = 0; + coverValues[2] = 0.5; + + return tree; +} + +GbtDecisionTree prepareFiveNodeTree() +{ + // create a tree with 5 nodes + // ROOT (1) (level 1) + // / \ + // L (2) S (3) (level 2) + // / \ + // L (6) L (7) (level 3) + // (note: on level 3, nodes 4 and 5 do not exist and will be created as "dummy leaf") + GbtDecisionTree tree = GbtDecisionTree(5, 3); + + ModelFPType * splitPoints = tree.getSplitPoints(); + FeatureIndexType * splitIndices = tree.getFeatureIndexesForSplit(); + int * defaultLeft = tree.getDefaultLeftForSplit(); + ModelFPType * coverValues = tree.getNodeCoverValues(); + + // node idx 1 + splitPoints[0] = 1; + splitIndices[0] = 0; + defaultLeft[0] = 1; + coverValues[0] = 10; + + // node idx 2 + // the node with dummy leaf children + splitPoints[1] = 10; + splitIndices[1] = 20; + defaultLeft[1] = 0; + coverValues[1] = 4; + + // node idx 3 + splitPoints[2] = 11; + splitIndices[2] = 0; + defaultLeft[2] = 0; + coverValues[2] = 6; + + // node idx 4 (dummy leaf) + // split point and value equal to parent node + splitPoints[3] = splitPoints[1]; + splitIndices[3] = splitIndices[1]; + defaultLeft[3] = 0; + coverValues[3] = 0; + + // node idx 5 (dummy leaf) + // split point and value equal to parent node + splitPoints[4] = splitPoints[1]; + splitIndices[4] = splitIndices[1]; + defaultLeft[4] = 0; + coverValues[4] = 0; + + // node idx 6 + splitPoints[5] = 12; + splitIndices[5] = 22; + defaultLeft[5] = 0; + coverValues[5] = 4; + + // node idx 7 + splitPoints[6] = 13; + splitIndices[6] = 23; + defaultLeft[6] = 0; + coverValues[6] = 2; + + return tree; +} + +TEST("nodeIsLeafThreeNodes", "[unit]") +{ + GbtDecisionTree tree = prepareThreeNodeTree(); + + REQUIRE(!ModelImpl::nodeIsLeaf(1, tree, 1)); + REQUIRE(ModelImpl::nodeIsLeaf(2, tree, 2)); + REQUIRE(ModelImpl::nodeIsLeaf(3, tree, 2)); +} + +TEST("nodeIsDummyLeafFiveNodes", "[unit]") +{ + GbtDecisionTree tree = prepareFiveNodeTree(); + + REQUIRE(!ModelImpl::nodeIsDummyLeaf(1, tree)); + REQUIRE(!ModelImpl::nodeIsDummyLeaf(2, tree)); + REQUIRE(!ModelImpl::nodeIsDummyLeaf(3, tree)); + REQUIRE(ModelImpl::nodeIsDummyLeaf(4, tree)); + REQUIRE(ModelImpl::nodeIsDummyLeaf(5, tree)); + REQUIRE(!ModelImpl::nodeIsDummyLeaf(6, tree)); + REQUIRE(!ModelImpl::nodeIsDummyLeaf(7, tree)); +} + +TEST("nodeIsLeafFiveNodes", "[unit]") +{ + GbtDecisionTree tree = prepareFiveNodeTree(); + + REQUIRE(!ModelImpl::nodeIsLeaf(1, tree, 1)); + REQUIRE(ModelImpl::nodeIsLeaf(2, tree, 2)); + REQUIRE(!ModelImpl::nodeIsLeaf(3, tree, 2)); + REQUIRE(ModelImpl::nodeIsLeaf(6, tree, 3)); + REQUIRE(ModelImpl::nodeIsLeaf(7, tree, 3)); +} +} // namespace daal::algorithms::gbt::internal From 4438cb679d69c1cc5140d712e1c7a48ebe8cd10e Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 6 Oct 2023 05:54:20 -0700 Subject: [PATCH 22/50] revert dal_module back to daal_module --- cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD index 3d1dbd611f2..d22e201bf0d 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD @@ -1,13 +1,11 @@ package(default_visibility = ["//visibility:public"]) -load("@onedal//dev/bazel:dal.bzl", - "dal_module", - "dal_test_suite", -) +load("@onedal//dev/bazel:daal.bzl", "daal_module") +load("@onedal//dev/bazel:dal.bzl", "dal_test_suite") -dal_module( +daal_module( name = "kernel", auto = True, - dal_deps = [ + deps = [ "@onedal//cpp/daal:core", "@onedal//cpp/daal:sycl", "@onedal//cpp/daal/src/algorithms/regression:kernel", From 65fcdc8876775d405427c10617293c61aa076522 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 6 Oct 2023 06:20:31 -0700 Subject: [PATCH 23/50] feat: execute dal unit tests in CI --- .ci/pipeline/ci.yml | 4 ++++ cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD | 3 ++- .../gbt/regression/test/gbt_regression_model_builder_unit.cpp | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.ci/pipeline/ci.yml b/.ci/pipeline/ci.yml index 811a046f10b..8f640fb1c4b 100755 --- a/.ci/pipeline/ci.yml +++ b/.ci/pipeline/ci.yml @@ -234,6 +234,10 @@ jobs: --test_link_mode=dev \ --test_thread_mode=par displayName: 'cpp-examples-thread-dev' + - script: | + bazel test bazel test //cpp/daal/src/algorithms/...:all \ + --test_tag_filters=unittest + displayName: 'daal-algorithms-unit-tests' - script: | export DALROOT=`pwd`/bazel-bin/release/daal/latest diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD index d22e201bf0d..8a81bd83e54 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD @@ -15,6 +15,7 @@ daal_module( dal_test_suite( name = "unit_tests", + tags = ["unittest"], framework = "catch2", compile_as = [ "c++" ], private = True, @@ -25,4 +26,4 @@ dal_test_suite( "@onedal//cpp/daal/src/algorithms/regression:kernel", "@onedal//cpp/daal/src/algorithms/dtrees/gbt:kernel", ] -) \ No newline at end of file +) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp index 6c1d9bceb09..21763f30636 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp @@ -37,7 +37,7 @@ GbtDecisionTree prepareThreeNodeTree() GbtDecisionTree prepareFiveNodeTree() { // create a tree with 5 nodes - // ROOT (1) (level 1) + // ROOT (1) (level 1) // / \ // L (2) S (3) (level 2) // / \ From f8f648c74bef6cf87df1844d693906f8bbf6af7b Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 6 Oct 2023 06:43:34 -0700 Subject: [PATCH 24/50] reorganize how tests are executed --- .ci/pipeline/ci.yml | 8 ++-- cpp/daal/BUILD | 37 +++++++++++++++---- cpp/daal/src/algorithms/dtrees/gbt/BUILD | 3 ++ .../algorithms/dtrees/gbt/regression/BUILD | 3 +- 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/.ci/pipeline/ci.yml b/.ci/pipeline/ci.yml index 8f640fb1c4b..5b1412635fc 100755 --- a/.ci/pipeline/ci.yml +++ b/.ci/pipeline/ci.yml @@ -234,10 +234,6 @@ jobs: --test_link_mode=dev \ --test_thread_mode=par displayName: 'cpp-examples-thread-dev' - - script: | - bazel test bazel test //cpp/daal/src/algorithms/...:all \ - --test_tag_filters=unittest - displayName: 'daal-algorithms-unit-tests' - script: | export DALROOT=`pwd`/bazel-bin/release/daal/latest @@ -253,6 +249,10 @@ jobs: --test_thread_mode=par displayName: 'cpp-examples-thread-release-dynamic' + - script: | + bazel test //cpp/daal:tests + displayName: 'daal-tests-algorithms' + - script: | bazel test //cpp/oneapi/dal:tests \ --config=host \ diff --git a/cpp/daal/BUILD b/cpp/daal/BUILD index 5e7f640c782..8e756accb1b 100644 --- a/cpp/daal/BUILD +++ b/cpp/daal/BUILD @@ -1,4 +1,8 @@ package(default_visibility = ["//visibility:public"]) +load("@onedal//dev/bazel:dal.bzl", + "dal_test_suite", + "dal_collect_test_suites", +) load("@onedal//dev/bazel:daal.bzl", "daal_module", "daal_static_lib", @@ -28,7 +32,7 @@ daal_module( deps = select({ "@config//:backend_ref": [ "@openblas//:openblas", ], - "//conditions:default": [ "@micromkl//:mkl_thr", + "//conditions:default": [ "@micromkl//:mkl_thr", ], }), ) @@ -54,7 +58,7 @@ daal_module( "DAAL_HIDE_DEPRECATED", ], deps = select({ - "@config//:backend_ref": [ + "@config//:backend_ref": [ ":public_includes", "@openblas//:headers", ], @@ -123,11 +127,11 @@ daal_module( hdrs = glob(["src/sycl/**/*.h", "src/sycl/**/*.cl"]), srcs = glob(["src/sycl/**/*.cpp"]), deps = select({ - "@config//:backend_ref": [ + "@config//:backend_ref": [ ":services", "@onedal//cpp/daal/src/algorithms/engines:kernel", ], - "//conditions:default": [ + "//conditions:default": [ ":services", "@onedal//cpp/daal/src/algorithms/engines:kernel", "@micromkl_dpc//:headers", @@ -146,13 +150,13 @@ daal_module( "TBB_USE_ASSERT=0", ], deps = select({ - "@config//:backend_ref": [ + "@config//:backend_ref": [ ":threading_headers", ":mathbackend_thread", "@tbb//:tbb", "@tbb//:tbbmalloc", ], - "//conditions:default": [ + "//conditions:default": [ ":threading_headers", ":mathbackend_thread", "@tbb//:tbb", @@ -269,7 +273,7 @@ daal_dynamic_lib( ], def_file = select({ "@config//:backend_ref": "src/threading/export_lnx32e.ref.def", - "//conditions:default": "src/threading/export_lnx32e.mkl.def", + "//conditions:default": "src/threading/export_lnx32e.mkl.def", }), ) @@ -316,3 +320,22 @@ filegroup( ":thread_static", ], ) + +dal_test_suite( + name = "unit_tests", + framework = "catch2", + srcs = glob([ + "test/*.cpp", + ]), +) + +dal_collect_test_suites( + name = "tests", + root = "@onedal//cpp/daal/src/algorithms", + modules = [ + "dtrees/gbt/regression" + ], + tests = [ + ":unit_tests", + ], +) \ No newline at end of file diff --git a/cpp/daal/src/algorithms/dtrees/gbt/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/BUILD index 712d778e8ae..9d49b0fbf21 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/BUILD @@ -1,4 +1,7 @@ package(default_visibility = ["//visibility:public"]) +load("@onedal//dev/bazel:dal.bzl", + "dal_collect_test_suites", +) load("@onedal//dev/bazel:daal.bzl", "daal_module") daal_module( diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD index 8a81bd83e54..ab76f1bb4b3 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD @@ -14,8 +14,7 @@ daal_module( ) dal_test_suite( - name = "unit_tests", - tags = ["unittest"], + name = "tests", framework = "catch2", compile_as = [ "c++" ], private = True, From 65e7806402f66e0b97b9a395df30cb512a88e4dc Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 6 Oct 2023 07:23:06 -0700 Subject: [PATCH 25/50] add license --- cpp/daal/src/algorithms/dtrees/gbt/BUILD | 8 ++++++++ .../test/gbt_regression_model_builder_unit.cpp | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/BUILD index 9d49b0fbf21..5c25549bdb5 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/BUILD @@ -14,3 +14,11 @@ daal_module( "@onedal//cpp/daal/src/algorithms/dtrees:kernel", ], ) + +dal_collect_test_suites( + name = "tests", + root = "@onedal//cpp/oneapi/dal/algo", + modules = [ + "regression" + ], +) \ No newline at end of file diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp index 21763f30636..a9463661891 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp @@ -1,3 +1,19 @@ +/******************************************************************************* +* Copyright 2023 Intel Corporation +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*******************************************************************************/ + #include "oneapi/dal/test/engine/common.hpp" #include "src/algorithms/dtrees/gbt/gbt_model_impl.h" From e418ec4d99191ed207d91930f4d32f3938a442c5 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 9 Oct 2023 06:17:58 -0700 Subject: [PATCH 26/50] Fix new_ts: nodeIsLeaf/nodeIsDummyLeaf internal usage & classification Parameter --- .../gbt_classification_predict_types.h | 15 ++++++-- .../src/algorithms/dtrees/gbt/gbt_model.cpp | 22 ------------ .../algorithms/dtrees/gbt/gbt_model_impl.h | 35 ++++++++++++++++--- 3 files changed, 42 insertions(+), 30 deletions(-) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h index ec6fe54ca9d..8ba92b4ee90 100755 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h @@ -70,9 +70,18 @@ namespace interface2 /* [Parameter source code] */ struct DAAL_EXPORT Parameter : public daal::algorithms::classifier::Parameter { - Parameter(size_t nClasses = 2) : daal::algorithms::classifier::Parameter(nClasses), nIterations(0) {} - Parameter(const Parameter & o) : daal::algorithms::classifier::Parameter(o), nIterations(o.nIterations) {} - size_t nIterations; /*!< Number of iterations of the trained model to be used for prediction */ + Parameter(size_t nClasses = 2) + : daal::algorithms::classifier::Parameter(nClasses), nIterations(0), predShapContributions(false), predShapInteractions(false) + {} + Parameter(const Parameter & o) + : daal::algorithms::classifier::Parameter(o), + nIterations(o.nIterations), + predShapContributions(o.predShapContributions), + predShapInteractions(o.predShapInteractions) + {} + size_t nIterations; /*!< Number of iterations of the trained model to be used for prediction */ + bool predShapContributions; /*!< Predict SHAP contributions */ + bool predShapInteractions; /*!< Predict SHAP interactions */ }; /* [Parameter source code] */ } // namespace interface2 diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp index 976f5f2e64f..f7f097361d9 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp @@ -225,14 +225,6 @@ void ModelImpl::destroy() super::destroy(); } -/** - * \brief Returns true if a node is a dummy leaf. A dummy leaf contains the same split feature & value as the parent - * - * \param nodeIndex 1-based index to the node array - * \param gbtTree tree containing nodes - * \param lvl current level in the tree - * \return true if the node is a dummy leaf, false otherwise - */ bool ModelImpl::nodeIsDummyLeaf(size_t nodeIndex, const GbtDecisionTree & gbtTree) { const size_t childArrayIndex = nodeIndex - 1; @@ -249,14 +241,6 @@ bool ModelImpl::nodeIsDummyLeaf(size_t nodeIndex, const GbtDecisionTree & gbtTre return false; } -/** - * \brief Return true if a node is leaf - * - * \param idx 1-based index to the node array - * \param gbtTree tree containing nodes - * \param lvl current level in the tree - * \return true if the node is a leaf, false otherwise - */ bool ModelImpl::nodeIsLeaf(size_t idx, const GbtDecisionTree & gbtTree, const size_t lvl) { if (lvl == gbtTree.getMaxLvl()) @@ -270,12 +254,6 @@ bool ModelImpl::nodeIsLeaf(size_t idx, const GbtDecisionTree & gbtTree, const si return false; } -/** - * \brief Return the node index of the provided node's parent - * - * \param childIdx 1-based node index of the child - * \return size_t 1-based node index of the parent - */ size_t ModelImpl::getIdxOfParent(const size_t childIdx) { return childIdx / 2; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h index 63f39e4110c..5ae2a8504a0 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model_impl.h @@ -333,10 +333,33 @@ class ModelImpl : protected dtrees::internal::ModelImpl static services::Status treeToTable(TreeType & t, gbt::internal::GbtDecisionTree ** pTbl, HomogenNumericTable ** pTblImp, HomogenNumericTable ** pTblSmplCnt, size_t nFeature); + /** + * \brief Returns true if a node is a dummy leaf. A dummy leaf contains the same split feature & value as the parent + * + * \param nodeIndex 1-based index to the node array + * \param gbtTree tree containing nodes + * \param lvl current level in the tree + * \return true if the node is a dummy leaf, false otherwise + */ static bool nodeIsDummyLeaf(size_t idx, const GbtDecisionTree & gbtTree); + + /** + * \brief Return true if a node is leaf + * + * \param idx 1-based index to the node array + * \param gbtTree tree containing nodes + * \param lvl current level in the tree + * \return true if the node is a leaf, false otherwise + */ static bool nodeIsLeaf(size_t idx, const GbtDecisionTree & gbtTree, const size_t lvl); protected: + /** + * \brief Return the node index of the provided node's parent + * + * \param childIdx 1-based node index of the child + * \return size_t 1-based node index of the parent + */ static size_t getIdxOfParent(const size_t sonIdx); static void getMaxLvl(const dtrees::internal::DecisionTreeNode * const arr, const size_t idx, size_t & maxLvl, size_t curLvl = 0); @@ -355,14 +378,15 @@ class ModelImpl : protected dtrees::internal::ModelImpl static void traverseGbtDF(size_t level, size_t iRowInTable, const GbtDecisionTree & gbtTree, OnSplitFunctor & visitSplit, OnLeafFunctor & visitLeaf) { - if (!nodeIsLeaf(iRowInTable, gbtTree, level)) + const size_t oneBasedNodeIndex = iRowInTable + 1; + if (!nodeIsLeaf(oneBasedNodeIndex, gbtTree, level)) { if (!visitSplit(iRowInTable, level)) return; //do not continue traversing traverseGbtDF(level + 1, iRowInTable * 2 + 1, gbtTree, visitSplit, visitLeaf); traverseGbtDF(level + 1, iRowInTable * 2 + 2, gbtTree, visitSplit, visitLeaf); } - else if (!nodeIsDummyLeaf(iRowInTable, gbtTree)) + else if (!nodeIsDummyLeaf(oneBasedNodeIndex, gbtTree)) { if (!visitLeaf(iRowInTable, level)) return; //do not continue traversing } @@ -376,14 +400,15 @@ class ModelImpl : protected dtrees::internal::ModelImpl { for (size_t j = 0; j < (level ? 2 : 1); ++j) { - size_t iRowInTable = aCur[i] + j; - if (!nodeIsLeaf(iRowInTable, gbtTree, level)) + const size_t iRowInTable = aCur[i] + j; + const size_t oneBasedNodeIndex = iRowInTable + 1; + if (!nodeIsLeaf(oneBasedNodeIndex, gbtTree, level)) { if (!visitSplit(iRowInTable, level)) return; //do not continue traversing aNext.push_back(iRowInTable * 2 + 1); } - else if (!nodeIsDummyLeaf(iRowInTable, gbtTree)) + else if (!nodeIsDummyLeaf(oneBasedNodeIndex, gbtTree)) { if (!visitLeaf(iRowInTable, level)) return; //do not continue traversing } From bc04a6822c33d978db738e080cf143a19eb1a168 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 9 Oct 2023 10:29:29 -0700 Subject: [PATCH 27/50] Update TreeVisitor with node cover value --- cpp/daal/include/algorithms/tree_utils/tree_utils.h | 1 + .../algorithms/tree_utils/tree_utils_classification.h | 5 +++-- .../include/algorithms/tree_utils/tree_utils_regression.h | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/cpp/daal/include/algorithms/tree_utils/tree_utils.h b/cpp/daal/include/algorithms/tree_utils/tree_utils.h index c1e98513b5b..96db91cc0f6 100644 --- a/cpp/daal/include/algorithms/tree_utils/tree_utils.h +++ b/cpp/daal/include/algorithms/tree_utils/tree_utils.h @@ -59,6 +59,7 @@ struct DAAL_EXPORT SplitNodeDescriptor : public NodeDescriptor { size_t featureIndex; /*!< Feature used for splitting the node */ double featureValue; /*!< Threshold value at the node */ + double coverValue; /*!< Cover (sum_hess) for the node */ }; /** diff --git a/cpp/daal/include/algorithms/tree_utils/tree_utils_classification.h b/cpp/daal/include/algorithms/tree_utils/tree_utils_classification.h index a776a5412d9..f8d13c56a9f 100644 --- a/cpp/daal/include/algorithms/tree_utils/tree_utils_classification.h +++ b/cpp/daal/include/algorithms/tree_utils/tree_utils_classification.h @@ -50,8 +50,9 @@ namespace interface2 */ struct DAAL_EXPORT LeafNodeDescriptor : public NodeDescriptor { - size_t label; /*!< Label to be predicted when reaching the leaf */ - const double * prob; /*!< Probabilities estimation for the leaf */ + size_t label; /*!< Label to be predicted when reaching the leaf */ + const double * prob; /*!< Probabilities estimation for the leaf */ + const double * cover; /*!< Cover (sum_hess) for the leaf */ }; typedef daal::algorithms::tree_utils::TreeNodeVisitor TreeNodeVisitor; diff --git a/cpp/daal/include/algorithms/tree_utils/tree_utils_regression.h b/cpp/daal/include/algorithms/tree_utils/tree_utils_regression.h index 9890556ea12..8e587f984ad 100644 --- a/cpp/daal/include/algorithms/tree_utils/tree_utils_regression.h +++ b/cpp/daal/include/algorithms/tree_utils/tree_utils_regression.h @@ -50,7 +50,8 @@ namespace interface1 */ struct DAAL_EXPORT LeafNodeDescriptor : public NodeDescriptor { - double response; /*!< Value to be predicted when reaching the leaf */ + double response; /*!< Value to be predicted when reaching the leaf */ + double coverValue; /*!< Cover (sum_hess) for the leaf */ }; typedef daal::algorithms::tree_utils::TreeNodeVisitor TreeNodeVisitor; From 6473d69cd941f9d93fe1f84f8074e79b5c5b5f35 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Tue, 10 Oct 2023 08:10:45 -0700 Subject: [PATCH 28/50] remove deprecation version in comment --- .../decision_forest_classification_model_builder.h | 6 +++--- .../gbt_classification_model_builder.h | 4 ++-- .../gradient_boosted_trees/gbt_regression_model_builder.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h index cf1041c25db..46b5aaa804b 100644 --- a/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h +++ b/cpp/daal/include/algorithms/decision_forest/decision_forest_classification_model_builder.h @@ -119,7 +119,7 @@ class DAAL_EXPORT ModelBuilder } /** - * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + * \DAAL_DEPRECATED */ DAAL_DEPRECATED NodeId addLeafNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t classLabel) { @@ -144,7 +144,7 @@ class DAAL_EXPORT ModelBuilder } /** - * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + * \DAAL_DEPRECATED */ DAAL_DEPRECATED NodeId addLeafNodeByProba(const TreeId treeId, const NodeId parentId, const size_t position, const double * const proba) { @@ -172,7 +172,7 @@ class DAAL_EXPORT ModelBuilder } /** - * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + * \DAAL_DEPRECATED */ DAAL_DEPRECATED NodeId addSplitNode(const TreeId treeId, const NodeId parentId, const size_t position, const size_t featureIndex, const double featureValue) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h index e0f8b00f855..b8d1c5da47d 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model_builder.h @@ -121,7 +121,7 @@ class DAAL_EXPORT ModelBuilder } /** - * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + * \DAAL_DEPRECATED */ DAAL_DEPRECATED NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response) { @@ -148,7 +148,7 @@ class DAAL_EXPORT ModelBuilder } /** - * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + * \DAAL_DEPRECATED */ DAAL_DEPRECATED NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue) { diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h index 23dbe6846d4..c9343f03d8d 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model_builder.h @@ -119,7 +119,7 @@ class DAAL_EXPORT ModelBuilder } /** - * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + * \DAAL_DEPRECATED */ DAAL_DEPRECATED NodeId addLeafNode(TreeId treeId, NodeId parentId, size_t position, double response) { @@ -146,7 +146,7 @@ class DAAL_EXPORT ModelBuilder } /** - * \DAAL_DEPRECATED { will be removed in r2025.0.0 } + * \DAAL_DEPRECATED */ DAAL_DEPRECATED NodeId addSplitNode(TreeId treeId, NodeId parentId, size_t position, size_t featureIndex, double featureValue) { From 7bc51f74391673cc472f31bc1db457372c7d110b Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 11 Oct 2023 02:22:23 -0700 Subject: [PATCH 29/50] remove skipping of XGBoost base_score tree --- .../gbt_regression_predict_dense_default_batch_impl.i | 3 --- 1 file changed, 3 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index fd7e49bf77c..5f0c0699873 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -343,9 +343,6 @@ services::Status PredictRegressionTask::predictContributio algorithmFPType * phi = res + (iRow * nColumnsPhi); for (size_t currentTreeIndex = iTree; currentTreeIndex < iTree + nTrees; ++currentTreeIndex) { - // regression model builder tree 0 contains only the base_score and must be skipped - if (currentTreeIndex == 0) continue; - const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; st |= gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, condition, conditionFeature); From bc0209375c893ca12dd040785d1f04a03cce9fa4 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 12 Oct 2023 09:47:03 -0700 Subject: [PATCH 30/50] feature: proper support for XGBoost's base_score value --- .../gbt_classification_model.h | 18 +++++++++ .../gbt_regression_model.h | 18 +++++++++ ...ication_predict_dense_default_batch_impl.i | 25 +++++++----- .../gbt_classification_predict_kernel.h | 7 ++-- ...ression_predict_dense_default_batch_impl.i | 38 +++++++++++++++---- 5 files changed, 87 insertions(+), 19 deletions(-) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h index 69f348c2baf..5707340a8ad 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h @@ -123,8 +123,26 @@ class DAAL_EXPORT Model : public classifier::Model */ virtual size_t getNumberOfTrees() const = 0; + /** + * \brief Set the Prediction Bias term + * + * \param value global prediction bias + */ + void setPredictionBias(double value) { _predictionBias = value; } + + /** + * \brief Get the Prediction Bias term + * + * \return double prediction bias + */ + double getPredictionBias() const { return _predictionBias; } + protected: Model() : classifier::Model() {} + +private: + /* global bias applied to predictions*/ + double _predictionBias; }; /** @} */ typedef services::SharedPtr ModelPtr; diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h index 99607a6d16f..c52d732ef4e 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h @@ -123,8 +123,26 @@ class DAAL_EXPORT Model : public algorithms::regression::Model */ virtual size_t getNumberOfTrees() const = 0; + /** + * \brief Set the Prediction Bias term + * + * \param value global prediction bias + */ + void setPredictionBias(double value) { _predictionBias = value; } + + /** + * \brief Get the Prediction Bias term + * + * \return double prediction bias + */ + double getPredictionBias() const { return _predictionBias; } + protected: Model(); + +private: + /* global bias applied to predictions*/ + double _predictionBias; }; typedef services::SharedPtr ModelPtr; typedef services::SharedPtr ModelConstPtr; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i index 169a891c978..a733fce753b 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i @@ -66,6 +66,13 @@ public: PredictBinaryClassificationTask(const NumericTable * x, NumericTable * y, NumericTable * prob) : super(x, y), _prob(prob) {} services::Status run(const gbt::classification::internal::ModelImpl * m, size_t nIterations, services::HostAppIface * pHostApp) { + double bias = m->getPredictionBias(); + DAAL_ASSERT((bias >= 0) && (bias < 1)); + if (bias > 0) + { + // convert to margin + bias = -1.0f * daal::internal::MathInst::sLog(1.0f / bias - 1.0f); + } DAAL_ASSERT(!nIterations || nIterations <= m->size()); DAAL_CHECK_MALLOC(this->_featHelper.init(*this->_data)); const auto nTreesTotal = (nIterations ? nIterations : m->size()); @@ -75,7 +82,7 @@ public: const auto nRows = this->_data->getNumberOfRows(); services::Status s; DAAL_OVERFLOW_CHECK_BY_MULTIPLICATION(size_t, nRows, sizeof(algorithmFPType)); - //compute raw boosted values + // compute raw boosted values if (this->_res && _prob) { WriteOnlyRows resBD(this->_res, 0, nRows); @@ -88,7 +95,7 @@ public: TArray expValPtr(nRows); algorithmFPType * expVal = expValPtr.get(); DAAL_CHECK_MALLOC(expVal); - s = super::runInternal(pHostApp, this->_res, false, false); + s = super::runInternal(pHostApp, this->_res, bias, false, false); if (!s) return s; auto nBlocks = daal::threader_get_threads_number(); @@ -120,7 +127,7 @@ public: algorithmFPType * expVal = expValPtr.get(); NumericTablePtr expNT = HomogenNumericTableCPU::create(expVal, 1, nRows, &s); DAAL_CHECK_MALLOC(expVal); - s = super::runInternal(pHostApp, expNT.get(), false, false); + s = super::runInternal(pHostApp, expNT.get(), bias, false, false); if (!s) return s; auto nBlocks = daal::threader_get_threads_number(); @@ -143,12 +150,12 @@ public: DAAL_CHECK_BLOCK_STATUS(resBD); const algorithmFPType label[2] = { algorithmFPType(1.), algorithmFPType(0.) }; algorithmFPType * res = resBD.get(); - s = super::runInternal(pHostApp, this->_res, false, false); + s = super::runInternal(pHostApp, this->_res, bias, false, false); if (!s) return s; for (size_t iRow = 0; iRow < nRows; ++iRow) { - //probablity is a sigmoid(f) hence sign(f) can be checked + // probability is a sigmoid(f) hence sign(f) can be checked res[iRow] = label[services::internal::SignBit::get(res[iRow])]; } } @@ -175,7 +182,7 @@ public: services::Status run(const gbt::classification::internal::ModelImpl * m, size_t nClasses, size_t nIterations, services::HostAppIface * pHostApp); protected: - services::Status predictByAllTrees(size_t nTreesTotal, size_t nClasses, const DimType & dim); + services::Status predictByAllTrees(size_t nTreesTotal, size_t nClasses, double bias, const DimType & dim); template using dispatcher_t = gbt::prediction::internal::PredictDispatcher; @@ -398,7 +405,7 @@ services::Status PredictMulticlassTask::run(const gbt::cla DimType dim(*_data, nTreesTotal, getNumberOfNodes(nTreesTotal)); - return predictByAllTrees(nTreesTotal, nClasses, dim); + return predictByAllTrees(nTreesTotal, nClasses, m->getPredictionBias(), dim); } template @@ -433,7 +440,7 @@ void PredictMulticlassTask::predictByTreesVector(algorithm } template -services::Status PredictMulticlassTask::predictByAllTrees(size_t nTreesTotal, size_t nClasses, const DimType & dim) +services::Status PredictMulticlassTask::predictByAllTrees(size_t nTreesTotal, size_t nClasses, double bias, const DimType & dim) { WriteOnlyRows resBD(_res, 0, dim.nRowsTotal); DAAL_CHECK_BLOCK_STATUS(resBD); @@ -449,7 +456,7 @@ services::Status PredictMulticlassTask::predictByAllTrees( DAAL_OVERFLOW_CHECK_BY_MULTIPLICATION(size_t, nRows * nClasses, sizeof(algorithmFPType)); TArray valPtr(nRows * nClasses); algorithmFPType * valFull = valPtr.get(); - services::internal::service_memset(valFull, algorithmFPType(0), nRows * nClasses); + services::internal::service_memset(valFull, algorithmFPType(bias), nRows * nClasses); daal::threader_for(dim.nDataBlocks, dim.nDataBlocks, [&](size_t iBlock) { const size_t iStartRow = iBlock * dim.nRowsInBlock; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_kernel.h b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_kernel.h index 1a32131f8b8..0efa3707206 100755 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_kernel.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_kernel.h @@ -51,9 +51,10 @@ class PredictKernel : public daal::algorithms::Kernel /** * \brief Compute gradient boosted trees prediction results. * - * \param a[in] Matrix of input variables X - * \param m[in] Gradient boosted trees model obtained on training stage - * \param r[out] Prediction results + * \param a[in] Matrix of input variables X + * \param m[in] Gradient boosted trees model obtained on training stage + * \param r[out] Prediction results + * \param prob[out] Prediction class probabilities * \param nClasses[in] Number of classes in gradient boosted trees algorithm parameter * \param nIterations[in] Number of iterations to predict in gradient boosted trees algorithm parameter */ diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 5f0c0699873..01b3a32ad50 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -72,7 +72,8 @@ protected: template using dispatcher_t = gbt::prediction::internal::PredictDispatcher; - services::Status runInternal(services::HostAppIface * pHostApp, NumericTable * result, bool predShapContributions, bool predShapInteractions); + services::Status runInternal(services::HostAppIface * pHostApp, NumericTable * result, double predictionBias, bool predShapContributions, + bool predShapInteractions); template algorithmFPType predictByTrees(size_t iFirstTree, size_t nTrees, const algorithmFPType * x, const dispatcher_t & dispatcher); @@ -310,7 +311,7 @@ services::Status PredictRegressionTask::run(const gbt::reg _aTree.reset(nTreesTotal); DAAL_CHECK_MALLOC(_aTree.get()); for (size_t i = 0; i < nTreesTotal; ++i) _aTree[i] = m->at(i); - return runInternal(pHostApp, this->_res, predShapContributions, predShapInteractions); + return runInternal(pHostApp, this->_res, m->getPredictionBias(), predShapContributions, predShapInteractions); } /** @@ -446,7 +447,8 @@ services::Status PredictRegressionTask::predictContributio template services::Status PredictRegressionTask::runInternal(services::HostAppIface * pHostApp, NumericTable * result, - bool predShapContributions, bool predShapInteractions) + double predictionBias, bool predShapContributions, + bool predShapInteractions) { // assert we're not requesting both contributions and interactions DAAL_ASSERT(!(predShapContributions && predShapInteractions)); @@ -481,12 +483,21 @@ services::Status PredictRegressionTask::runInternal(servic // thread-local write rows into global result buffer WriteOnlyRows resRow(result, iStartRow, nRowsToProcess); DAAL_CHECK_BLOCK_STATUS_THR(resRow); - + auto resRowPtr = resRow.get(); + if ((predictionBias * predictionBias) > (DBL_EPSILON * DBL_EPSILON)) + { + // memory is already initialized to 0 + // only set it to the bias term if it's != 0 + for (size_t i = 0; i < nRowsToProcess; ++i) + { + resRowPtr[i * resultNColumns + predictionIndex] = predictionBias; + } + } // bias term: prediction - sum_i phi_i (subtraction in predictContributions) - predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), dim, resultNColumns); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRowPtr, dim, resultNColumns); // TODO: support tree weights - safeStat |= predictContributions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRow.get(), 0, 0, dim); + safeStat |= predictContributions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRowPtr, 0, 0, dim); } else if (predShapInteractions) { @@ -495,12 +506,16 @@ services::Status PredictRegressionTask::runInternal(servic DAAL_CHECK_BLOCK_STATUS_THR(resRow); // nominal values are required to calculate the correct bias term - algorithmFPType * nominal = static_cast(daal_calloc(nRowsToProcess * sizeof(algorithmFPType))); + algorithmFPType * nominal = static_cast(daal_malloc(nRowsToProcess * sizeof(algorithmFPType))); if (!nominal) { safeStat.add(ErrorMemoryAllocationFailed); return; } + for (size_t i = 0; i < nRowsToProcess; ++i) + { + nominal[i] = predictionBias; + } predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim, 1); // TODO: support tree weights @@ -511,6 +526,15 @@ services::Status PredictRegressionTask::runInternal(servic else { algorithmFPType * res = resMatrix.get() + iStartRow; + if ((predictionBias * predictionBias) > (DBL_EPSILON * DBL_EPSILON)) + { + // memory is already initialized to 0 + // only set it to the bias term if it's != 0 + for (size_t i = 0; i < nRowsToProcess; ++i) + { + res[i] = predictionBias; + } + } predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), res, dim, 1); } }); From 30f4f28164d2072b154fc30df12ed176e3c65e99 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 13 Oct 2023 00:13:03 -0700 Subject: [PATCH 31/50] Update code attributions / cite / license --- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 04540bf1f70..f3d8412327c 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -18,9 +18,15 @@ /** * Original TreeSHAP algorithm by Scott Lundberg, 2018 * https://arxiv.org/abs/1802.03888 + * Originally contributed to XGBoost in + * - https://github.com/dmlc/xgboost/pull/2438 + * - https://github.com/dmlc/xgboost/pull/3043 + * XGBoost is licensed under Apache-2 (https://github.com/dmlc/xgboost/blob/master/LICENSE) * * Fast TreeSHAP algorithm v1 and v2 by Jilei Yang, 2021 * https://arxiv.org/abs/2109.09847. + * C code available at https://github.com/linkedin/FastTreeSHAP/blob/master/fasttreeshap/cext/_cext.cc + * Fast TreeSHAP is licensed under BSD-2 (https://github.com/linkedin/FastTreeSHAP/blob/master/LICENSE) */ /* From 1a3906f15573c0009099e99ffd5127ff3ce4e388 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 13 Oct 2023 01:57:04 -0700 Subject: [PATCH 32/50] typo --- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index f3d8412327c..3a7ce41e3a2 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -24,7 +24,7 @@ * XGBoost is licensed under Apache-2 (https://github.com/dmlc/xgboost/blob/master/LICENSE) * * Fast TreeSHAP algorithm v1 and v2 by Jilei Yang, 2021 - * https://arxiv.org/abs/2109.09847. + * https://arxiv.org/abs/2109.09847 * C code available at https://github.com/linkedin/FastTreeSHAP/blob/master/fasttreeshap/cext/_cext.cc * Fast TreeSHAP is licensed under BSD-2 (https://github.com/linkedin/FastTreeSHAP/blob/master/LICENSE) */ From 932dfa5093047c47e89fc2cf3bd0d209ef911eb4 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 16 Oct 2023 03:15:59 -0700 Subject: [PATCH 33/50] chore: remove resIncrement from GBT predict --- ...ression_predict_dense_default_batch_impl.i | 83 ++++++++++--------- 1 file changed, 43 insertions(+), 40 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 01b3a32ad50..efac26662a3 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -79,7 +79,7 @@ protected: const dispatcher_t & dispatcher); template void predictByTreesVector(size_t iFirstTree, size_t nTrees, const algorithmFPType * x, algorithmFPType * res, - const dispatcher_t & dispatcher, size_t resIncrement); + const dispatcher_t & dispatcher); inline size_t getNumberOfNodes(size_t nTrees) { @@ -114,50 +114,44 @@ protected: } template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - size_t resIncrement) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res) { size_t iRow; dispatcher_t dispatcher; for (iRow = 0; iRow + vectorBlockSize <= nRows; iRow += vectorBlockSize) { - predictByTreesVector(iTree, nTrees, x + iRow * nColumns, res + iRow, dispatcher, - resIncrement); + predictByTreesVector(iTree, nTrees, x + iRow * nColumns, res + iRow, dispatcher); } for (; iRow < nRows; ++iRow) { - // result goes into final columns of current row - const size_t lastColumn = (iRow + 1) * resIncrement - 1; - res[lastColumn] += predictByTrees(iTree, nTrees, x + iRow * nColumns, dispatcher); + res[iRow] += predictByTrees(iTree, nTrees, x + iRow * nColumns, dispatcher); } } template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - size_t resIncrement) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res) { if (_featHelper.hasUnorderedFeatures()) { - predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + predict(iTree, nTrees, nRows, nColumns, x, res); } else { - predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + predict(iTree, nTrees, nRows, nColumns, x, res); } } template - inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res, - size_t resIncrement) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * res) { const bool hasAnyMissing = checkForMissing(x, nTrees, nRows, nColumns); if (hasAnyMissing) { - predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + predict(iTree, nTrees, nRows, nColumns, x, res); } else { - predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + predict(iTree, nTrees, nRows, nColumns, x, res); } } @@ -240,42 +234,41 @@ protected: // Recursively checking template parameter until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor. template inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim, - BooleanConstant keepLooking, size_t resIncrement) + BooleanConstant keepLooking) { const size_t nColumns = dim.nCols; constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; if (dim.vectorBlockSizeFactor == vectorBlockSizeFactor) { - predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + predict(iTree, nTrees, nRows, nColumns, x, res); } else { predict(iTree, nTrees, nRows, x, res, dim, - BooleanConstant(), resIncrement); + BooleanConstant()); } } template inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim, - BooleanConstant keepLooking, size_t resIncrement) + BooleanConstant keepLooking) { const size_t nColumns = dim.nCols; constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; - predict(iTree, nTrees, nRows, nColumns, x, res, resIncrement); + predict(iTree, nTrees, nRows, nColumns, x, res); } - inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim, - size_t resIncrement) + inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim) { const size_t nColumns = dim.nCols; constexpr size_t maxVectorBlockSizeFactor = DimType::maxVectorBlockSizeFactor; if (maxVectorBlockSizeFactor > 1) { - predict(iTree, nTrees, nRows, x, res, dim, BooleanConstant(), resIncrement); + predict(iTree, nTrees, nRows, x, res, dim, BooleanConstant()); } else { - predict(iTree, nTrees, nRows, x, res, dim, BooleanConstant(), resIncrement); + predict(iTree, nTrees, nRows, x, res, dim, BooleanConstant()); } } @@ -480,21 +473,33 @@ services::Status PredictRegressionTask::runInternal(servic if (predShapContributions) { + // nominal values are required to calculate the correct bias term + algorithmFPType * nominal = static_cast(daal_malloc(nRowsToProcess * sizeof(algorithmFPType))); + if (!nominal) + { + safeStat.add(ErrorMemoryAllocationFailed); + return; + } + for (size_t i = 0; i < nRowsToProcess; ++i) + { + nominal[i] = predictionBias; + } + + // bias term: prediction - sum_i phi_i (subtraction in predictContributions) + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim); + // thread-local write rows into global result buffer WriteOnlyRows resRow(result, iStartRow, nRowsToProcess); DAAL_CHECK_BLOCK_STATUS_THR(resRow); + + // copy nominal predictions for bias term to correct spot in result array auto resRowPtr = resRow.get(); - if ((predictionBias * predictionBias) > (DBL_EPSILON * DBL_EPSILON)) + for (size_t i = 0; i < nRowsToProcess; ++i) { - // memory is already initialized to 0 - // only set it to the bias term if it's != 0 - for (size_t i = 0; i < nRowsToProcess; ++i) - { - resRowPtr[i * resultNColumns + predictionIndex] = predictionBias; - } + resRowPtr[i * resultNColumns + predictionIndex] = nominal[i]; } - // bias term: prediction - sum_i phi_i (subtraction in predictContributions) - predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRowPtr, dim, resultNColumns); + + daal_free(nominal); // TODO: support tree weights safeStat |= predictContributions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRowPtr, 0, 0, dim); @@ -516,7 +521,7 @@ services::Status PredictRegressionTask::runInternal(servic { nominal[i] = predictionBias; } - predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim, 1); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim); // TODO: support tree weights safeStat |= predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, resRow.get(), dim); @@ -535,7 +540,7 @@ services::Status PredictRegressionTask::runInternal(servic res[i] = predictionBias; } } - predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), res, dim, 1); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), res, dim); } }); @@ -560,8 +565,7 @@ template template void PredictRegressionTask::predictByTreesVector(size_t iFirstTree, size_t nTrees, const algorithmFPType * x, algorithmFPType * res, - const dispatcher_t & dispatcher, - size_t resIncrement) + const dispatcher_t & dispatcher) { algorithmFPType v[vectorBlockSize]; for (size_t iTree = iFirstTree, iLastTree = iFirstTree + nTrees; iTree < iLastTree; ++iTree) @@ -573,8 +577,7 @@ void PredictRegressionTask::predictByTreesVector(size_t iF PRAGMA_VECTOR_ALWAYS for (size_t row = 0; row < vectorBlockSize; ++row) { - const size_t lastColumn = (row + 1) * resIncrement - 1; - res[lastColumn] += v[row]; + res[row] += v[row]; } } } From 0d229ed4417d1a4e7fd4760cb50dc164cd453d67 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 18 Oct 2023 03:09:21 -0700 Subject: [PATCH 34/50] Document functions and separate declarations and implementations --- ...ication_predict_dense_default_batch_impl.i | 798 ++++++++++++------ 1 file changed, 563 insertions(+), 235 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i index a733fce753b..7f52051e8c0 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i @@ -56,118 +56,49 @@ namespace internal { ////////////////////////////////////////////////////////////////////////////////////////// -// PredictBinaryClassificationTask +// PredictBinaryClassificationTask - declaration ////////////////////////////////////////////////////////////////////////////////////////// template class PredictBinaryClassificationTask : public gbt::regression::prediction::internal::PredictRegressionTask { public: typedef gbt::regression::prediction::internal::PredictRegressionTask super; + +public: + /** + * \brief Construct a new Predict Binary Classification Task object + * + * \param x NumericTable observation data + * \param y NumericTable prediction data + * \param prob NumericTable probability data + */ PredictBinaryClassificationTask(const NumericTable * x, NumericTable * y, NumericTable * prob) : super(x, y), _prob(prob) {} - services::Status run(const gbt::classification::internal::ModelImpl * m, size_t nIterations, services::HostAppIface * pHostApp) - { - double bias = m->getPredictionBias(); - DAAL_ASSERT((bias >= 0) && (bias < 1)); - if (bias > 0) - { - // convert to margin - bias = -1.0f * daal::internal::MathInst::sLog(1.0f / bias - 1.0f); - } - DAAL_ASSERT(!nIterations || nIterations <= m->size()); - DAAL_CHECK_MALLOC(this->_featHelper.init(*this->_data)); - const auto nTreesTotal = (nIterations ? nIterations : m->size()); - this->_aTree.reset(nTreesTotal); - DAAL_CHECK_MALLOC(this->_aTree.get()); - for (size_t i = 0; i < nTreesTotal; ++i) this->_aTree[i] = m->at(i); - const auto nRows = this->_data->getNumberOfRows(); - services::Status s; - DAAL_OVERFLOW_CHECK_BY_MULTIPLICATION(size_t, nRows, sizeof(algorithmFPType)); - // compute raw boosted values - if (this->_res && _prob) - { - WriteOnlyRows resBD(this->_res, 0, nRows); - DAAL_CHECK_BLOCK_STATUS(resBD); - const algorithmFPType label[2] = { algorithmFPType(1.), algorithmFPType(0.) }; - algorithmFPType * res = resBD.get(); - WriteOnlyRows probBD(_prob, 0, nRows); - DAAL_CHECK_BLOCK_STATUS(probBD); - algorithmFPType * prob_pred = probBD.get(); - TArray expValPtr(nRows); - algorithmFPType * expVal = expValPtr.get(); - DAAL_CHECK_MALLOC(expVal); - s = super::runInternal(pHostApp, this->_res, bias, false, false); - if (!s) return s; - - auto nBlocks = daal::threader_get_threads_number(); - const size_t blockSize = nRows / nBlocks; - nBlocks += (nBlocks * blockSize != nRows); - - daal::threader_for(nBlocks, nBlocks, [&](const size_t iBlock) { - const size_t startRow = iBlock * blockSize; - const size_t finishRow = (((iBlock + 1) == nBlocks) ? nRows : (iBlock + 1) * blockSize); - daal::internal::MathInst::vExp(finishRow - startRow, res + startRow, expVal + startRow); - - PRAGMA_IVDEP - PRAGMA_VECTOR_ALWAYS - for (size_t iRow = startRow; iRow < finishRow; ++iRow) - { - res[iRow] = label[services::internal::SignBit::get(res[iRow])]; - prob_pred[2 * iRow + 1] = expVal[iRow] / (algorithmFPType(1.) + expVal[iRow]); - prob_pred[2 * iRow] = algorithmFPType(1.) - prob_pred[2 * iRow + 1]; - } - }); - } - else if ((!this->_res) && _prob) - { - WriteOnlyRows probBD(_prob, 0, nRows); - DAAL_CHECK_BLOCK_STATUS(probBD); - algorithmFPType * prob_pred = probBD.get(); - TArray expValPtr(nRows); - algorithmFPType * expVal = expValPtr.get(); - NumericTablePtr expNT = HomogenNumericTableCPU::create(expVal, 1, nRows, &s); - DAAL_CHECK_MALLOC(expVal); - s = super::runInternal(pHostApp, expNT.get(), bias, false, false); - if (!s) return s; - - auto nBlocks = daal::threader_get_threads_number(); - const size_t blockSize = nRows / nBlocks; - nBlocks += (nBlocks * blockSize != nRows); - daal::threader_for(nBlocks, nBlocks, [&](const size_t iBlock) { - const size_t startRow = iBlock * blockSize; - const size_t finishRow = (((iBlock + 1) == nBlocks) ? nRows : (iBlock + 1) * blockSize); - daal::internal::MathInst::vExp(finishRow - startRow, expVal + startRow, expVal + startRow); - for (size_t iRow = startRow; iRow < finishRow; ++iRow) - { - prob_pred[2 * iRow + 1] = expVal[iRow] / (algorithmFPType(1.) + expVal[iRow]); - prob_pred[2 * iRow] = algorithmFPType(1.) - prob_pred[2 * iRow + 1]; - } - }); - } - else if (this->_res && (!_prob)) - { - WriteOnlyRows resBD(this->_res, 0, nRows); - DAAL_CHECK_BLOCK_STATUS(resBD); - const algorithmFPType label[2] = { algorithmFPType(1.), algorithmFPType(0.) }; - algorithmFPType * res = resBD.get(); - s = super::runInternal(pHostApp, this->_res, bias, false, false); - if (!s) return s; - - for (size_t iRow = 0; iRow < nRows; ++iRow) - { - // probability is a sigmoid(f) hence sign(f) can be checked - res[iRow] = label[services::internal::SignBit::get(res[iRow])]; - } - } - return s; - } + /** + * \brief Run prediction for the given model + * + * \param m The model for which to run prediction + * \param nIterations Number of iterations + * \param pHostApp HostAppIntertface + * \return services::Status + */ + services::Status run(const gbt::classification::internal::ModelImpl * m, size_t nIterations, services::HostAppIface * pHostApp); + +protected: + /** + * \brief Convert the model bias to a margin, considering the softmax activation + * + * \param bias Bias in class score units + * \return algorithmFPType Bias in softmax offset units + */ + algorithmFPType getMarginFromModelBias(algorithmFPType bias) const; protected: NumericTable * _prob; }; ////////////////////////////////////////////////////////////////////////////////////////// -// PredictMulticlassTask +// PredictMulticlassTask - declaration ////////////////////////////////////////////////////////////////////////////////////////// template class PredictMulticlassTask @@ -178,200 +109,597 @@ public: typedef daal::tls ClassesRawBoostedTlsBase; typedef daal::TlsMem ClassesRawBoostedTls; + /** + * \brief Construct a new Predict Multiclass Task object + * + * \param x NumericTable observation data + * \param y NumericTable prediction data + * \param prob NumericTable probability data + */ PredictMulticlassTask(const NumericTable * x, NumericTable * y, NumericTable * prob) : _data(x), _res(y), _prob(prob) {} + + /** + * \brief Run prediction for the given model + * + * \param m The model for which to run prediction + * \param nClasses Number of data classes + * \param nIterations Number of iterations + * \param pHostApp HostAppIntertface + * \return services::Status + */ services::Status run(const gbt::classification::internal::ModelImpl * m, size_t nClasses, size_t nIterations, services::HostAppIface * pHostApp); protected: - services::Status predictByAllTrees(size_t nTreesTotal, size_t nClasses, double bias, const DimType & dim); - + /** Dispatcher type for template dispatching */ template using dispatcher_t = gbt::prediction::internal::PredictDispatcher; + + /** + * \brief Helper boolean constant to populate template dispatcher + * + * \param val A boolean value, known at compile time + */ + template + struct BooleanConstant + { + typedef BooleanConstant type; + }; + + /** + * \brief Run prediction for all trees + * + * \param nTreesTotal Total number of trees in model + * \param nClasses Number of data classes + * \param bias Global prediction bias (e.g. base_score in XGBoost) + * \param dim DimType helper + * \return services::Status + */ + services::Status predictByAllTrees(size_t nTreesTotal, size_t nClasses, double bias, const DimType & dim); + + /** + * \brief Make prediction for a number of trees + * + * \param hasUnorderedFeatures Data has unordered features yes/no + * \param hasAnyMissing Data has missing values yes/no + * \param val Output prediction + * \param iFirstTree Index of first ree + * \param nTrees Number of trees included in prediction + * \param nClasses Number of data classes + * \param x Input observation data + * \param dispatcher Template dispatcher helper + */ template void predictByTrees(algorithmFPType * val, size_t iFirstTree, size_t nTrees, size_t nClasses, const algorithmFPType * x, const dispatcher_t & dispatcher); + + /** + * \brief Make prediction for a number of trees leveraging vector instructions + * + * \param hasUnorderedFeatures Data has unordered features yes/no + * \param hasAnyMissing Data has missing values yes/no + * \param vectorBlockSize Vector instruction block size + * \param val Output prediction + * \param iFirstTree Index of first ree + * \param nTrees Number of trees included in prediction + * \param nClasses Number of data classes + * \param x Input observation data + * \param dispatcher Template dispatcher helper + */ template void predictByTreesVector(algorithmFPType * val, size_t iFirstTree, size_t nTrees, size_t nClasses, const algorithmFPType * x, const dispatcher_t & dispatcher); - template - struct BooleanConstant - { - typedef BooleanConstant type; - }; + /** + * \brief Assign a class index to the result + * + * \param res Pointer to result array + * \param val Value of current prediction + * \param iRow + * \param i + * \param nClasses Number of data classes + * \param dispatcher Template dispatcher helper + */ + inline void updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, BooleanConstant dispatcher); + + /** + * \brief + * + * \param res Pointer to result array + * \param val Value of current prediction + * \param iRow + * \param i + * \param nClasses Number of data classes + * \param dispatcher Template dispatcher helper + */ + inline void updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, BooleanConstant dispatcher); + + /** + * \brief + * + * \param buff Pointer to a buffer + * \param buf_shift + * \param buf_size + * \param dispatcher + * \return algorithmFPType* Pointer to the input buffer + */ + inline algorithmFPType * updateBuffer(algorithmFPType * buff, size_t buf_shift, size_t buf_size, BooleanConstant dispatcher); + + /** + * \brief + * + * \param buff + * \param buf_shift + * \param buf_size + * \param dispatcher + * \return algorithmFPType* + */ + inline algorithmFPType * updateBuffer(algorithmFPType * buff, size_t buf_shift, size_t buf_size, BooleanConstant dispatcher); + + /** + * \brief Get the total number of nodes in all trees for tree number [1, 2, ... nTrees] + * + * \param nTrees Number of trees that contribute to the sum + * \return size_t Number of nodes in all contributing trees + */ + inline size_t getNumberOfNodes(size_t nTrees); + + /** + * \brief Check for missing data + * + * \param x Input observation data + * \param nTrees Number of contributing trees + * \param nRows Number of rows in input observation data to be considered + * \param nColumns Number of columns in input observation data to be considered + * \return true If missing data is found + * \return false If no missing data is found + */ + inline bool checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns); + + /** + * \brief Traverse a number of trees to get prediction results + * + * \param hasUnorderedFeatures Data has unordered features yes/no + * \param hasAnyMissing Data has missing values yes/no + * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) + * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param vectorBlockSize Vector instruction block size + * \param nTrees Number of trees contributing to prediction + * \param nClasses Number of data classes + * \param nRows Number of rows in observation data for which prediction is run + * \param nColumns Number of columns in observation data + * \param x Input observation data + * \param buff A pre-allocated buffer for computations + * \param[out] res Output prediction result + */ + template + inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, + algorithmFPType * res); + + /** + * \brief Traverse a number of trees to get prediction results + * + * \param hasAnyMissing Data has missing values yes/no + * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) + * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param vectorBlockSize Vector instruction block size + * \param nTrees Number of trees contributing to prediction + * \param nClasses Number of data classes + * \param nRows Number of rows in observation data for which prediction is run + * \param nColumns Number of columns in observation data + * \param x Input observation data + * \param buff A pre-allocated buffer for computations + * \param[out] res Output prediction result + */ + template + inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, + algorithmFPType * res); + + /** + * \brief Traverse a number of trees to get prediction results + * + * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) + * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param vectorBlockSize Vector instruction block size + * \param nTrees Number of trees contributing to prediction + * \param nClasses Number of data classes + * \param nRows Number of rows in observation data for which prediction is run + * \param nColumns Number of columns in observation data + * \param x Input observation data + * \param buff A pre-allocated buffer for computations + * \param[out] res Output prediction result + */ + template + inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, + algorithmFPType * res); + + /** + * \brief Traverse a number of trees to get prediction results + * + * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) + * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param vectorBlockSizeFactor Vector instruction block size - recursively decremented until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor + * \param nTrees Number of trees contributing to prediction + * \param nClasses Number of data classes + * \param nRows Number of rows in observation data for which prediction is run + * \param nColumns Number of columns in observation data + * \param x Input observation data + * \param buff A pre-allocated buffer for computations + * \param[out] res Output prediction result + */ + template + inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, + algorithmFPType * res, const DimType & dim, BooleanConstant keepLooking); + + /** + * \brief Traverse a number of trees to get prediction results + * + * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) + * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param vectorBlockSizeFactor Vector instruction block size - recursively decremented until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor + * \param nTrees Number of trees contributing to prediction + * \param nClasses Number of data classes + * \param nRows Number of rows in observation data for which prediction is run + * \param nColumns Number of columns in observation data + * \param x Input observation data + * \param buff A pre-allocated buffer for computations + * \param[out] res Output prediction result + * \param dim DimType helper + * \param keepLooking + */ + template + inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, + algorithmFPType * res, const DimType & dim, BooleanConstant keepLooking); + + /** + * \brief Traverse a number of trees to get prediction results + * + * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) + * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param nTrees Number of trees contributing to prediction + * \param nClasses Number of data classes + * \param nRows Number of rows in observation data for which prediction is run + * \param nColumns Number of columns in observation data + * \param x Input observation data + * \param buff A pre-allocated buffer for computations + * \param[out] res Output prediction result + * \param dim DimType helper + */ + template + inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, + algorithmFPType * res, const DimType & dim); + + /** + * \brief Traverse a number of trees to get prediction results + * + * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param nTrees Number of trees contributing to prediction + * \param nClasses Number of data classes + * \param nRows Number of rows in observation data for which prediction is run + * \param nColumns Number of columns in observation data + * \param x Input observation data + * \param buff A pre-allocated buffer for computations + * \param[out] res Output prediction result + * \param dim DimType helper + */ + template + inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, + algorithmFPType * res, const DimType & dim); - inline void updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, BooleanConstant dispatcher) - { - res[iRow + i] = getMaxClass(val + i * nClasses, nClasses); - } + /** + * \brief Get index of element with maximum value / activation + * + * \param val Pointer to values + * \param nClasses Number of columns per value + * \return size_t The column index with the maximum value + */ + size_t getMaxClass(const algorithmFPType * val, size_t nClasses) const; - inline void updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, BooleanConstant dispatcher) - {} +protected: + const NumericTable * _data; + NumericTable * _res; + NumericTable * _prob; + dtrees::internal::FeatureTypes _featHelper; + TArray _aTree; +}; - inline algorithmFPType * updateBuffer(algorithmFPType * buff, size_t buf_shift, size_t buf_size, BooleanConstant dispatcher) - { - services::internal::service_memset_seq(buff, algorithmFPType(0), buf_size); - return buff; - } +////////////////////////////////////////////////////////////////////////////////////////// +// PredictBinaryClassificationTask - implementation +////////////////////////////////////////////////////////////////////////////////////////// +template +services::Status PredictBinaryClassificationTask::run(const gbt::classification::internal::ModelImpl * m, size_t nIterations, + services::HostAppIface * pHostApp) +{ + DAAL_ASSERT(!nIterations || nIterations <= m->size()); + DAAL_CHECK_MALLOC(this->_featHelper.init(*this->_data)); + const auto nTreesTotal = (nIterations ? nIterations : m->size()); + this->_aTree.reset(nTreesTotal); + DAAL_CHECK_MALLOC(this->_aTree.get()); + for (size_t i = 0; i < nTreesTotal; ++i) this->_aTree[i] = m->at(i); - inline algorithmFPType * updateBuffer(algorithmFPType * buff, size_t buf_shift, size_t buf_size, BooleanConstant dispatcher) - { - return buff + buf_shift; - } + const auto nRows = this->_data->getNumberOfRows(); + services::Status s; + DAAL_OVERFLOW_CHECK_BY_MULTIPLICATION(size_t, nRows, sizeof(algorithmFPType)); - inline size_t getNumberOfNodes(size_t nTrees) - { - size_t nNodesTotal = 0; - for (size_t iTree = 0; iTree < nTrees; ++iTree) - { - nNodesTotal += this->_aTree[iTree]->getNumberOfNodes(); - } - return nNodesTotal; - } + algorithmFPType margin = getMarginFromModelBias(m->getPredictionBias()); - inline bool checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns) + // compute raw boosted values + if (this->_res && _prob) { - size_t nLvlTotal = 0; - for (size_t iTree = 0; iTree < nTrees; ++iTree) - { - nLvlTotal += this->_aTree[iTree]->getMaxLvl(); - } - if (nLvlTotal <= nColumns) - { - // Checking is compicated. Better to do it during inferense. - return true; - } - else - { - for (size_t idx = 0; idx < nRows * nColumns; ++idx) + WriteOnlyRows resBD(this->_res, 0, nRows); + DAAL_CHECK_BLOCK_STATUS(resBD); + const algorithmFPType label[2] = { algorithmFPType(1.), algorithmFPType(0.) }; + algorithmFPType * res = resBD.get(); + WriteOnlyRows probBD(_prob, 0, nRows); + DAAL_CHECK_BLOCK_STATUS(probBD); + algorithmFPType * prob_pred = probBD.get(); + TArray expValPtr(nRows); + algorithmFPType * expVal = expValPtr.get(); + DAAL_CHECK_MALLOC(expVal); + s = super::runInternal(pHostApp, this->_res, margin, false, false); + if (!s) return s; + + auto nBlocks = daal::threader_get_threads_number(); + const size_t blockSize = nRows / nBlocks; + nBlocks += (nBlocks * blockSize != nRows); + + daal::threader_for(nBlocks, nBlocks, [&](const size_t iBlock) { + const size_t startRow = iBlock * blockSize; + const size_t finishRow = (((iBlock + 1) == nBlocks) ? nRows : (iBlock + 1) * blockSize); + daal::internal::MathInst::vExp(finishRow - startRow, res + startRow, expVal + startRow); + + PRAGMA_IVDEP + PRAGMA_VECTOR_ALWAYS + for (size_t iRow = startRow; iRow < finishRow; ++iRow) { - if (checkFinitenessByComparison(x[idx])) return true; + res[iRow] = label[services::internal::SignBit::get(res[iRow])]; + prob_pred[2 * iRow + 1] = expVal[iRow] / (algorithmFPType(1.) + expVal[iRow]); + prob_pred[2 * iRow] = algorithmFPType(1.) - prob_pred[2 * iRow + 1]; } - } - return false; + }); } - template - inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, - algorithmFPType * res) + else if ((!this->_res) && _prob) { - dispatcher_t dispatcher; - size_t iRow = 0; - for (; iRow + vectorBlockSize <= nRows; iRow += vectorBlockSize) - { - algorithmFPType * val = updateBuffer(buff, iRow * nClasses, nClasses * vectorBlockSize, BooleanConstant()); - predictByTreesVector(val, 0, nTrees, nClasses, x + iRow * nColumns, dispatcher); - for (size_t i = 0; i < vectorBlockSize; ++i) + WriteOnlyRows probBD(_prob, 0, nRows); + DAAL_CHECK_BLOCK_STATUS(probBD); + algorithmFPType * prob_pred = probBD.get(); + TArray expValPtr(nRows); + algorithmFPType * expVal = expValPtr.get(); + NumericTablePtr expNT = HomogenNumericTableCPU::create(expVal, 1, nRows, &s); + DAAL_CHECK_MALLOC(expVal); + s = super::runInternal(pHostApp, expNT.get(), margin, false, false); + if (!s) return s; + + auto nBlocks = daal::threader_get_threads_number(); + const size_t blockSize = nRows / nBlocks; + nBlocks += (nBlocks * blockSize != nRows); + daal::threader_for(nBlocks, nBlocks, [&](const size_t iBlock) { + const size_t startRow = iBlock * blockSize; + const size_t finishRow = (((iBlock + 1) == nBlocks) ? nRows : (iBlock + 1) * blockSize); + daal::internal::MathInst::vExp(finishRow - startRow, expVal + startRow, expVal + startRow); + for (size_t iRow = startRow; iRow < finishRow; ++iRow) { - updateResult(res, val, iRow, i, nClasses, BooleanConstant()); + prob_pred[2 * iRow + 1] = expVal[iRow] / (algorithmFPType(1.) + expVal[iRow]); + prob_pred[2 * iRow] = algorithmFPType(1.) - prob_pred[2 * iRow + 1]; } - } - for (; iRow < nRows; ++iRow) + }); + } + else if (this->_res && (!_prob)) + { + WriteOnlyRows resBD(this->_res, 0, nRows); + DAAL_CHECK_BLOCK_STATUS(resBD); + const algorithmFPType label[2] = { algorithmFPType(1.), algorithmFPType(0.) }; + algorithmFPType * res = resBD.get(); + s = super::runInternal(pHostApp, this->_res, margin, false, false); + if (!s) return s; + + typedef services::internal::SignBit SignBit; + + PRAGMA_IVDEP + for (size_t iRow = 0; iRow < nRows; ++iRow) { - algorithmFPType * val = updateBuffer(buff, iRow * nClasses, nClasses, BooleanConstant()); - predictByTrees(val, 0, nTrees, nClasses, x + iRow * nColumns, dispatcher); - updateResult(res, val, iRow, 0, nClasses, BooleanConstant()); + // probability is a sigmoid(f) hence sign(f) can be checked + const algorithmFPType initial = res[iRow]; + const int sign = SignBit::get(initial); + res[iRow] = label[sign]; } } + return s; +} - template - inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, - algorithmFPType * res) +template +algorithmFPType PredictBinaryClassificationTask::getMarginFromModelBias(algorithmFPType bias) const +{ + DAAL_ASSERT((0.0 < bias) && (bias < 1.0)); + constexpr algorithmFPType one(1); + // convert bias to margin + return -one * daal::internal::MathInst::sLog(one / bias - one); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// PredictMulticlassTask - implementation +////////////////////////////////////////////////////////////////////////////////////////// + +template +void PredictMulticlassTask::updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, + BooleanConstant dispatcher) +{ + res[iRow + i] = getMaxClass(val + i * nClasses, nClasses); +} + +template +void PredictMulticlassTask::updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, + BooleanConstant dispatcher) +{} + +template +algorithmFPType * PredictMulticlassTask::updateBuffer(algorithmFPType * buff, size_t buf_shift, size_t buf_size, + BooleanConstant dispatcher) +{ + services::internal::service_memset_seq(buff, algorithmFPType(0), buf_size); + return buff; +} + +template +algorithmFPType * PredictMulticlassTask::updateBuffer(algorithmFPType * buff, size_t buf_shift, size_t buf_size, + BooleanConstant dispatcher) +{ + return buff + buf_shift; +} + +template +size_t PredictMulticlassTask::getNumberOfNodes(size_t nTrees) +{ + size_t nNodesTotal = 0; + for (size_t iTree = 0; iTree < nTrees; ++iTree) { - if (this->_featHelper.hasUnorderedFeatures()) - { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res); - } - else - { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res); - } + nNodesTotal += this->_aTree[iTree]->getNumberOfNodes(); } + return nNodesTotal; +} - template - inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, - algorithmFPType * res) +template +bool PredictMulticlassTask::checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns) +{ + size_t nLvlTotal = 0; + for (size_t iTree = 0; iTree < nTrees; ++iTree) { - const bool hasAnyMissing = checkForMissing(x, nTrees, nRows, nColumns); - if (hasAnyMissing) - { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res); - } - else + nLvlTotal += this->_aTree[iTree]->getMaxLvl(); + } + if (nLvlTotal <= nColumns) + { + // Checking is complicated. Better to do it during inference. + return true; + } + else + { + for (size_t idx = 0; idx < nRows * nColumns; ++idx) { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res); + if (checkFinitenessByComparison(x[idx])) return true; } } + return false; +} - // Recursivelly checking template parameter until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor. - template - inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, - algorithmFPType * res, const DimType & dim, BooleanConstant keepLooking) +template +template +void PredictMulticlassTask::predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, + algorithmFPType * buff, algorithmFPType * res) +{ + dispatcher_t dispatcher; + size_t iRow = 0; + for (; iRow + vectorBlockSize <= nRows; iRow += vectorBlockSize) { - constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; - if (dim.vectorBlockSizeFactor == vectorBlockSizeFactor) + algorithmFPType * val = updateBuffer(buff, iRow * nClasses, nClasses * vectorBlockSize, BooleanConstant()); + predictByTreesVector(val, 0, nTrees, nClasses, x + iRow * nColumns, dispatcher); + for (size_t i = 0; i < vectorBlockSize; ++i) { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res); - } - else - { - predict( - nTrees, nClasses, nRows, nColumns, x, buff, res, dim, BooleanConstant()); + updateResult(res, val, iRow, i, nClasses, BooleanConstant()); } } + for (; iRow < nRows; ++iRow) + { + algorithmFPType * val = updateBuffer(buff, iRow * nClasses, nClasses, BooleanConstant()); + predictByTrees(val, 0, nTrees, nClasses, x + iRow * nColumns, dispatcher); + updateResult(res, val, iRow, 0, nClasses, BooleanConstant()); + } +} - template - inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, - algorithmFPType * res, const DimType & dim, BooleanConstant keepLooking) +template +template +void PredictMulticlassTask::predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, + algorithmFPType * buff, algorithmFPType * res) +{ + if (this->_featHelper.hasUnorderedFeatures()) { - constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; - predict(nTrees, nClasses, nRows, nColumns, x, buff, res); + predict(nTrees, nClasses, nRows, nColumns, x, buff, res); + } + else + { + predict(nTrees, nClasses, nRows, nColumns, x, buff, res); } +} - template - inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, - algorithmFPType * res, const DimType & dim) +template +template +void PredictMulticlassTask::predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, + algorithmFPType * buff, algorithmFPType * res) +{ + const bool hasAnyMissing = checkForMissing(x, nTrees, nRows, nColumns); + if (hasAnyMissing) { - constexpr size_t maxVectorBlockSizeFactor = DimType::maxVectorBlockSizeFactor; - if (maxVectorBlockSizeFactor > 1) - { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim, - BooleanConstant()); - } - else - { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim, - BooleanConstant()); - } + predict(nTrees, nClasses, nRows, nColumns, x, buff, res); + } + else + { + predict(nTrees, nClasses, nRows, nColumns, x, buff, res); } +} - template - inline void predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, algorithmFPType * buff, - algorithmFPType * res, const DimType & dim) +template +template +void PredictMulticlassTask::predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, + algorithmFPType * buff, algorithmFPType * res, const DimType & dim, + BooleanConstant keepLooking) +{ + constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; + if (dim.vectorBlockSizeFactor == vectorBlockSizeFactor) { - if (res) - { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim); - } - else - { - predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim); - } + predict(nTrees, nClasses, nRows, nColumns, x, buff, res); + } + else + { + predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim, + BooleanConstant()); } +} + +template +template +void PredictMulticlassTask::predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, + algorithmFPType * buff, algorithmFPType * res, const DimType & dim, + BooleanConstant keepLooking) +{ + constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; + predict(nTrees, nClasses, nRows, nColumns, x, buff, res); +} - void softmax(algorithmFPType * Input, algorithmFPType * Output, size_t nRows, size_t nCols); +template +template +void PredictMulticlassTask::predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, + algorithmFPType * buff, algorithmFPType * res, const DimType & dim) +{ + constexpr size_t maxVectorBlockSizeFactor = DimType::maxVectorBlockSizeFactor; + if (maxVectorBlockSizeFactor > 1) + { + predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim, BooleanConstant()); + } + else + { + predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim, BooleanConstant()); + } +} - size_t getMaxClass(const algorithmFPType * val, size_t nClasses) const +template +template +void PredictMulticlassTask::predict(size_t nTrees, size_t nClasses, size_t nRows, size_t nColumns, const algorithmFPType * x, + algorithmFPType * buff, algorithmFPType * res, const DimType & dim) +{ + if (res) { - return services::internal::getMaxElementIndex(val, nClasses); + predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim); } + else + { + predict(nTrees, nClasses, nRows, nColumns, x, buff, res, dim); + } +} -protected: - const NumericTable * _data; - NumericTable * _res; - NumericTable * _prob; - dtrees::internal::FeatureTypes _featHelper; - TArray _aTree; -}; +template +size_t PredictMulticlassTask::getMaxClass(const algorithmFPType * val, size_t nClasses) const +{ + return services::internal::getMaxElementIndex(val, nClasses); +} ////////////////////////////////////////////////////////////////////////////////////////// // PredictKernel From 6c87c7c9d160ee5639d69c5e33b8d3ab21d7eddf Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 18 Oct 2023 07:19:00 -0700 Subject: [PATCH 35/50] review comments #1 --- .../gbt_classification_predict_types.h | 11 ++-- cpp/daal/src/algorithms/dtrees/gbt/BUILD | 2 +- .../gbt_classification_model_builder.cpp | 2 +- .../src/algorithms/dtrees/gbt/gbt_model.cpp | 3 +- ...ression_predict_dense_default_batch_impl.i | 56 ++++++++++--------- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 9 +-- 6 files changed, 42 insertions(+), 41 deletions(-) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h index 8ba92b4ee90..2509a0077d6 100755 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h @@ -70,14 +70,11 @@ namespace interface2 /* [Parameter source code] */ struct DAAL_EXPORT Parameter : public daal::algorithms::classifier::Parameter { - Parameter(size_t nClasses = 2) - : daal::algorithms::classifier::Parameter(nClasses), nIterations(0), predShapContributions(false), predShapInteractions(false) - {} + typedef daal::algorithms::classifier::Parameter super; + + Parameter(size_t nClasses = 2) : super(nClasses), nIterations(0), predShapContributions(false), predShapInteractions(false) {} Parameter(const Parameter & o) - : daal::algorithms::classifier::Parameter(o), - nIterations(o.nIterations), - predShapContributions(o.predShapContributions), - predShapInteractions(o.predShapInteractions) + : super(o), nIterations(o.nIterations), predShapContributions(o.predShapContributions), predShapInteractions(o.predShapInteractions) {} size_t nIterations; /*!< Number of iterations of the trained model to be used for prediction */ bool predShapContributions; /*!< Predict SHAP contributions */ diff --git a/cpp/daal/src/algorithms/dtrees/gbt/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/BUILD index 5c25549bdb5..9d717dae310 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/BUILD @@ -21,4 +21,4 @@ dal_collect_test_suites( modules = [ "regression" ], -) \ No newline at end of file +) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp index 6476e20e1e4..c4c195e400f 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_builder.cpp @@ -83,7 +83,7 @@ services::Status ModelBuilder::createTreeInternal(size_t nNodes, size_t classLab { return Status(ErrorID::ErrorIncorrectParameter); } - if (classLabel > (_nClasses - 1)) + if (_nClasses <= classLabel) { return Status(ErrorID::ErrorIncorrectParameter); } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp index f7f097361d9..391a267f62c 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/gbt_model.cpp @@ -355,7 +355,8 @@ void ModelImpl::getMaxLvl(const dtrees::internal::DecisionTreeNode * const arr, const GbtDecisionTree * ModelImpl::at(const size_t idx) const { - return static_cast((*super::_serializationData)[idx].get()); + auto * const rawTree = (*super::_serializationData)[idx].get(); + return static_cast(rawTree); } } // namespace internal diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index efac26662a3..2b57ea2eddc 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -36,6 +36,7 @@ #include "src/data_management/service_numeric_table.h" #include "src/externals/service_memory.h" #include "src/threading/threading.h" +#include // DBL_EPSILON using namespace daal::internal; using namespace daal::services::internal; @@ -83,8 +84,8 @@ protected: inline size_t getNumberOfNodes(size_t nTrees) { - size_t nNodesTotal = 0; - for (size_t iTree = 0; iTree < nTrees; ++iTree) + size_t nNodesTotal = 0ul; + for (size_t iTree = 0ul; iTree < nTrees; ++iTree) { nNodesTotal += _aTree[iTree]->getNumberOfNodes(); } @@ -93,8 +94,8 @@ protected: inline bool checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns) const { - size_t nLvlTotal = 0; - for (size_t iTree = 0; iTree < nTrees; ++iTree) + size_t nLvlTotal = 0ul; + for (size_t iTree = 0ul; iTree < nTrees; ++iTree) { nLvlTotal += _aTree[iTree]->getMaxLvl(); } @@ -105,7 +106,7 @@ protected: } else { - for (size_t idx = 0; idx < nRows * nColumns; ++idx) + for (size_t idx = 0ul; idx < nRows * nColumns; ++idx) { if (checkFinitenessByComparison(x[idx])) return true; } @@ -178,8 +179,7 @@ protected: inline services::Status predictContributions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, algorithmFPType * res, int condition, FeatureIndexType conditionFeature, const DimType & dim) { - const size_t nColumnsData = dim.nCols; - const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); + const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, dim.nCols); if (hasAnyMissing) { return predictContributions(iTree, nTrees, nRowsData, x, res, condition, conditionFeature, dim); @@ -213,8 +213,7 @@ protected: inline services::Status predictContributionInteractions(size_t iTree, size_t nTrees, size_t nRowsData, const algorithmFPType * x, const algorithmFPType * nominal, algorithmFPType * res, const DimType & dim) { - const size_t nColumnsData = dim.nCols; - const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, nColumnsData); + const bool hasAnyMissing = checkForMissing(x, nTrees, nRowsData, dim.nCols); if (hasAnyMissing) { return predictContributionInteractions(iTree, nTrees, nRowsData, x, nominal, res, dim); @@ -253,9 +252,8 @@ protected: inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim, BooleanConstant keepLooking) { - const size_t nColumns = dim.nCols; constexpr size_t vectorBlockSizeStep = DimType::vectorBlockSizeStep; - predict(iTree, nTrees, nRows, nColumns, x, res); + predict(iTree, nTrees, nRows, dim.nCols, x, res); } inline void predict(size_t iTree, size_t nTrees, size_t nRows, const algorithmFPType * x, algorithmFPType * res, const DimType & dim) @@ -303,7 +301,10 @@ services::Status PredictRegressionTask::run(const gbt::reg const auto nTreesTotal = (nIterations ? nIterations : m->size()); _aTree.reset(nTreesTotal); DAAL_CHECK_MALLOC(_aTree.get()); - for (size_t i = 0; i < nTreesTotal; ++i) _aTree[i] = m->at(i); + + PRAGMA_VECTOR_ALWAYS + for (size_t i = 0ul; i < nTreesTotal; ++i) _aTree[i] = m->at(i); + return runInternal(pHostApp, this->_res, m->getPredictionBias(), predShapContributions, predShapInteractions); } @@ -331,10 +332,10 @@ services::Status PredictRegressionTask::predictContributio const size_t nColumnsPhi = nColumnsData + 1; const size_t biasTermIndex = nColumnsPhi - 1; - for (size_t iRow = 0; iRow < nRowsData; ++iRow) + for (size_t iRow = 0ul; iRow < nRowsData; ++iRow) { - const algorithmFPType * currentX = x + (iRow * nColumnsData); - algorithmFPType * phi = res + (iRow * nColumnsPhi); + const algorithmFPType * const currentX = x + (iRow * nColumnsData); + algorithmFPType * const phi = res + (iRow * nColumnsPhi); for (size_t currentTreeIndex = iTree; currentTreeIndex < iTree + nTrees; ++currentTreeIndex) { const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; @@ -398,13 +399,13 @@ services::Status PredictRegressionTask::predictContributio // Copy nominal values (for bias term) to the condition = 0 buffer PRAGMA_IVDEP PRAGMA_VECTOR_ALWAYS - for (size_t i = 0; i < nRowsData; ++i) + for (size_t i = 0ul; i < nRowsData; ++i) { contribsDiag[i * nColumnsPhi + biasTermIndex] = nominal[i]; } predictContributions(iTree, nTrees, nRowsData, x, contribsDiag, 0, 0, dim); - for (size_t i = 0; i < nColumnsPhi; ++i) + for (size_t i = 0ul; i < nColumnsPhi; ++i) { // initialize/reset the on/off buffers service_memset_seq(contribsOff, algorithmFPType(0), 2 * elementsInMatrix); @@ -412,12 +413,12 @@ services::Status PredictRegressionTask::predictContributio predictContributions(iTree, nTrees, nRowsData, x, contribsOff, -1, i, dim); predictContributions(iTree, nTrees, nRowsData, x, contribsOn, 1, i, dim); - for (size_t j = 0; j < nRowsData; ++j) + for (size_t j = 0ul; j < nRowsData; ++j) { const unsigned dataRowOffset = j * interactionMatrixSize + i * nColumnsPhi; const unsigned columnOffset = j * nColumnsPhi; res[dataRowOffset + i] = 0; - for (size_t k = 0; k < nColumnsPhi; ++k) + for (size_t k = 0ul; k < nColumnsPhi; ++k) { // fill in the diagonal with additive effects, and off-diagonal with the interactions if (k == i) @@ -426,7 +427,8 @@ services::Status PredictRegressionTask::predictContributio } else { - res[dataRowOffset + k] = (contribsOn[columnOffset + k] - contribsOff[columnOffset + k]) / 2.0f; + constexpr algorithmFPType half(0.5); + res[dataRowOffset + k] = (contribsOn[columnOffset + k] - contribsOff[columnOffset + k]) * half; res[dataRowOffset + i] -= res[dataRowOffset + k]; } } @@ -460,7 +462,7 @@ services::Status PredictRegressionTask::runInternal(servic HostAppHelper host(pHostApp, 100); const size_t predictionIndex = resultNColumns - 1; - for (size_t iTree = 0; iTree < nTreesTotal; iTree += dim.nTreesInBlock) + for (size_t iTree = 0ul; iTree < nTreesTotal; iTree += dim.nTreesInBlock) { if (!s || host.isCancelled(s, 1)) return s; size_t nTreesToUse = ((iTree + dim.nTreesInBlock) < nTreesTotal ? dim.nTreesInBlock : (nTreesTotal - iTree)); @@ -480,7 +482,7 @@ services::Status PredictRegressionTask::runInternal(servic safeStat.add(ErrorMemoryAllocationFailed); return; } - for (size_t i = 0; i < nRowsToProcess; ++i) + for (size_t i = 0ul; i < nRowsToProcess; ++i) { nominal[i] = predictionBias; } @@ -494,7 +496,7 @@ services::Status PredictRegressionTask::runInternal(servic // copy nominal predictions for bias term to correct spot in result array auto resRowPtr = resRow.get(); - for (size_t i = 0; i < nRowsToProcess; ++i) + for (size_t i = 0ul; i < nRowsToProcess; ++i) { resRowPtr[i * resultNColumns + predictionIndex] = nominal[i]; } @@ -517,7 +519,7 @@ services::Status PredictRegressionTask::runInternal(servic safeStat.add(ErrorMemoryAllocationFailed); return; } - for (size_t i = 0; i < nRowsToProcess; ++i) + for (size_t i = 0ul; i < nRowsToProcess; ++i) { nominal[i] = predictionBias; } @@ -531,11 +533,11 @@ services::Status PredictRegressionTask::runInternal(servic else { algorithmFPType * res = resMatrix.get() + iStartRow; - if ((predictionBias * predictionBias) > (DBL_EPSILON * DBL_EPSILON)) + if ((predictionBias < 0 && predictionBias < -DBL_EPSILON) || (0 < predictionBias && DBL_EPSILON < predictionBias)) { // memory is already initialized to 0 // only set it to the bias term if it's != 0 - for (size_t i = 0; i < nRowsToProcess; ++i) + for (size_t i = 0ul; i < nRowsToProcess; ++i) { res[i] = predictionBias; } @@ -575,7 +577,7 @@ void PredictRegressionTask::predictByTreesVector(size_t iF PRAGMA_IVDEP PRAGMA_VECTOR_ALWAYS - for (size_t row = 0; row < vectorBlockSize; ++row) + for (size_t row = 0ul; row < vectorBlockSize; ++row) { res[row] += v[row]; } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 3a7ce41e3a2..2a3492295f5 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -44,6 +44,7 @@ #include "src/algorithms/dtrees/gbt/gbt_model_impl.h" #include "src/services/service_arrays.h" #include "src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i" +#include // FLT_EPSILON namespace daal { @@ -157,7 +158,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith // see if we have already split on this feature, // if so we undo that split so we can redo it for this node - size_t previousSplitPathIndex = 0; + size_t previousSplitPathIndex = 0ul; for (; previousSplitPathIndex <= uniqueDepth; ++previousSplitPathIndex) { const FeatureIndexType castIndex = static_cast(uniquePath[previousSplitPathIndex].featureIndex); @@ -220,7 +221,7 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nUniquePath)); DAAL_CHECK_MALLOC(uniquePathData) PathElement init; - for (size_t i = 0; i < nUniquePath; ++i) + for (size_t i = 0ul; i < nUniquePath; ++i) { DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); } @@ -419,14 +420,14 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nElements)); DAAL_CHECK_MALLOC(uniquePathData) PathElement init; - for (size_t i = 0; i < nElements; ++i) + for (size_t i = 0ul; i < nElements; ++i) { DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); } float * pWeights = static_cast(daal_malloc(sizeof(float) * nElements)); DAAL_CHECK_MALLOC(pWeights) - for (size_t i = 0; i < nElements; ++i) + for (size_t i = 0ul; i < nElements; ++i) { pWeights[i] = 0.0f; } From a367244fae2002e7f838ba8b7c3f36f0168945ce Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Wed, 18 Oct 2023 07:43:59 -0700 Subject: [PATCH 36/50] review comments #2 - fix pImpl idiom --- cpp/daal/BUILD | 2 +- .../gbt_classification_model.h | 5 ++-- .../gbt_regression_model.h | 5 ++-- .../gbt_classification_model.cpp | 12 +++++++++ .../gbt_classification_model_impl.h | 3 +++ .../gbt/regression/gbt_regression_model.cpp | 12 +++++++++ .../regression/gbt_regression_model_impl.h | 3 +++ .../src/algorithms/dtrees/gbt/treeshap.cpp | 26 ++++++++++++------- 8 files changed, 51 insertions(+), 17 deletions(-) diff --git a/cpp/daal/BUILD b/cpp/daal/BUILD index 8e756accb1b..cea370c9b2d 100644 --- a/cpp/daal/BUILD +++ b/cpp/daal/BUILD @@ -338,4 +338,4 @@ dal_collect_test_suites( tests = [ ":unit_tests", ], -) \ No newline at end of file +) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h index 5707340a8ad..7c3a3de0d96 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h @@ -128,19 +128,18 @@ class DAAL_EXPORT Model : public classifier::Model * * \param value global prediction bias */ - void setPredictionBias(double value) { _predictionBias = value; } + virtual void setPredictionBias(double value) = 0; /** * \brief Get the Prediction Bias term * * \return double prediction bias */ - double getPredictionBias() const { return _predictionBias; } + virtual double getPredictionBias() const = 0; protected: Model() : classifier::Model() {} -private: /* global bias applied to predictions*/ double _predictionBias; }; diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h index c52d732ef4e..1962a9bc127 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h @@ -128,19 +128,18 @@ class DAAL_EXPORT Model : public algorithms::regression::Model * * \param value global prediction bias */ - void setPredictionBias(double value) { _predictionBias = value; } + virtual void setPredictionBias(double value) = 0; /** * \brief Get the Prediction Bias term * * \return double prediction bias */ - double getPredictionBias() const { return _predictionBias; } + virtual double getPredictionBias() const = 0; protected: Model(); -private: /* global bias applied to predictions*/ double _predictionBias; }; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model.cpp b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model.cpp index 1e4c6b71234..afb01125ef6 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model.cpp @@ -84,10 +84,21 @@ void ModelImpl::traverseBFS(size_t iTree, tree_utils::regression::TreeNodeVisito ImplType::traverseBFS(iTree, visitor); } +void ModelImpl::setPredictionBias(double value) +{ + _predictionBias = value; +} + +double ModelImpl::getPredictionBias() const +{ + return _predictionBias; +} + services::Status ModelImpl::serializeImpl(data_management::InputDataArchive * arch) { auto s = algorithms::classifier::Model::serialImpl(arch); arch->set(this->_nFeatures); //algorithms::classifier::internal::ModelInternal + arch->set(this->_predictionBias); return s.add(ImplType::serialImpl(arch)); } @@ -95,6 +106,7 @@ services::Status ModelImpl::deserializeImpl(const data_management::OutputDataArc { auto s = algorithms::classifier::Model::serialImpl(arch); arch->set(this->_nFeatures); //algorithms::classifier::internal::ModelInternal + arch->set(this->_predictionBias); return s.add(ImplType::serialImpl( arch, COMPUTE_DAAL_VERSION(arch->getMajorVersion(), arch->getMinorVersion(), arch->getUpdateVersion()))); } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h index 8e9fbbc216e..3fcbaa31f18 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h @@ -61,6 +61,9 @@ class ModelImpl : public daal::algorithms::gbt::classification::Model, virtual void traverseDFS(size_t iTree, tree_utils::regression::TreeNodeVisitor & visitor) const DAAL_C11_OVERRIDE; virtual void traverseBFS(size_t iTree, tree_utils::regression::TreeNodeVisitor & visitor) const DAAL_C11_OVERRIDE; + virtual void setPredictionBias(double value) DAAL_C11_OVERRIDE; + virtual double getPredictionBias() const DAAL_C11_OVERRIDE; + virtual services::Status serializeImpl(data_management::InputDataArchive * arch) DAAL_C11_OVERRIDE; virtual services::Status deserializeImpl(const data_management::OutputDataArchive * arch) DAAL_C11_OVERRIDE; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model.cpp index 2c2818b3104..0012688dc1e 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model.cpp @@ -85,9 +85,20 @@ void ModelImpl::traverseBFS(size_t iTree, tree_utils::regression::TreeNodeVisito ImplType::traverseBFS(iTree, visitor); } +void ModelImpl::setPredictionBias(double value) +{ + _predictionBias = value; +} + +double ModelImpl::getPredictionBias() const +{ + return _predictionBias; +} + services::Status ModelImpl::serializeImpl(data_management::InputDataArchive * arch) { auto s = algorithms::regression::Model::serialImpl(arch); + arch->set(this->_predictionBias); s.add(algorithms::regression::internal::ModelInternal::serialImpl(arch)); return s.add(ImplType::serialImpl(arch)); } @@ -95,6 +106,7 @@ services::Status ModelImpl::serializeImpl(data_management::InputDataArchive * ar services::Status ModelImpl::deserializeImpl(const data_management::OutputDataArchive * arch) { auto s = algorithms::regression::Model::serialImpl(arch); + arch->set(this->_predictionBias); s.add(algorithms::regression::internal::ModelInternal::serialImpl(arch)); return s.add(ImplType::serialImpl(arch)); } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h index a711f0d747c..04b83c00d73 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h @@ -61,6 +61,9 @@ class ModelImpl : public daal::algorithms::gbt::regression::Model, virtual void traverseDFS(size_t iTree, tree_utils::regression::TreeNodeVisitor & visitor) const DAAL_C11_OVERRIDE; virtual void traverseBFS(size_t iTree, tree_utils::regression::TreeNodeVisitor & visitor) const DAAL_C11_OVERRIDE; + virtual void setPredictionBias(double value) DAAL_C11_OVERRIDE; + virtual double getPredictionBias() const DAAL_C11_OVERRIDE; + virtual services::Status serializeImpl(data_management::InputDataArchive * arch) DAAL_C11_OVERRIDE; virtual services::Status deserializeImpl(const data_management::OutputDataArchive * arch) DAAL_C11_OVERRIDE; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index 4904a4b542f..d46eee1f650 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -94,22 +94,28 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t float nextOnePortion = uniquePath[uniqueDepth].partialWeight; float total = 0; - for (int i = uniqueDepth - 1; i >= 0; --i) + if (oneFraction != 0) { - if (oneFraction != 0) + const float frac = zeroFraction / oneFraction; + for (int i = uniqueDepth - 1; i >= 0; --i) { - const float tmp = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); + const float tmp = nextOnePortion / (i + 1); total += tmp; - nextOnePortion = uniquePath[i].partialWeight - tmp * zeroFraction * ((uniqueDepth - i) / static_cast(uniqueDepth + 1)); + nextOnePortion = uniquePath[i].partialWeight - tmp * frac * (uniqueDepth - i); } - else if (zeroFraction != 0) - { - total += (uniquePath[i].partialWeight / zeroFraction) / ((uniqueDepth - i) / static_cast(uniqueDepth + 1)); - } - else + total *= (uniqueDepth + 1) / oneFraction; + } + else if (zeroFraction != 0) + { + for (int i = uniqueDepth - 1; i >= 0; --i) { - DAAL_ASSERT(uniquePath[i].partialWeight == 0); + total += uniquePath[i].partialWeight / (uniqueDepth - i); } + total *= (uniqueDepth + 1) / zeroFraction; + } + else + { + DAAL_ASSERT(uniquePath[i].partialWeight == 0); } return total; From 10a9984e35be4d714878228042a9be68e072ac2c Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 19 Oct 2023 03:48:11 -0700 Subject: [PATCH 37/50] refactor: replace boolean parameters with DAAL_UINT64 flag --- .../gbt_classification_predict_types.h | 22 ++++++++++------ .../gbt_regression_predict_types.h | 25 +++++++++++-------- .../gbt_classification_predict_types.cpp | 15 ++++++++--- .../gbt_regression_predict_container.h | 4 ++- .../gbt_regression_predict_result_fpt.cpp | 4 +-- .../gbt_regression_predict_types.cpp | 8 +++--- 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h index 2509a0077d6..58aeea58a3a 100755 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_predict_types.h @@ -56,6 +56,17 @@ enum Method defaultDense = 0 /*!< Default method */ }; +/** + * + * Available identifiers to specify the result to compute - results are mutually exclusive + */ +enum ResultToComputeId +{ + predictionResult = (1 << 0), /*!< Compute the regular prediction */ + shapContributions = (1 << 1), /*!< Compute SHAP contribution values */ + shapInteractions = (1 << 2) /*!< Compute SHAP interaction values */ +}; + /** * \brief Contains version 2.0 of the Intel(R) oneAPI Data Analytics Library interface. */ @@ -72,13 +83,10 @@ struct DAAL_EXPORT Parameter : public daal::algorithms::classifier::Parameter { typedef daal::algorithms::classifier::Parameter super; - Parameter(size_t nClasses = 2) : super(nClasses), nIterations(0), predShapContributions(false), predShapInteractions(false) {} - Parameter(const Parameter & o) - : super(o), nIterations(o.nIterations), predShapContributions(o.predShapContributions), predShapInteractions(o.predShapInteractions) - {} - size_t nIterations; /*!< Number of iterations of the trained model to be used for prediction */ - bool predShapContributions; /*!< Predict SHAP contributions */ - bool predShapInteractions; /*!< Predict SHAP interactions */ + Parameter(size_t nClasses = 2) : super(nClasses), nIterations(0), resultsToCompute(predictionResult) {} + Parameter(const Parameter & o) : super(o), nIterations(o.nIterations), resultsToCompute(o.resultsToCompute) {} + size_t nIterations; /*!< Number of iterations of the trained model to be used for prediction */ + DAAL_UINT64 resultsToCompute; /*!< 64 bit integer flag that indicates the results to compute */ }; /* [Parameter source code] */ } // namespace interface2 diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h index 33691d6ac1a..a1984a3ca3d 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h @@ -90,6 +90,17 @@ enum ResultId lastResultId = prediction }; +/** + * + * Available identifiers to specify the result to compute - results are mutually exclusive + */ +enum ResultToComputeId +{ + predictionResult = (1 << 0), /*!< Compute the regular prediction */ + shapContributions = (1 << 1), /*!< Compute SHAP contribution values */ + shapInteractions = (1 << 2) /*!< Compute SHAP interaction values */ +}; + /** * \brief Contains version 1.0 of the Intel(R) oneAPI Data Analytics Library interface */ @@ -104,16 +115,10 @@ namespace interface1 /* [Parameter source code] */ struct DAAL_EXPORT Parameter : public daal::algorithms::Parameter { - Parameter() : daal::algorithms::Parameter(), nIterations(0), predShapContributions(false), predShapInteractions(false) {} - Parameter(const Parameter & o) - : daal::algorithms::Parameter(o), - nIterations(o.nIterations), - predShapContributions(o.predShapContributions), - predShapInteractions(o.predShapInteractions) - {} - size_t nIterations; /*!< Number of iterations of the trained model to be uses for prediction*/ - bool predShapContributions; /*!< Predict SHAP contributions */ - bool predShapInteractions; /*!< Predict SHAP interactions */ + Parameter() : daal::algorithms::Parameter(), nIterations(0), resultsToCompute(predictionResult) {} + Parameter(const Parameter & o) : daal::algorithms::Parameter(o), nIterations(o.nIterations), resultsToCompute(o.resultsToCompute) {} + size_t nIterations; /*!< Number of iterations of the trained model to be uses for prediction*/ + DAAL_UINT64 resultsToCompute; /*!< 64 bit integer flag that indicates the results to compute */ }; /* [Parameter source code] */ diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_types.cpp b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_types.cpp index f3ac56aa5f5..58e3da1a52b 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_types.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_types.cpp @@ -96,20 +96,27 @@ services::Status Input::check(const daal::algorithms::Parameter * parameter, int size_t nClasses = 0, nIterations = 0; - const gbt::classification::prediction::interface2::Parameter * pPrm2 = + const gbt::classification::prediction::interface2::Parameter * pPrm = dynamic_cast(parameter); - if (pPrm2) + if (pPrm) { - nClasses = pPrm2->nClasses; - nIterations = pPrm2->nIterations; + nClasses = pPrm->nClasses; + nIterations = pPrm->nIterations; } else + { return services::ErrorNullParameterNotSupported; + } auto maxNIterations = pModel->getNumberOfTrees(); if (nClasses > 2) maxNIterations /= nClasses; DAAL_CHECK((nClasses < 3) || (pModel->getNumberOfTrees() % nClasses == 0), services::ErrorGbtIncorrectNumberOfTrees); DAAL_CHECK((nIterations == 0) || (nIterations <= maxNIterations), services::ErrorGbtPredictIncorrectNumberOfIterations); + + const bool predictContribs = pPrm->resultsToCompute & shapContributions; + const bool predictInteractions = pPrm->resultsToCompute & shapInteractions; + DAAL_CHECK(!(predictContribs || predictInteractions), services::ErrorMethodNotImplemented); + return s; } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h index 0a63fd0aac4..d2e4bd4bdf7 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_container.h @@ -61,8 +61,10 @@ services::Status BatchContainer::compute() const gbt::regression::prediction::Parameter * par = static_cast(_par); daal::services::Environment::env & env = *_env; + const bool predShapContributions = par->resultsToCompute & shapContributions; + const bool predShapInteractions = par->resultsToCompute & shapInteractions; __DAAL_CALL_KERNEL(env, internal::PredictKernel, __DAAL_KERNEL_ARGUMENTS(algorithmFPType, method), compute, - daal::services::internal::hostApp(*input), a, m, r, par->nIterations, par->predShapContributions, par->predShapInteractions); + daal::services::internal::hostApp(*input), a, m, r, par->nIterations, predShapContributions, predShapInteractions); } } // namespace prediction diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp index 6ba92a2b436..e63ff6e91a3 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_result_fpt.cpp @@ -48,12 +48,12 @@ DAAL_EXPORT services::Status Result::allocate(const daal::algorithms::Input * in size_t nColumnsToAllocate = 1; const Parameter * regressionParameter = static_cast(par); - if (regressionParameter->predShapContributions) + if (regressionParameter->resultsToCompute & shapContributions) { const size_t nColumns = dataPtr->getNumberOfColumns(); nColumnsToAllocate = nColumns + 1; } - else if (regressionParameter->predShapInteractions) + else if (regressionParameter->resultsToCompute & shapInteractions) { const size_t nColumns = dataPtr->getNumberOfColumns(); nColumnsToAllocate = (nColumns + 1) * (nColumns + 1); diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp index 268c3dbe069..82e6492e309 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_types.cpp @@ -107,7 +107,9 @@ services::Status Input::check(const daal::algorithms::Parameter * parameter, int size_t nIterations = pPrm->nIterations; DAAL_CHECK((nIterations == 0) || (nIterations <= maxNIterations), services::ErrorGbtPredictIncorrectNumberOfIterations); - DAAL_CHECK(!(pPrm->predShapContributions && pPrm->predShapInteractions), services::ErrorGbtPredictShapOptions); + const bool predictContribs = pPrm->resultsToCompute & shapContributions; + const bool predictInteractions = pPrm->resultsToCompute & shapInteractions; + DAAL_CHECK(!(predictContribs && predictInteractions), services::ErrorGbtPredictShapOptions); return s; } @@ -146,12 +148,12 @@ services::Status Result::check(const daal::algorithms::Input * input, const daal const auto inputCast = static_cast(input); const prediction::Parameter * regressionParameter = static_cast(par); size_t expectedNColumns = 1; - if (regressionParameter->predShapContributions) + if (regressionParameter->resultsToCompute & shapContributions) { const size_t nColumns = inputCast->get(data)->getNumberOfColumns(); expectedNColumns = nColumns + 1; } - else if (regressionParameter->predShapInteractions) + else if (regressionParameter->resultsToCompute & shapInteractions) { const size_t nColumns = inputCast->get(data)->getNumberOfColumns(); expectedNColumns = (nColumns + 1) * (nColumns + 1); From 3ef5a9c7fa050fb1058176f593708440a9ec99dc Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 19 Oct 2023 06:47:05 -0700 Subject: [PATCH 38/50] fix: usage of bias/margin for LightGBM models --- ...gbt_classification_predict_dense_default_batch_impl.i | 9 ++++++++- cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i index 7f52051e8c0..190b483158a 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i @@ -38,6 +38,7 @@ #include "src/algorithms/dtrees/gbt/gbt_predict_dense_default_impl.i" #include "src/algorithms/objective_function/cross_entropy_loss/cross_entropy_loss_dense_default_batch_kernel.h" #include "src/services/service_algo_utils.h" +#include using namespace daal::internal; using namespace daal::services::internal; @@ -420,7 +421,13 @@ services::Status PredictBinaryClassificationTask::run(cons services::Status s; DAAL_OVERFLOW_CHECK_BY_MULTIPLICATION(size_t, nRows, sizeof(algorithmFPType)); - algorithmFPType margin = getMarginFromModelBias(m->getPredictionBias()); + // we convert the bias to a margin if it's > 0 + // otherwise the margin is 0 + algorithmFPType margin(0); + if (m->getPredictionBias() > FLT_EPSILON) + { + margin = getMarginFromModelBias(m->getPredictionBias()); + } // compute raw boosted values if (this->_res && _prob) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index d46eee1f650..fafa14c6792 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -115,7 +115,10 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t } else { - DAAL_ASSERT(uniquePath[i].partialWeight == 0); + for (int i = uniqueDepth - 1; i >= 0; --i) + { + DAAL_ASSERT(uniquePath[i].partialWeight == 0); + } } return total; From 73326adaca237ca95591e01ddc2f867435e52ce5 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 19 Oct 2023 07:05:50 -0700 Subject: [PATCH 39/50] review comments #2 --- .../src/algorithms/dtrees/dtrees_model_impl.h | 2 +- .../src/algorithms/dtrees/gbt/treeshap.cpp | 21 +++++++++++-------- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 1 - 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h index 15cb6a92dbe..08c04d677a8 100644 --- a/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/dtrees_model_impl.h @@ -53,7 +53,7 @@ struct DecisionTreeNode ClassIndexType leftIndexOrClass; //split: left node index, classification leaf: class index ModelFPType featureValueOrResponse; //split: feature value, regression tree leaf: response int defaultLeft; //split: if 1: go to the yes branch for missing value - double cover; //split: cover (sum_hess) of the node + ModelFPType cover; //split: cover (sum_hess) of the node DAAL_FORCEINLINE bool isSplit() const { return featureIndex != -1; } ModelFPType featureValue() const { return featureValueOrResponse; } }; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index fafa14c6792..227fd65f38e 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -49,7 +49,7 @@ void extendPath(PathElement * uniquePath, size_t uniqueDepth, float zeroFraction uniquePath[uniqueDepth].partialWeight = (uniqueDepth == 0 ? 1.0f : 0.0f); const float constant = 1.0f / static_cast(uniqueDepth + 1); - for (int i = uniqueDepth - 1; i >= 0; i--) + for (int i = uniqueDepth - 1; i >= 0; --i) { uniquePath[i + 1].partialWeight += oneFraction * uniquePath[i].partialWeight * (i + 1) * constant; uniquePath[i].partialWeight = zeroFraction * uniquePath[i].partialWeight * (uniqueDepth - i) * constant; @@ -63,15 +63,18 @@ void unwindPath(PathElement * uniquePath, size_t uniqueDepth, size_t pathIndex) const float zeroFraction = uniquePath[pathIndex].zeroFraction; float nextOnePortion = uniquePath[uniqueDepth].partialWeight; - for (int i = uniqueDepth - 1; i >= 0; --i) + if (oneFraction != 0) { - if (oneFraction != 0) + for (int i = uniqueDepth - 1; i >= 0; --i) { const float tmp = uniquePath[i].partialWeight; uniquePath[i].partialWeight = nextOnePortion * (uniqueDepth + 1) / static_cast((i + 1) * oneFraction); nextOnePortion = tmp - uniquePath[i].partialWeight * zeroFraction * (uniqueDepth - i) / static_cast(uniqueDepth + 1); } - else + } + else + { + for (int i = 0; i < uniqueDepth; ++i) { uniquePath[i].partialWeight = (uniquePath[i].partialWeight * (uniqueDepth + 1)) / static_cast(zeroFraction * (uniqueDepth - i)); } @@ -107,7 +110,7 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t } else if (zeroFraction != 0) { - for (int i = uniqueDepth - 1; i >= 0; --i) + for (int i = 0; i < uniqueDepth; ++i) { total += uniquePath[i].partialWeight / (uniqueDepth - i); } @@ -115,7 +118,7 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t } else { - for (int i = uniqueDepth - 1; i >= 0; --i) + for (int i = 0; i < uniqueDepth; ++i) { DAAL_ASSERT(uniquePath[i].partialWeight == 0); } @@ -162,7 +165,7 @@ void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqu if (oneFraction != 0) { // shrink partialWeights iff the feature satisfies the threshold - for (int i = uniqueDepthPartialWeights - 1; i >= 0; --i) + for (unsigned i = uniqueDepthPartialWeights - 1; i >= 0; --i) { const float tmp = partialWeights[i]; partialWeights[i] = nextOnePortion * (uniqueDepth + 1) / static_cast(i + 1); @@ -171,7 +174,7 @@ void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqu } else { - for (int i = uniqueDepthPartialWeights; i >= 0; --i) + for (unsigned i = 0; i <= uniqueDepthPartialWeights; ++i) { partialWeights[i] *= (uniqueDepth + 1) / static_cast(uniqueDepth - i); } @@ -207,7 +210,7 @@ float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, uns float total = 0; if (uniqueDepth > uniqueDepthPartialWeights) { - for (int i = uniqueDepthPartialWeights; i >= 0; --i) + for (unsigned i = 0; i <= uniqueDepthPartialWeights; ++i) { total += partialWeights[i] / static_cast(uniqueDepth - i); } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 2a3492295f5..52b6ec50e92 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -290,7 +290,6 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith if (isLeaf) { - // +1 to account for -1 in splitValues array const unsigned valuesOffset = nodeIndex * numOutputs; unsigned valuesNonZeroInd = 0; unsigned valuesNonZeroCount = 0; From 3ba321b223f9dfb3569129412a5cb957a0692116 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 19 Oct 2023 07:48:16 -0700 Subject: [PATCH 40/50] fixup endless for loop --- cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index 227fd65f38e..a50ac3a3a67 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -165,11 +165,12 @@ void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqu if (oneFraction != 0) { // shrink partialWeights iff the feature satisfies the threshold - for (unsigned i = uniqueDepthPartialWeights - 1; i >= 0; --i) + for (unsigned i = uniqueDepthPartialWeights - 1;; --i) { const float tmp = partialWeights[i]; partialWeights[i] = nextOnePortion * (uniqueDepth + 1) / static_cast(i + 1); nextOnePortion = tmp - partialWeights[i] * zeroFraction * (uniqueDepth - i) / static_cast(uniqueDepth + 1); + if (i == 0) break; } } else From 48167a50cb65d8347b94eea88b2254c7bce9935b Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 19 Oct 2023 08:28:03 -0700 Subject: [PATCH 41/50] use TArray, introduce TreeShapVersion enum --- ...ression_predict_dense_default_batch_impl.i | 4 +- .../src/algorithms/dtrees/gbt/treeshap.cpp | 9 --- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 74 ++++++++----------- 3 files changed, 33 insertions(+), 54 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 2b57ea2eddc..0306e7663ce 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -339,8 +339,8 @@ services::Status PredictRegressionTask::predictContributio for (size_t currentTreeIndex = iTree; currentTreeIndex < iTree + nTrees; ++currentTreeIndex) { const gbt::internal::GbtDecisionTree * currentTree = _aTree[currentTreeIndex]; - st |= gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, condition, - conditionFeature); + st |= gbt::treeshap::treeShap(currentTree, currentX, phi, &_featHelper, + condition, conditionFeature); } if (condition == 0) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index a50ac3a3a67..7aadccef2b9 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -25,15 +25,6 @@ namespace gbt namespace treeshap { -uint8_t getRequestedAlgorithmVersion(uint8_t fallback) -{ - char * val = getenv("SHAP_VERSION"); - if (val) - { - return atoi(val); - } - return fallback; -} namespace internal { diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index 52b6ec50e92..f4c3ceba41d 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -211,25 +211,19 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) * \param conditionFeature the index of the feature to fix */ -template +template inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) { services::Status st; - const int depth = tree->getMaxLvl() + 2; - const size_t nUniquePath = ((depth * (depth + 1)) / 2); - PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nUniquePath)); - DAAL_CHECK_MALLOC(uniquePathData) - PathElement init; - for (size_t i = 0ul; i < nUniquePath; ++i) - { - DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); - } + const int depth = tree->getMaxLvl() + 2; + const size_t nUniquePath = ((depth * (depth + 1)) / 2); - treeShap(tree, x, phi, featureHelper, 1, 0, 0, uniquePathData, 1, 1, -1, condition, - conditionFeature, 1); + TArray uniquePathData(nUniquePath); + DAAL_CHECK_MALLOC(uniquePathData.get()); - daal_free(uniquePathData); + treeShap(tree, x, phi, featureHelper, 1, 0, 0, uniquePathData.get(), 1, 1, -1, condition, + conditionFeature, 1); return st; } @@ -407,35 +401,24 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) * \param conditionFeature the index of the feature to fix */ -template +template inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) { services::Status st; // pre-allocate space for the unique path data and pWeights - const int depth = tree->getMaxLvl() + 2; - const size_t nElements = (depth * (depth + 1)) / 2; - PathElement * uniquePathData = static_cast(daal_malloc(sizeof(PathElement) * nElements)); - DAAL_CHECK_MALLOC(uniquePathData) - PathElement init; - for (size_t i = 0ul; i < nElements; ++i) - { - DAAL_ASSERT(0 == daal::services::internal::daal_memcpy_s(uniquePathData + i, sizeof(PathElement), &init, sizeof(PathElement))); - } + const int depth = tree->getMaxLvl() + 2; + const size_t nElements = (depth * (depth + 1)) / 2; - float * pWeights = static_cast(daal_malloc(sizeof(float) * nElements)); - DAAL_CHECK_MALLOC(pWeights) - for (size_t i = 0ul; i < nElements; ++i) - { - pWeights[i] = 0.0f; - } + TArray uniquePathData(nElements); + DAAL_CHECK_MALLOC(uniquePathData.get()); - treeShap(tree, x, phi, featureHelper, 1, 0, 0, 0, uniquePathData, pWeights, 1, 1, 1, -1, - condition, conditionFeature, 1); + TArray pWeights(nElements); + DAAL_CHECK_MALLOC(pWeights.get()); - daal_free(uniquePathData); - daal_free(pWeights); + treeShap(tree, x, phi, featureHelper, 1, 0, 0, 0, uniquePathData.get(), pWeights.get(), 1, + 1, 1, -1, condition, conditionFeature, 1); return st; } @@ -443,6 +426,12 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co } // namespace internal +enum TreeShapVersion +{ + lundberg = 0, /** https://arxiv.org/abs/1802.03888 */ + fast_v1, /** https://arxiv.org/abs/2109.09847 */ +}; + /** * \brief Recursive function that computes the feature attributions for a single tree. * \param tree current tree @@ -452,24 +441,23 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co * \param condition fix one feature to either off (-1) on (1) or not fixed (0 default) * \param conditionFeature the index of the feature to fix */ -template +template inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, const algorithmFPType * x, algorithmFPType * phi, - const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature) + const FeatureTypes * featureHelper, int condition, FeatureIndexType conditionFeature, + TreeShapVersion shapVersion = fast_v1) { DAAL_ASSERT(x); DAAL_ASSERT(phi); DAAL_ASSERT(featureHelper); - uint8_t shapVersion = getRequestedAlgorithmVersion(1); - switch (shapVersion) { - case 0: - return treeshap::internal::v0::treeShap(tree, x, phi, featureHelper, condition, - conditionFeature); - case 1: - return treeshap::internal::v1::treeShap(tree, x, phi, featureHelper, condition, - conditionFeature); + case lundberg: + return treeshap::internal::v0::treeShap(tree, x, phi, featureHelper, condition, + conditionFeature); + case fast_v1: + return treeshap::internal::v1::treeShap(tree, x, phi, featureHelper, condition, + conditionFeature); default: return services::Status(ErrorMethodNotImplemented); } } From 5e94bcf66b2309da0531ad6129db68fd1594d6f7 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 19 Oct 2023 08:39:15 -0700 Subject: [PATCH 42/50] use TArray where possible --- ...ression_predict_dense_default_batch_impl.i | 46 ++++++------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i index 0306e7663ce..7b506fe36b5 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_predict_dense_default_batch_impl.i @@ -476,19 +476,13 @@ services::Status PredictRegressionTask::runInternal(servic if (predShapContributions) { // nominal values are required to calculate the correct bias term - algorithmFPType * nominal = static_cast(daal_malloc(nRowsToProcess * sizeof(algorithmFPType))); - if (!nominal) - { - safeStat.add(ErrorMemoryAllocationFailed); - return; - } - for (size_t i = 0ul; i < nRowsToProcess; ++i) - { - nominal[i] = predictionBias; - } + TArray nominal(nRowsToProcess); + algorithmFPType * nominalPtr = nominal.get(); + DAAL_CHECK_MALLOC_THR(nominalPtr); + service_memset(nominalPtr, algorithmFPType(predictionBias), nRowsToProcess); // bias term: prediction - sum_i phi_i (subtraction in predictContributions) - predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominalPtr, dim); // thread-local write rows into global result buffer WriteOnlyRows resRow(result, iStartRow, nRowsToProcess); @@ -498,11 +492,9 @@ services::Status PredictRegressionTask::runInternal(servic auto resRowPtr = resRow.get(); for (size_t i = 0ul; i < nRowsToProcess; ++i) { - resRowPtr[i * resultNColumns + predictionIndex] = nominal[i]; + resRowPtr[i * resultNColumns + predictionIndex] = nominalPtr[i]; } - daal_free(nominal); - // TODO: support tree weights safeStat |= predictContributions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), resRowPtr, 0, 0, dim); } @@ -513,22 +505,15 @@ services::Status PredictRegressionTask::runInternal(servic DAAL_CHECK_BLOCK_STATUS_THR(resRow); // nominal values are required to calculate the correct bias term - algorithmFPType * nominal = static_cast(daal_malloc(nRowsToProcess * sizeof(algorithmFPType))); - if (!nominal) - { - safeStat.add(ErrorMemoryAllocationFailed); - return; - } - for (size_t i = 0ul; i < nRowsToProcess; ++i) - { - nominal[i] = predictionBias; - } - predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, dim); + TArray nominal(nRowsToProcess); + algorithmFPType * nominalPtr = nominal.get(); + DAAL_CHECK_MALLOC_THR(nominalPtr); + service_memset(nominalPtr, algorithmFPType(predictionBias), nRowsToProcess); - // TODO: support tree weights - safeStat |= predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominal, resRow.get(), dim); + predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominalPtr, dim); - daal_free(nominal); + // TODO: support tree weights + safeStat |= predictContributionInteractions(iTree, nTreesToUse, nRowsToProcess, xBD.get(), nominalPtr, resRow.get(), dim); } else { @@ -537,10 +522,7 @@ services::Status PredictRegressionTask::runInternal(servic { // memory is already initialized to 0 // only set it to the bias term if it's != 0 - for (size_t i = 0ul; i < nRowsToProcess; ++i) - { - res[i] = predictionBias; - } + service_memset(res, algorithmFPType(predictionBias), nRowsToProcess); } predict(iTree, nTreesToUse, nRowsToProcess, xBD.get(), res, dim); } From 6a7b7c921e665114b5a3673d0760f455f4a24298 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 20 Oct 2023 02:49:25 -0700 Subject: [PATCH 43/50] fix: move data field to implementation class --- .../gradient_boosted_trees/gbt_classification_model.h | 3 --- .../algorithms/gradient_boosted_trees/gbt_regression_model.h | 3 --- .../dtrees/gbt/classification/gbt_classification_model_impl.h | 4 ++++ .../dtrees/gbt/regression/gbt_regression_model_impl.h | 4 ++++ 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h index 7c3a3de0d96..f6e0a7e8188 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_classification_model.h @@ -139,9 +139,6 @@ class DAAL_EXPORT Model : public classifier::Model protected: Model() : classifier::Model() {} - - /* global bias applied to predictions*/ - double _predictionBias; }; /** @} */ typedef services::SharedPtr ModelPtr; diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h index 1962a9bc127..6e5e2b33027 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_model.h @@ -139,9 +139,6 @@ class DAAL_EXPORT Model : public algorithms::regression::Model protected: Model(); - - /* global bias applied to predictions*/ - double _predictionBias; }; typedef services::SharedPtr ModelPtr; typedef services::SharedPtr ModelConstPtr; diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h index 3fcbaa31f18..931780a5f1c 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_model_impl.h @@ -68,6 +68,10 @@ class ModelImpl : public daal::algorithms::gbt::classification::Model, virtual services::Status deserializeImpl(const data_management::OutputDataArchive * arch) DAAL_C11_OVERRIDE; virtual size_t getNumberOfTrees() const DAAL_C11_OVERRIDE; + +private: + /* global bias applied to predictions*/ + double _predictionBias; }; } // namespace internal diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h index 04b83c00d73..13409fb1790 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/gbt_regression_model_impl.h @@ -68,6 +68,10 @@ class ModelImpl : public daal::algorithms::gbt::regression::Model, virtual services::Status deserializeImpl(const data_management::OutputDataArchive * arch) DAAL_C11_OVERRIDE; virtual size_t getNumberOfTrees() const DAAL_C11_OVERRIDE; + +private: + /* global bias applied to predictions*/ + double _predictionBias; }; } // namespace internal From d65f80615f7d48f3dda8c36968e67356aa59099a Mon Sep 17 00:00:00 2001 From: Andreas Huber <9201869+ahuber21@users.noreply.github.com> Date: Fri, 20 Oct 2023 11:50:06 +0200 Subject: [PATCH 44/50] Update cpp/daal/include/algorithms/tree_utils/tree_utils.h Co-authored-by: Victoriya Fedotova --- cpp/daal/include/algorithms/tree_utils/tree_utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/daal/include/algorithms/tree_utils/tree_utils.h b/cpp/daal/include/algorithms/tree_utils/tree_utils.h index 96db91cc0f6..d98ef4779cc 100644 --- a/cpp/daal/include/algorithms/tree_utils/tree_utils.h +++ b/cpp/daal/include/algorithms/tree_utils/tree_utils.h @@ -59,7 +59,7 @@ struct DAAL_EXPORT SplitNodeDescriptor : public NodeDescriptor { size_t featureIndex; /*!< Feature used for splitting the node */ double featureValue; /*!< Threshold value at the node */ - double coverValue; /*!< Cover (sum_hess) for the node */ + double coverValue; /*!< Cover, a sum of the Hessian values of the loss function evaluated at the points flowing through the node */ }; /** From f0a42a983b136dbe10a51690c329201c78aa3f8d Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 20 Oct 2023 03:04:11 -0700 Subject: [PATCH 45/50] add typedef to shorten statements --- .../gradient_boosted_trees/gbt_regression_predict_types.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h index a1984a3ca3d..f69591a9a6e 100644 --- a/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h +++ b/cpp/daal/include/algorithms/gradient_boosted_trees/gbt_regression_predict_types.h @@ -115,8 +115,10 @@ namespace interface1 /* [Parameter source code] */ struct DAAL_EXPORT Parameter : public daal::algorithms::Parameter { - Parameter() : daal::algorithms::Parameter(), nIterations(0), resultsToCompute(predictionResult) {} - Parameter(const Parameter & o) : daal::algorithms::Parameter(o), nIterations(o.nIterations), resultsToCompute(o.resultsToCompute) {} + typedef daal::algorithms::Parameter super; + + Parameter() : super(), nIterations(0), resultsToCompute(predictionResult) {} + Parameter(const Parameter & o) : super(o), nIterations(o.nIterations), resultsToCompute(o.resultsToCompute) {} size_t nIterations; /*!< Number of iterations of the trained model to be uses for prediction*/ DAAL_UINT64 resultsToCompute; /*!< 64 bit integer flag that indicates the results to compute */ }; From b4a50583d9b76892ca6209dbdbe13853da6e505d Mon Sep 17 00:00:00 2001 From: Dmitry Razdoburdin <> Date: Fri, 20 Oct 2023 06:26:28 -0700 Subject: [PATCH 46/50] provide doxygen description of gbt classification funtions --- ...ication_predict_dense_default_batch_impl.i | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i index 190b483158a..9037b102b66 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i @@ -203,7 +203,7 @@ protected: inline void updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, BooleanConstant dispatcher); /** - * \brief + * \brief Empty function if results assigning is not required. * * \param res Pointer to result array * \param val Value of current prediction @@ -215,7 +215,7 @@ protected: inline void updateResult(algorithmFPType * res, algorithmFPType * val, size_t iRow, size_t i, size_t nClasses, BooleanConstant dispatcher); /** - * \brief + * \brief Prepare buff pointer for the next using. All steps reuse the same memory. * * \param buff Pointer to a buffer * \param buf_shift @@ -226,7 +226,7 @@ protected: inline algorithmFPType * updateBuffer(algorithmFPType * buff, size_t buf_shift, size_t buf_size, BooleanConstant dispatcher); /** - * \brief + * \brief Prepare buff pointer for the next using. Steps have own memory. * * \param buff * \param buf_shift @@ -251,8 +251,8 @@ protected: * \param nTrees Number of contributing trees * \param nRows Number of rows in input observation data to be considered * \param nColumns Number of columns in input observation data to be considered - * \return true If missing data is found - * \return false If no missing data is found + * \return true If runtime check for missing is requared + * \return false If runtime check for missing is not requared */ inline bool checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns); @@ -261,8 +261,8 @@ protected: * * \param hasUnorderedFeatures Data has unordered features yes/no * \param hasAnyMissing Data has missing values yes/no - * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) - * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSize Vector instruction block size * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes @@ -280,8 +280,8 @@ protected: * \brief Traverse a number of trees to get prediction results * * \param hasAnyMissing Data has missing values yes/no - * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) - * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSize Vector instruction block size * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes @@ -298,8 +298,8 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) - * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSize Vector instruction block size * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes @@ -316,8 +316,8 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) - * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSizeFactor Vector instruction block size - recursively decremented until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes @@ -334,8 +334,8 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) - * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSizeFactor Vector instruction block size - recursively decremented until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes @@ -354,8 +354,8 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (triggers initialization if necessary) - * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes * \param nRows Number of rows in observation data for which prediction is run @@ -372,7 +372,7 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param reuseBuffer Re-use buffer yes/no (will initialize fresh memory as necessary if no) + * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes * \param nRows Number of rows in observation data for which prediction is run From 137e1bb34d4e612175c4c6aa6bcff6aad9ef3a06 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Fri, 20 Oct 2023 07:55:12 -0700 Subject: [PATCH 47/50] fix some typos --- ...ication_predict_dense_default_batch_impl.i | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i index 9037b102b66..00a9ee5884d 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i +++ b/cpp/daal/src/algorithms/dtrees/gbt/classification/gbt_classification_predict_dense_default_batch_impl.i @@ -80,7 +80,7 @@ public: * * \param m The model for which to run prediction * \param nIterations Number of iterations - * \param pHostApp HostAppIntertface + * \param pHostApp HostAppInterface * \return services::Status */ services::Status run(const gbt::classification::internal::ModelImpl * m, size_t nIterations, services::HostAppIface * pHostApp); @@ -125,7 +125,7 @@ public: * \param m The model for which to run prediction * \param nClasses Number of data classes * \param nIterations Number of iterations - * \param pHostApp HostAppIntertface + * \param pHostApp HostAppInterface * \return services::Status */ services::Status run(const gbt::classification::internal::ModelImpl * m, size_t nClasses, size_t nIterations, services::HostAppIface * pHostApp); @@ -251,8 +251,8 @@ protected: * \param nTrees Number of contributing trees * \param nRows Number of rows in input observation data to be considered * \param nColumns Number of columns in input observation data to be considered - * \return true If runtime check for missing is requared - * \return false If runtime check for missing is not requared + * \return true If runtime check for missing is required + * \return false If runtime check for missing is not required */ inline bool checkForMissing(const algorithmFPType * x, size_t nTrees, size_t nRows, size_t nColumns); @@ -261,7 +261,7 @@ protected: * * \param hasUnorderedFeatures Data has unordered features yes/no * \param hasAnyMissing Data has missing values yes/no - * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skip if no) * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSize Vector instruction block size * \param nTrees Number of trees contributing to prediction @@ -280,7 +280,7 @@ protected: * \brief Traverse a number of trees to get prediction results * * \param hasAnyMissing Data has missing values yes/no - * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skip if no) * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSize Vector instruction block size * \param nTrees Number of trees contributing to prediction @@ -298,7 +298,7 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skip if no) * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSize Vector instruction block size * \param nTrees Number of trees contributing to prediction @@ -316,7 +316,7 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skip if no) * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSizeFactor Vector instruction block size - recursively decremented until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor * \param nTrees Number of trees contributing to prediction @@ -334,7 +334,7 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skip if no) * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param vectorBlockSizeFactor Vector instruction block size - recursively decremented until it becomes equal to dim.vectorBlockSizeFactor or equal to DimType::minVectorBlockSizeFactor * \param nTrees Number of trees contributing to prediction @@ -354,7 +354,7 @@ protected: /** * \brief Traverse a number of trees to get prediction results * - * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skipp if no) + * \param isResValidPtr Result pointer is valid yes/no (write result to the pointer if yes, skip if no) * \param reuseBuffer Re-use buffer yes/no (will fill buffer with zero if yes, shift buff pointer if no) * \param nTrees Number of trees contributing to prediction * \param nClasses Number of data classes From 2c7b59a9bb3a5a7a4789b99fb308ab0a70e1d789 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Mon, 23 Oct 2023 05:28:54 -0700 Subject: [PATCH 48/50] consistently use size_t for node indexing; unsigned -> uint32_t --- .../src/algorithms/dtrees/gbt/treeshap.cpp | 18 ++++----- cpp/daal/src/algorithms/dtrees/gbt/treeshap.h | 40 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp index 7aadccef2b9..07d8a0df628 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.cpp @@ -122,7 +122,7 @@ float unwoundPathSum(const PathElement * uniquePath, size_t uniqueDepth, size_t namespace v1 { -void extendPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, float zeroFraction, +void extendPath(PathElement * uniquePath, float * partialWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPartialWeights, float zeroFraction, float oneFraction, int featureIndex) { uniquePath[uniqueDepth].featureIndex = featureIndex; @@ -147,7 +147,7 @@ void extendPath(PathElement * uniquePath, float * partialWeights, unsigned uniqu } } -void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, unsigned pathIndex) +void unwindPath(PathElement * uniquePath, float * partialWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPartialWeights, uint32_t pathIndex) { const float oneFraction = uniquePath[pathIndex].oneFraction; const float zeroFraction = uniquePath[pathIndex].zeroFraction; @@ -156,7 +156,7 @@ void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqu if (oneFraction != 0) { // shrink partialWeights iff the feature satisfies the threshold - for (unsigned i = uniqueDepthPartialWeights - 1;; --i) + for (uint32_t i = uniqueDepthPartialWeights - 1;; --i) { const float tmp = partialWeights[i]; partialWeights[i] = nextOnePortion * (uniqueDepth + 1) / static_cast(i + 1); @@ -166,13 +166,13 @@ void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqu } else { - for (unsigned i = 0; i <= uniqueDepthPartialWeights; ++i) + for (uint32_t i = 0; i <= uniqueDepthPartialWeights; ++i) { partialWeights[i] *= (uniqueDepth + 1) / static_cast(uniqueDepth - i); } } - for (unsigned i = pathIndex; i < uniqueDepth; ++i) + for (uint32_t i = pathIndex; i < uniqueDepth; ++i) { uniquePath[i].featureIndex = uniquePath[i + 1].featureIndex; uniquePath[i].zeroFraction = uniquePath[i + 1].zeroFraction; @@ -182,8 +182,8 @@ void unwindPath(PathElement * uniquePath, float * partialWeights, unsigned uniqu // determine what the total permuation weight would be if // we unwound a previous extension in the decision path (for feature satisfying the threshold) -float unwoundPathSum(const PathElement * uniquePath, const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights, - unsigned pathIndex) +float unwoundPathSum(const PathElement * uniquePath, const float * partialWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPartialWeights, + uint32_t pathIndex) { float total = 0; const float zeroFraction = uniquePath[pathIndex].zeroFraction; @@ -197,12 +197,12 @@ float unwoundPathSum(const PathElement * uniquePath, const float * partialWeight return total * (uniqueDepth + 1); } -float unwoundPathSumZero(const float * partialWeights, unsigned uniqueDepth, unsigned uniqueDepthPartialWeights) +float unwoundPathSumZero(const float * partialWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPartialWeights) { float total = 0; if (uniqueDepth > uniqueDepthPartialWeights) { - for (unsigned i = 0; i <= uniqueDepthPartialWeights; ++i) + for (uint32_t i = 0; i <= uniqueDepthPartialWeights; ++i) { total += partialWeights[i] / static_cast(uniqueDepth - i); } diff --git a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h index f4c3ceba41d..6a701cd97d8 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h +++ b/cpp/daal/src/algorithms/dtrees/gbt/treeshap.h @@ -143,8 +143,8 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith const algorithmFPType dataValue = x[splitIndex]; gbt::prediction::internal::PredictDispatcher dispatcher; - FeatureIndexType hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); - const FeatureIndexType coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); + size_t hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); + const size_t coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); const float w = nodeCoverValues[nodeIndex]; DAAL_ASSERT(w > 0); @@ -233,11 +233,11 @@ inline services::Status treeShap(const gbt::internal::GbtDecisionTree * tree, co namespace v1 { -void extendPath(PathElement * uniquePath, float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights, float zeroFraction, float oneFraction, +void extendPath(PathElement * uniquePath, float * pWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPWeights, float zeroFraction, float oneFraction, int featureIndex); -void unwindPath(PathElement * uniquePath, float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights, unsigned pathIndex); -float unwoundPathSum(const PathElement * uniquePath, const float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights, unsigned pathIndex); -float unwoundPathSumZero(const float * pWeights, unsigned uniqueDepth, unsigned uniqueDepthPWeights); +void unwindPath(PathElement * uniquePath, float * pWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPWeights, uint32_t pathIndex); +float unwoundPathSum(const PathElement * uniquePath, const float * pWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPWeights, uint32_t pathIndex); +float unwoundPathSumZero(const float * pWeights, uint32_t uniqueDepth, uint32_t uniqueDepthPWeights); /** * Recursive Fast TreeSHAP version 1 @@ -269,7 +269,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith copyStatus = daal::services::internal::daal_memcpy_s(pWeights, nBytes, parentPWeights, nBytes); DAAL_ASSERT(copyStatus == 0); - if (condition == 0 || conditionFeature != static_cast(parentFeatureIndex)) + if (condition == 0 || conditionFeature != static_cast(parentFeatureIndex)) { extendPath(uniquePath, pWeights, uniqueDepth, uniqueDepthPWeights, parentZeroFraction, parentOneFraction, parentFeatureIndex); // update pWeightsResidual if the feature of the last split does not satisfy the threshold @@ -284,10 +284,10 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith if (isLeaf) { - const unsigned valuesOffset = nodeIndex * numOutputs; - unsigned valuesNonZeroInd = 0; - unsigned valuesNonZeroCount = 0; - for (unsigned j = 0; j < numOutputs; ++j) + const size_t valuesOffset = nodeIndex * numOutputs; + uint32_t valuesNonZeroInd = 0; + uint32_t valuesNonZeroCount = 0; + for (uint32_t j = 0; j < numOutputs; ++j) { if (splitValues[valuesOffset + j] != 0) { @@ -299,10 +299,10 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith const algorithmFPType wZero = unwoundPathSumZero(pWeights, uniqueDepth, uniqueDepthPWeights); const algorithmFPType scaleZero = -wZero * pWeightsResidual * conditionFraction; algorithmFPType scale; - for (unsigned i = 1; i <= uniqueDepth; ++i) + for (uint32_t i = 1; i <= uniqueDepth; ++i) { const PathElement & el = uniquePath[i]; - const unsigned phiOffset = el.featureIndex * numOutputs; + const uint32_t phiOffset = el.featureIndex * numOutputs; // update contributions to SHAP values for features satisfying the thresholds and not satisfying the thresholds separately if (el.oneFraction != 0) { @@ -319,7 +319,7 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith } else { - for (unsigned j = 0; j < numOutputs; ++j) + for (uint32_t j = 0; j < numOutputs; ++j) { phi[phiOffset + j] += scale * splitValues[valuesOffset + j]; } @@ -329,12 +329,12 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith return; } - const unsigned splitIndex = fIndexes[nodeIndex]; - const algorithmFPType dataValue = x[splitIndex]; + const FeatureIndexType splitIndex = fIndexes[nodeIndex]; + const algorithmFPType dataValue = x[splitIndex]; gbt::prediction::internal::PredictDispatcher dispatcher; - FeatureIndexType hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); - const FeatureIndexType coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); + size_t hotIndex = updateIndex(nodeIndex, dataValue, splitValues, defaultLeft, *featureHelper, splitIndex, dispatcher); + const size_t coldIndex = 2 * nodeIndex + (hotIndex == (2 * nodeIndex)); const algorithmFPType w = nodeCoverValues[nodeIndex]; const algorithmFPType hotZeroFraction = nodeCoverValues[hotIndex] / w; @@ -344,10 +344,10 @@ inline void treeShap(const gbt::internal::GbtDecisionTree * tree, const algorith // see if we have already split on this feature, // if so we undo that split so we can redo it for this node - unsigned pathIndex = 0; + uint32_t pathIndex = 0; for (; pathIndex <= uniqueDepth; ++pathIndex) { - if (static_cast(uniquePath[pathIndex].featureIndex) == splitIndex) break; + if (uniquePath[pathIndex].featureIndex == splitIndex) break; } if (pathIndex != uniqueDepth + 1) { From 5f19b8c4a36c957562bbb3827e73ab9f00469bed Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 26 Oct 2023 04:05:23 -0700 Subject: [PATCH 49/50] fix: don't include test in release --- cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD index ab76f1bb4b3..8e85e233059 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/BUILD @@ -4,7 +4,9 @@ load("@onedal//dev/bazel:dal.bzl", "dal_test_suite") daal_module( name = "kernel", - auto = True, + auto = False, + hdrs = glob(["**/*.h", "**/*.i", "**/*.cl"]), + srcs = glob(["*.cpp"]), deps = [ "@onedal//cpp/daal:core", "@onedal//cpp/daal:sycl", @@ -15,7 +17,6 @@ daal_module( dal_test_suite( name = "tests", - framework = "catch2", compile_as = [ "c++" ], private = True, srcs = glob([ From a0c2c7b8a6f38cde8b834d58a31655ed7675e500 Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Thu, 26 Oct 2023 05:08:18 -0700 Subject: [PATCH 50/50] fix multiline comments --- .../gbt_regression_model_builder_unit.cpp | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp index a9463661891..8228a29cace 100644 --- a/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp +++ b/cpp/daal/src/algorithms/dtrees/gbt/regression/test/gbt_regression_model_builder_unit.cpp @@ -21,10 +21,12 @@ namespace daal::algorithms::gbt::internal { GbtDecisionTree prepareThreeNodeTree() { - // create a tree with 3 nodes, 1 root (split), 2 leaves - // ROOT (level 1) - // / \ - // L L (level 2) + /** + * create a tree with 3 nodes, 1 root (split), 2 leaves + * ROOT (level 1) + * / \ + * L L (level 2) + */ GbtDecisionTree tree = GbtDecisionTree(3, 2); ModelFPType * splitPoints = tree.getSplitPoints(); @@ -52,13 +54,14 @@ GbtDecisionTree prepareThreeNodeTree() GbtDecisionTree prepareFiveNodeTree() { - // create a tree with 5 nodes - // ROOT (1) (level 1) - // / \ - // L (2) S (3) (level 2) - // / \ - // L (6) L (7) (level 3) - // (note: on level 3, nodes 4 and 5 do not exist and will be created as "dummy leaf") + /** create a tree with 5 nodes + * ROOT (1) (level 1) + * / \ + * L (2) S (3) (level 2) + * / \ + * L (6) L (7) (level 3) + * (note: on level 3, nodes 4 and 5 do not exist and will be created as "dummy leaf") + */ GbtDecisionTree tree = GbtDecisionTree(5, 3); ModelFPType * splitPoints = tree.getSplitPoints();