[Breaking] Don't drop trees during DART prediction by default (#5115)
* Simplify DropTrees calling logic * Add `training` parameter for prediction method. * [Breaking]: Add `training` to C API. * Change for R and Python custom objective. * Correct comment. Co-authored-by: Philip Hyunsu Cho <chohyu01@cs.washington.edu> Co-authored-by: Jiaming Yuan <jm.yuan@outlook.com>
This commit is contained in:
@@ -196,7 +196,8 @@ def ctypes2numpy(cptr, length, dtype):
|
||||
np.uint32: ctypes.c_uint,
|
||||
}
|
||||
if dtype not in NUMPY_TO_CTYPES_MAPPING:
|
||||
raise RuntimeError('Supported types: {}'.format(NUMPY_TO_CTYPES_MAPPING.keys()))
|
||||
raise RuntimeError('Supported types: {}'.format(
|
||||
NUMPY_TO_CTYPES_MAPPING.keys()))
|
||||
ctype = NUMPY_TO_CTYPES_MAPPING[dtype]
|
||||
if not isinstance(cptr, ctypes.POINTER(ctype)):
|
||||
raise RuntimeError('expected {} pointer'.format(ctype))
|
||||
@@ -1275,14 +1276,16 @@ class Booster(object):
|
||||
|
||||
"""
|
||||
if not isinstance(dtrain, DMatrix):
|
||||
raise TypeError('invalid training matrix: {}'.format(type(dtrain).__name__))
|
||||
raise TypeError('invalid training matrix: {}'.format(
|
||||
type(dtrain).__name__))
|
||||
self._validate_features(dtrain)
|
||||
|
||||
if fobj is None:
|
||||
_check_call(_LIB.XGBoosterUpdateOneIter(self.handle, ctypes.c_int(iteration),
|
||||
_check_call(_LIB.XGBoosterUpdateOneIter(self.handle,
|
||||
ctypes.c_int(iteration),
|
||||
dtrain.handle))
|
||||
else:
|
||||
pred = self.predict(dtrain)
|
||||
pred = self.predict(dtrain, training=True)
|
||||
grad, hess = fobj(pred, dtrain)
|
||||
self.boost(dtrain, grad, hess)
|
||||
|
||||
@@ -1332,22 +1335,25 @@ class Booster(object):
|
||||
"""
|
||||
for d in evals:
|
||||
if not isinstance(d[0], DMatrix):
|
||||
raise TypeError('expected DMatrix, got {}'.format(type(d[0]).__name__))
|
||||
raise TypeError('expected DMatrix, got {}'.format(
|
||||
type(d[0]).__name__))
|
||||
if not isinstance(d[1], STRING_TYPES):
|
||||
raise TypeError('expected string, got {}'.format(type(d[1]).__name__))
|
||||
raise TypeError('expected string, got {}'.format(
|
||||
type(d[1]).__name__))
|
||||
self._validate_features(d[0])
|
||||
|
||||
dmats = c_array(ctypes.c_void_p, [d[0].handle for d in evals])
|
||||
evnames = c_array(ctypes.c_char_p, [c_str(d[1]) for d in evals])
|
||||
msg = ctypes.c_char_p()
|
||||
_check_call(_LIB.XGBoosterEvalOneIter(self.handle, ctypes.c_int(iteration),
|
||||
_check_call(_LIB.XGBoosterEvalOneIter(self.handle,
|
||||
ctypes.c_int(iteration),
|
||||
dmats, evnames,
|
||||
c_bst_ulong(len(evals)),
|
||||
ctypes.byref(msg)))
|
||||
res = msg.value.decode()
|
||||
if feval is not None:
|
||||
for dmat, evname in evals:
|
||||
feval_ret = feval(self.predict(dmat), dmat)
|
||||
feval_ret = feval(self.predict(dmat, training=False), dmat)
|
||||
if isinstance(feval_ret, list):
|
||||
for name, val in feval_ret:
|
||||
res += '\t%s-%s:%f' % (evname, name, val)
|
||||
@@ -1378,27 +1384,24 @@ class Booster(object):
|
||||
self._validate_features(data)
|
||||
return self.eval_set([(data, name)], iteration)
|
||||
|
||||
def predict(self, data, output_margin=False, ntree_limit=0, pred_leaf=False,
|
||||
pred_contribs=False, approx_contribs=False, pred_interactions=False,
|
||||
validate_features=True):
|
||||
def predict(self,
|
||||
data,
|
||||
output_margin=False,
|
||||
ntree_limit=0,
|
||||
pred_leaf=False,
|
||||
pred_contribs=False,
|
||||
approx_contribs=False,
|
||||
pred_interactions=False,
|
||||
validate_features=True,
|
||||
training=False):
|
||||
"""Predict with data.
|
||||
|
||||
.. note:: This function is not thread safe.
|
||||
|
||||
For each booster object, predict can only be called from one thread.
|
||||
If you want to run prediction using multiple thread, call ``bst.copy()`` to make copies
|
||||
of model object and then call ``predict()``.
|
||||
|
||||
.. note:: Using ``predict()`` with DART booster
|
||||
|
||||
If the booster object is DART type, ``predict()`` will perform dropouts, i.e. only
|
||||
some of the trees will be evaluated. This will produce incorrect results if ``data`` is
|
||||
not the training data. To obtain correct results on test sets, set ``ntree_limit`` to
|
||||
a nonzero value, e.g.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
preds = bst.predict(dtest, ntree_limit=num_round)
|
||||
If you want to run prediction using multiple thread, call
|
||||
``bst.copy()`` to make copies of model object and then call
|
||||
``predict()``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -1409,38 +1412,53 @@ class Booster(object):
|
||||
Whether to output the raw untransformed margin value.
|
||||
|
||||
ntree_limit : int
|
||||
Limit number of trees in the prediction; defaults to 0 (use all trees).
|
||||
Limit number of trees in the prediction; defaults to 0 (use all
|
||||
trees).
|
||||
|
||||
pred_leaf : bool
|
||||
When this option is on, the output will be a matrix of (nsample, ntrees)
|
||||
with each record indicating the predicted leaf index of each sample in each tree.
|
||||
Note that the leaf index of a tree is unique per tree, so you may find leaf 1
|
||||
in both tree 1 and tree 0.
|
||||
When this option is on, the output will be a matrix of (nsample,
|
||||
ntrees) with each record indicating the predicted leaf index of
|
||||
each sample in each tree. Note that the leaf index of a tree is
|
||||
unique per tree, so you may find leaf 1 in both tree 1 and tree 0.
|
||||
|
||||
pred_contribs : bool
|
||||
When this is True the output will be a matrix of size (nsample, nfeats + 1)
|
||||
with each record indicating the feature contributions (SHAP values) for that
|
||||
prediction. The sum of all feature contributions is equal to the raw untransformed
|
||||
margin value of the prediction. Note the final column is the bias term.
|
||||
When this is True the output will be a matrix of size (nsample,
|
||||
nfeats + 1) with each record indicating the feature contributions
|
||||
(SHAP values) for that prediction. The sum of all feature
|
||||
contributions is equal to the raw untransformed margin value of the
|
||||
prediction. Note the final column is the bias term.
|
||||
|
||||
approx_contribs : bool
|
||||
Approximate the contributions of each feature
|
||||
|
||||
pred_interactions : bool
|
||||
When this is True the output will be a matrix of size (nsample, nfeats + 1, nfeats + 1)
|
||||
indicating the SHAP interaction values for each pair of features. The sum of each
|
||||
row (or column) of the interaction values equals the corresponding SHAP value (from
|
||||
pred_contribs), and the sum of the entire matrix equals the raw untransformed margin
|
||||
value of the prediction. Note the last row and column correspond to the bias term.
|
||||
When this is True the output will be a matrix of size (nsample,
|
||||
nfeats + 1, nfeats + 1) indicating the SHAP interaction values for
|
||||
each pair of features. The sum of each row (or column) of the
|
||||
interaction values equals the corresponding SHAP value (from
|
||||
pred_contribs), and the sum of the entire matrix equals the raw
|
||||
untransformed margin value of the prediction. Note the last row and
|
||||
column correspond to the bias term.
|
||||
|
||||
validate_features : bool
|
||||
When this is True, validate that the Booster's and data's
|
||||
feature_names are identical. Otherwise, it is assumed that the
|
||||
feature_names are the same.
|
||||
|
||||
training : bool
|
||||
Whether the prediction value is used for training. This can effect
|
||||
`dart` booster, which performs dropouts during training iterations.
|
||||
|
||||
.. note:: Using ``predict()`` with DART booster
|
||||
|
||||
If the booster object is DART type, ``predict()`` will not perform
|
||||
dropouts, i.e. all the trees will be evaluated. If you want to
|
||||
obtain result with dropouts, provide `training=True`.
|
||||
|
||||
Returns
|
||||
-------
|
||||
prediction : numpy array
|
||||
|
||||
"""
|
||||
option_mask = 0x00
|
||||
if output_margin:
|
||||
@@ -1466,6 +1484,7 @@ class Booster(object):
|
||||
_check_call(_LIB.XGBoosterPredict(self.handle, data.handle,
|
||||
ctypes.c_int(option_mask),
|
||||
ctypes.c_uint(ntree_limit),
|
||||
ctypes.c_int(training),
|
||||
ctypes.byref(length),
|
||||
ctypes.byref(preds)))
|
||||
preds = ctypes2numpy(preds, length.value, np.float32)
|
||||
@@ -1476,11 +1495,16 @@ class Booster(object):
|
||||
chunk_size = int(preds.size / nrow)
|
||||
|
||||
if pred_interactions:
|
||||
ngroup = int(chunk_size / ((data.num_col() + 1) * (data.num_col() + 1)))
|
||||
ngroup = int(chunk_size / ((data.num_col() + 1) *
|
||||
(data.num_col() + 1)))
|
||||
if ngroup == 1:
|
||||
preds = preds.reshape(nrow, data.num_col() + 1, data.num_col() + 1)
|
||||
preds = preds.reshape(nrow,
|
||||
data.num_col() + 1,
|
||||
data.num_col() + 1)
|
||||
else:
|
||||
preds = preds.reshape(nrow, ngroup, data.num_col() + 1, data.num_col() + 1)
|
||||
preds = preds.reshape(nrow, ngroup,
|
||||
data.num_col() + 1,
|
||||
data.num_col() + 1)
|
||||
elif pred_contribs:
|
||||
ngroup = int(chunk_size / (data.num_col() + 1))
|
||||
if ngroup == 1:
|
||||
|
||||
Reference in New Issue
Block a user