From 81da34ec81f42dea99866921706c4fcff3ed1607 Mon Sep 17 00:00:00 2001 From: Zhen Li Date: Thu, 2 Sep 2021 10:19:24 -0500 Subject: [PATCH 1/4] remove lstrip(b'binf') from XGBTreeModelLoader --- shap/explainers/_tree.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shap/explainers/_tree.py b/shap/explainers/_tree.py index d13efee99..2bc2c279d 100644 --- a/shap/explainers/_tree.py +++ b/shap/explainers/_tree.py @@ -1414,8 +1414,8 @@ class XGBTreeModelLoader(object): """ def __init__(self, xgb_model): # new in XGBoost 1.1, 'binf' is appended to the buffer - self.buf = xgb_model.save_raw().lstrip(b'binf') - self.pos = 0 + self.buf = xgb_model.save_raw() + self.pos = 4 # load the model parameters self.base_score = self.read('f') From 91e3b8c6a6d6f13579233facd6f036eb571d729d Mon Sep 17 00:00:00 2001 From: Zhen Li Date: Thu, 2 Sep 2021 16:54:34 -0500 Subject: [PATCH 2/4] Update _tree.py --- shap/explainers/_tree.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shap/explainers/_tree.py b/shap/explainers/_tree.py index 2bc2c279d..1560f6973 100644 --- a/shap/explainers/_tree.py +++ b/shap/explainers/_tree.py @@ -1415,7 +1415,10 @@ class XGBTreeModelLoader(object): def __init__(self, xgb_model): # new in XGBoost 1.1, 'binf' is appended to the buffer self.buf = xgb_model.save_raw() - self.pos = 4 + start_buf = self.buf[:4] + if start_buf == bytearray(b'binf'): + self.buf = self.buf[4:] + self.pos = 0 # load the model parameters self.base_score = self.read('f') From c183e051eb30678894c0d40a53cae32da33fa74b Mon Sep 17 00:00:00 2001 From: Zhen Li Date: Thu, 30 Dec 2021 17:54:47 -0600 Subject: [PATCH 3/4] Update code to be more succinctly --- shap/explainers/_tree.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/shap/explainers/_tree.py b/shap/explainers/_tree.py index 1560f6973..66ae07a4c 100644 --- a/shap/explainers/_tree.py +++ b/shap/explainers/_tree.py @@ -1415,8 +1415,7 @@ class XGBTreeModelLoader(object): def __init__(self, xgb_model): # new in XGBoost 1.1, 'binf' is appended to the buffer self.buf = xgb_model.save_raw() - start_buf = self.buf[:4] - if start_buf == bytearray(b'binf'): + if self.buf.startswith(b'binf'): self.buf = self.buf[4:] self.pos = 0 From f1318e3d337e467062da503239cd7ae55643137b Mon Sep 17 00:00:00 2001 From: lrjball Date: Mon, 28 Feb 2022 21:34:06 +0000 Subject: [PATCH 4/4] Added test for buffer strip update --- tests/explainers/test_tree.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/explainers/test_tree.py b/tests/explainers/test_tree.py index 1f0dcb26d..7ee711447 100644 --- a/tests/explainers/test_tree.py +++ b/tests/explainers/test_tree.py @@ -1110,3 +1110,20 @@ def objective_function(x): result_et.models[-1].predict(et_df)) assert np.allclose(shap_values_rf.sum(1) + explainer_rf.expected_value, result_rf.models[-1].predict(rf_df)) + + +def test_xgboost_buffer_strip(): + # test to make sure bug #1864 doesn't get reintroduced + xgboost = pytest.importorskip("xgboost") + X = np.array([[1, 2, 3, 4, 5], [3, 3, 3, 2, 4]]) + y = np.array([1, 0]) + # specific values (e.g. 1.3) caused the bug previously + model = xgboost.XGBRegressor(base_score=1.3) + model.fit(X, y, eval_metric="rmse") + # previous bug did .lstrip('binf'), so would have incorrectly handled + # buffer starting with binff + assert model.get_booster().save_raw().startswith(b"binff") + + # after this fix, this line should not error + explainer = shap.TreeExplainer(model) + assert isinstance(explainer, shap.explainers.Tree)