release(camera-ai): publish 0.4.12 catalog portability fix

This commit is contained in:
KINDNICK
2026-08-16 01:05:07 +09:00
parent 8e5e2e8ad0
commit b65e54a7e8
211 changed files with 45990 additions and 45913 deletions
@@ -1,262 +1,262 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.stdlib cimport free
from libc.string cimport memset
import numpy as np
from scipy.sparse import issparse
from ..utils._typedefs cimport float32_t, float64_t, intp_t, int32_t, uint8_t
# Note: _tree uses cimport numpy, cnp.import_array, so we need to include
# numpy headers in the build configuration of this extension
from ..tree._tree cimport Node
from ..tree._tree cimport Tree
from ..tree._utils cimport safe_realloc
# no namespace lookup for numpy dtype and array creation
from numpy import zeros as np_zeros
# constant to mark tree leafs
cdef intp_t TREE_LEAF = -1
cdef void _predict_regression_tree_inplace_fast_dense(
const float32_t[:, ::1] X,
Node* root_node,
double *value,
double scale,
Py_ssize_t k,
float64_t[:, :] out
) noexcept nogil:
"""Predicts output for regression tree and stores it in ``out[i, k]``.
This function operates directly on the data arrays of the tree
data structures. This is 5x faster than the variant above because
it allows us to avoid buffer validation.
The function assumes that the ndarray that wraps ``X`` is
c-continuous.
Parameters
----------
X : float32_t 2d memory view
The memory view on the data ndarray of the input ``X``.
Assumes that the array is c-continuous.
root_node : tree Node pointer
Pointer to the main node array of the :class:``sklearn.tree.Tree``.
value : np.float64_t pointer
The pointer to the data array of the ``value`` array attribute
of the :class:``sklearn.tree.Tree``.
scale : double
A constant to scale the predictions.
k : int
The index of the tree output to be predicted. Must satisfy
0 <= ``k`` < ``K``.
out : memory view on array of type np.float64_t
The data array where the predictions are stored.
``out`` is assumed to be a two-dimensional array of
shape ``(n_samples, K)``.
"""
cdef intp_t n_samples = X.shape[0]
cdef Py_ssize_t i
cdef Node *node
for i in range(n_samples):
node = root_node
# While node not a leaf
while node.left_child != TREE_LEAF:
if X[i, node.feature] <= node.threshold:
node = root_node + node.left_child
else:
node = root_node + node.right_child
out[i, k] += scale * value[node - root_node]
def _predict_regression_tree_stages_sparse(
object[:, :] estimators,
object X,
double scale,
float64_t[:, :] out
):
"""Predicts output for regression tree inplace and adds scaled value to ``out[i, k]``.
The function assumes that the ndarray that wraps ``X`` is csr_matrix.
"""
cdef const float32_t[::1] X_data = X.data
cdef const int32_t[::1] X_indices = X.indices
cdef const int32_t[::1] X_indptr = X.indptr
cdef intp_t n_samples = X.shape[0]
cdef intp_t n_features = X.shape[1]
cdef intp_t n_stages = estimators.shape[0]
cdef intp_t n_outputs = estimators.shape[1]
# Indices and temporary variables
cdef intp_t sample_i
cdef intp_t feature_i
cdef intp_t stage_i
cdef intp_t output_i
cdef Node *root_node = NULL
cdef Node *node = NULL
cdef double *value = NULL
cdef Tree tree
cdef Node** nodes = NULL
cdef double** values = NULL
safe_realloc(&nodes, n_stages * n_outputs)
safe_realloc(&values, n_stages * n_outputs)
for stage_i in range(n_stages):
for output_i in range(n_outputs):
tree = estimators[stage_i, output_i].tree_
nodes[stage_i * n_outputs + output_i] = tree.nodes
values[stage_i * n_outputs + output_i] = tree.value
# Initialize auxiliary data-structure
cdef float32_t feature_value = 0.
cdef float32_t* X_sample = NULL
# feature_to_sample as a data structure records the last seen sample
# for each feature; functionally, it is an efficient way to identify
# which features are nonzero in the present sample.
cdef intp_t* feature_to_sample = NULL
safe_realloc(&X_sample, n_features)
safe_realloc(&feature_to_sample, n_features)
memset(feature_to_sample, -1, n_features * sizeof(intp_t))
# Cycle through all samples
for sample_i in range(n_samples):
for feature_i in range(X_indptr[sample_i], X_indptr[sample_i + 1]):
feature_to_sample[X_indices[feature_i]] = sample_i
X_sample[X_indices[feature_i]] = X_data[feature_i]
# Cycle through all stages
for stage_i in range(n_stages):
# Cycle through all trees
for output_i in range(n_outputs):
root_node = nodes[stage_i * n_outputs + output_i]
value = values[stage_i * n_outputs + output_i]
node = root_node
# While node not a leaf
while node.left_child != TREE_LEAF:
# ... and node.right_child != TREE_LEAF:
if feature_to_sample[node.feature] == sample_i:
feature_value = X_sample[node.feature]
else:
feature_value = 0.
if feature_value <= node.threshold:
node = root_node + node.left_child
else:
node = root_node + node.right_child
out[sample_i, output_i] += scale * value[node - root_node]
# Free auxiliary arrays
free(X_sample)
free(feature_to_sample)
free(nodes)
free(values)
def predict_stages(
object[:, :] estimators,
object X,
double scale,
float64_t[:, :] out
):
"""Add predictions of ``estimators`` to ``out``.
Each estimator is scaled by ``scale`` before its prediction
is added to ``out``.
"""
cdef Py_ssize_t i
cdef Py_ssize_t k
cdef Py_ssize_t n_estimators = estimators.shape[0]
cdef Py_ssize_t K = estimators.shape[1]
cdef Tree tree
if issparse(X):
if X.format != 'csr':
raise ValueError("When X is a sparse matrix, a CSR format is"
" expected, got {!r}".format(type(X)))
_predict_regression_tree_stages_sparse(
estimators=estimators, X=X, scale=scale, out=out
)
else:
if not isinstance(X, np.ndarray) or np.isfortran(X):
raise ValueError(f"X should be C-ordered np.ndarray, got {type(X)}")
for i in range(n_estimators):
for k in range(K):
tree = estimators[i, k].tree_
# avoid buffer validation by casting to ndarray
# and get data pointer
# need brackets because of casting operator priority
_predict_regression_tree_inplace_fast_dense(
X=X,
root_node=tree.nodes,
value=tree.value,
scale=scale,
k=k,
out=out
)
# out[:, k] += scale * tree.predict(X).ravel()
def predict_stage(
object[:, :] estimators,
int stage,
object X,
double scale,
float64_t[:, :] out
):
"""Add predictions of ``estimators[stage]`` to ``out``.
Each estimator in the stage is scaled by ``scale`` before
its prediction is added to ``out``.
"""
return predict_stages(
estimators=estimators[stage:stage + 1], X=X, scale=scale, out=out
)
def _random_sample_mask(
intp_t n_total_samples,
intp_t n_total_in_bag,
random_state
):
"""Create a random sample mask where ``n_total_in_bag`` elements are set.
Parameters
----------
n_total_samples : int
The length of the resulting mask.
n_total_in_bag : int
The number of elements in the sample mask which are set to 1.
random_state : RandomState
A numpy ``RandomState`` object.
Returns
-------
sample_mask : np.ndarray, shape=[n_total_samples]
An ndarray where ``n_total_in_bag`` elements are set to ``True``
the others are ``False``.
"""
cdef float64_t[::1] rand = random_state.uniform(size=n_total_samples)
cdef uint8_t[::1] sample_mask = np_zeros((n_total_samples,), dtype=bool)
cdef intp_t n_bagged = 0
cdef intp_t i = 0
for i in range(n_total_samples):
if rand[i] * (n_total_samples - i) < (n_total_in_bag - n_bagged):
sample_mask[i] = 1
n_bagged += 1
return sample_mask.base
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from libc.stdlib cimport free
from libc.string cimport memset
import numpy as np
from scipy.sparse import issparse
from ..utils._typedefs cimport float32_t, float64_t, intp_t, int32_t, uint8_t
# Note: _tree uses cimport numpy, cnp.import_array, so we need to include
# numpy headers in the build configuration of this extension
from ..tree._tree cimport Node
from ..tree._tree cimport Tree
from ..tree._utils cimport safe_realloc
# no namespace lookup for numpy dtype and array creation
from numpy import zeros as np_zeros
# constant to mark tree leafs
cdef intp_t TREE_LEAF = -1
cdef void _predict_regression_tree_inplace_fast_dense(
const float32_t[:, ::1] X,
Node* root_node,
double *value,
double scale,
Py_ssize_t k,
float64_t[:, :] out
) noexcept nogil:
"""Predicts output for regression tree and stores it in ``out[i, k]``.
This function operates directly on the data arrays of the tree
data structures. This is 5x faster than the variant above because
it allows us to avoid buffer validation.
The function assumes that the ndarray that wraps ``X`` is
c-continuous.
Parameters
----------
X : float32_t 2d memory view
The memory view on the data ndarray of the input ``X``.
Assumes that the array is c-continuous.
root_node : tree Node pointer
Pointer to the main node array of the :class:``sklearn.tree.Tree``.
value : np.float64_t pointer
The pointer to the data array of the ``value`` array attribute
of the :class:``sklearn.tree.Tree``.
scale : double
A constant to scale the predictions.
k : int
The index of the tree output to be predicted. Must satisfy
0 <= ``k`` < ``K``.
out : memory view on array of type np.float64_t
The data array where the predictions are stored.
``out`` is assumed to be a two-dimensional array of
shape ``(n_samples, K)``.
"""
cdef intp_t n_samples = X.shape[0]
cdef Py_ssize_t i
cdef Node *node
for i in range(n_samples):
node = root_node
# While node not a leaf
while node.left_child != TREE_LEAF:
if X[i, node.feature] <= node.threshold:
node = root_node + node.left_child
else:
node = root_node + node.right_child
out[i, k] += scale * value[node - root_node]
def _predict_regression_tree_stages_sparse(
object[:, :] estimators,
object X,
double scale,
float64_t[:, :] out
):
"""Predicts output for regression tree inplace and adds scaled value to ``out[i, k]``.
The function assumes that the ndarray that wraps ``X`` is csr_matrix.
"""
cdef const float32_t[::1] X_data = X.data
cdef const int32_t[::1] X_indices = X.indices
cdef const int32_t[::1] X_indptr = X.indptr
cdef intp_t n_samples = X.shape[0]
cdef intp_t n_features = X.shape[1]
cdef intp_t n_stages = estimators.shape[0]
cdef intp_t n_outputs = estimators.shape[1]
# Indices and temporary variables
cdef intp_t sample_i
cdef intp_t feature_i
cdef intp_t stage_i
cdef intp_t output_i
cdef Node *root_node = NULL
cdef Node *node = NULL
cdef double *value = NULL
cdef Tree tree
cdef Node** nodes = NULL
cdef double** values = NULL
safe_realloc(&nodes, n_stages * n_outputs)
safe_realloc(&values, n_stages * n_outputs)
for stage_i in range(n_stages):
for output_i in range(n_outputs):
tree = estimators[stage_i, output_i].tree_
nodes[stage_i * n_outputs + output_i] = tree.nodes
values[stage_i * n_outputs + output_i] = tree.value
# Initialize auxiliary data-structure
cdef float32_t feature_value = 0.
cdef float32_t* X_sample = NULL
# feature_to_sample as a data structure records the last seen sample
# for each feature; functionally, it is an efficient way to identify
# which features are nonzero in the present sample.
cdef intp_t* feature_to_sample = NULL
safe_realloc(&X_sample, n_features)
safe_realloc(&feature_to_sample, n_features)
memset(feature_to_sample, -1, n_features * sizeof(intp_t))
# Cycle through all samples
for sample_i in range(n_samples):
for feature_i in range(X_indptr[sample_i], X_indptr[sample_i + 1]):
feature_to_sample[X_indices[feature_i]] = sample_i
X_sample[X_indices[feature_i]] = X_data[feature_i]
# Cycle through all stages
for stage_i in range(n_stages):
# Cycle through all trees
for output_i in range(n_outputs):
root_node = nodes[stage_i * n_outputs + output_i]
value = values[stage_i * n_outputs + output_i]
node = root_node
# While node not a leaf
while node.left_child != TREE_LEAF:
# ... and node.right_child != TREE_LEAF:
if feature_to_sample[node.feature] == sample_i:
feature_value = X_sample[node.feature]
else:
feature_value = 0.
if feature_value <= node.threshold:
node = root_node + node.left_child
else:
node = root_node + node.right_child
out[sample_i, output_i] += scale * value[node - root_node]
# Free auxiliary arrays
free(X_sample)
free(feature_to_sample)
free(nodes)
free(values)
def predict_stages(
object[:, :] estimators,
object X,
double scale,
float64_t[:, :] out
):
"""Add predictions of ``estimators`` to ``out``.
Each estimator is scaled by ``scale`` before its prediction
is added to ``out``.
"""
cdef Py_ssize_t i
cdef Py_ssize_t k
cdef Py_ssize_t n_estimators = estimators.shape[0]
cdef Py_ssize_t K = estimators.shape[1]
cdef Tree tree
if issparse(X):
if X.format != 'csr':
raise ValueError("When X is a sparse matrix, a CSR format is"
" expected, got {!r}".format(type(X)))
_predict_regression_tree_stages_sparse(
estimators=estimators, X=X, scale=scale, out=out
)
else:
if not isinstance(X, np.ndarray) or np.isfortran(X):
raise ValueError(f"X should be C-ordered np.ndarray, got {type(X)}")
for i in range(n_estimators):
for k in range(K):
tree = estimators[i, k].tree_
# avoid buffer validation by casting to ndarray
# and get data pointer
# need brackets because of casting operator priority
_predict_regression_tree_inplace_fast_dense(
X=X,
root_node=tree.nodes,
value=tree.value,
scale=scale,
k=k,
out=out
)
# out[:, k] += scale * tree.predict(X).ravel()
def predict_stage(
object[:, :] estimators,
int stage,
object X,
double scale,
float64_t[:, :] out
):
"""Add predictions of ``estimators[stage]`` to ``out``.
Each estimator in the stage is scaled by ``scale`` before
its prediction is added to ``out``.
"""
return predict_stages(
estimators=estimators[stage:stage + 1], X=X, scale=scale, out=out
)
def _random_sample_mask(
intp_t n_total_samples,
intp_t n_total_in_bag,
random_state
):
"""Create a random sample mask where ``n_total_in_bag`` elements are set.
Parameters
----------
n_total_samples : int
The length of the resulting mask.
n_total_in_bag : int
The number of elements in the sample mask which are set to 1.
random_state : RandomState
A numpy ``RandomState`` object.
Returns
-------
sample_mask : np.ndarray, shape=[n_total_samples]
An ndarray where ``n_total_in_bag`` elements are set to ``True``
the others are ``False``.
"""
cdef float64_t[::1] rand = random_state.uniform(size=n_total_samples)
cdef uint8_t[::1] sample_mask = np_zeros((n_total_samples,), dtype=bool)
cdef intp_t n_bagged = 0
cdef intp_t i = 0
for i in range(n_total_samples):
if rand[i] * (n_total_samples - i) < (n_total_in_bag - n_bagged):
sample_mask[i] = 1
n_bagged += 1
return sample_mask.base
@@ -1,85 +1,85 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython.parallel import prange
from libc.math cimport isnan
from .common cimport X_DTYPE_C, X_BINNED_DTYPE_C
from ...utils._typedefs cimport uint8_t
def _map_to_bins(const X_DTYPE_C [:, :] data,
list binning_thresholds,
const uint8_t[::1] is_categorical,
const uint8_t missing_values_bin_idx,
int n_threads,
X_BINNED_DTYPE_C [::1, :] binned):
"""Bin continuous and categorical values to discrete integer-coded levels.
A given value x is mapped into bin value i iff
thresholds[i - 1] < x <= thresholds[i]
Parameters
----------
data : ndarray, shape (n_samples, n_features)
The data to bin.
binning_thresholds : list of arrays
For each feature, stores the increasing numeric values that are
used to separate the bins.
is_categorical : ndarray of uint8_t of shape (n_features,)
Indicates categorical features.
n_threads : int
Number of OpenMP threads to use.
binned : ndarray, shape (n_samples, n_features)
Output array, must be fortran aligned.
"""
cdef:
int feature_idx
for feature_idx in range(data.shape[1]):
_map_col_to_bins(
data[:, feature_idx],
binning_thresholds[feature_idx],
is_categorical[feature_idx],
missing_values_bin_idx,
n_threads,
binned[:, feature_idx]
)
cdef void _map_col_to_bins(
const X_DTYPE_C [:] data,
const X_DTYPE_C [:] binning_thresholds,
const uint8_t is_categorical,
const uint8_t missing_values_bin_idx,
int n_threads,
X_BINNED_DTYPE_C [:] binned
):
"""Binary search to find the bin index for each value in the data."""
cdef:
int i
int left
int right
int middle
for i in prange(data.shape[0], schedule='static', nogil=True,
num_threads=n_threads):
if (
isnan(data[i]) or
# To follow LightGBM's conventions, negative values for
# categorical features are considered as missing values.
(is_categorical and data[i] < 0)
):
binned[i] = missing_values_bin_idx
else:
# for known values, use binary search
left, right = 0, binning_thresholds.shape[0]
while left < right:
# equal to (right + left - 1) // 2 but avoids overflow
middle = left + (right - left - 1) // 2
if data[i] <= binning_thresholds[middle]:
right = middle
else:
left = middle + 1
binned[i] = left
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython.parallel import prange
from libc.math cimport isnan
from .common cimport X_DTYPE_C, X_BINNED_DTYPE_C
from ...utils._typedefs cimport uint8_t
def _map_to_bins(const X_DTYPE_C [:, :] data,
list binning_thresholds,
const uint8_t[::1] is_categorical,
const uint8_t missing_values_bin_idx,
int n_threads,
X_BINNED_DTYPE_C [::1, :] binned):
"""Bin continuous and categorical values to discrete integer-coded levels.
A given value x is mapped into bin value i iff
thresholds[i - 1] < x <= thresholds[i]
Parameters
----------
data : ndarray, shape (n_samples, n_features)
The data to bin.
binning_thresholds : list of arrays
For each feature, stores the increasing numeric values that are
used to separate the bins.
is_categorical : ndarray of uint8_t of shape (n_features,)
Indicates categorical features.
n_threads : int
Number of OpenMP threads to use.
binned : ndarray, shape (n_samples, n_features)
Output array, must be fortran aligned.
"""
cdef:
int feature_idx
for feature_idx in range(data.shape[1]):
_map_col_to_bins(
data[:, feature_idx],
binning_thresholds[feature_idx],
is_categorical[feature_idx],
missing_values_bin_idx,
n_threads,
binned[:, feature_idx]
)
cdef void _map_col_to_bins(
const X_DTYPE_C [:] data,
const X_DTYPE_C [:] binning_thresholds,
const uint8_t is_categorical,
const uint8_t missing_values_bin_idx,
int n_threads,
X_BINNED_DTYPE_C [:] binned
):
"""Binary search to find the bin index for each value in the data."""
cdef:
int i
int left
int right
int middle
for i in prange(data.shape[0], schedule='static', nogil=True,
num_threads=n_threads):
if (
isnan(data[i]) or
# To follow LightGBM's conventions, negative values for
# categorical features are considered as missing values.
(is_categorical and data[i] < 0)
):
binned[i] = missing_values_bin_idx
else:
# for known values, use binary search
left, right = 0, binning_thresholds.shape[0]
while left < right:
# equal to (right + left - 1) // 2 but avoids overflow
middle = left + (right - left - 1) // 2
if data[i] <= binning_thresholds[middle]:
right = middle
else:
left = middle + 1
binned[i] = left
@@ -1,20 +1,20 @@
from .common cimport X_BINNED_DTYPE_C
from .common cimport BITSET_DTYPE_C
from .common cimport BITSET_INNER_DTYPE_C
from .common cimport X_DTYPE_C
from ...utils._typedefs cimport uint8_t
cdef void init_bitset(BITSET_DTYPE_C bitset) noexcept nogil
cdef void set_bitset(BITSET_DTYPE_C bitset, X_BINNED_DTYPE_C val) noexcept nogil
cdef uint8_t in_bitset(BITSET_DTYPE_C bitset, X_BINNED_DTYPE_C val) noexcept nogil
cpdef uint8_t in_bitset_memoryview(const BITSET_INNER_DTYPE_C[:] bitset,
X_BINNED_DTYPE_C val) noexcept nogil
cdef uint8_t in_bitset_2d_memoryview(
const BITSET_INNER_DTYPE_C[:, :] bitset,
X_BINNED_DTYPE_C val,
unsigned int row) noexcept nogil
from .common cimport X_BINNED_DTYPE_C
from .common cimport BITSET_DTYPE_C
from .common cimport BITSET_INNER_DTYPE_C
from .common cimport X_DTYPE_C
from ...utils._typedefs cimport uint8_t
cdef void init_bitset(BITSET_DTYPE_C bitset) noexcept nogil
cdef void set_bitset(BITSET_DTYPE_C bitset, X_BINNED_DTYPE_C val) noexcept nogil
cdef uint8_t in_bitset(BITSET_DTYPE_C bitset, X_BINNED_DTYPE_C val) noexcept nogil
cpdef uint8_t in_bitset_memoryview(const BITSET_INNER_DTYPE_C[:] bitset,
X_BINNED_DTYPE_C val) noexcept nogil
cdef uint8_t in_bitset_2d_memoryview(
const BITSET_INNER_DTYPE_C[:, :] bitset,
X_BINNED_DTYPE_C val,
unsigned int row) noexcept nogil
@@ -1,65 +1,65 @@
from .common cimport BITSET_INNER_DTYPE_C
from .common cimport BITSET_DTYPE_C
from .common cimport X_DTYPE_C
from .common cimport X_BINNED_DTYPE_C
from ...utils._typedefs cimport uint8_t
# A bitset is a data structure used to represent sets of integers in [0, n]. We
# use them to represent sets of features indices (e.g. features that go to the
# left child, or features that are categorical). For familiarity with bitsets
# and bitwise operations:
# https://en.wikipedia.org/wiki/Bit_array
# https://en.wikipedia.org/wiki/Bitwise_operation
cdef inline void init_bitset(BITSET_DTYPE_C bitset) noexcept nogil: # OUT
cdef:
unsigned int i
for i in range(8):
bitset[i] = 0
cdef inline void set_bitset(BITSET_DTYPE_C bitset, # OUT
X_BINNED_DTYPE_C val) noexcept nogil:
bitset[val // 32] |= (1 << (val % 32))
cdef inline uint8_t in_bitset(BITSET_DTYPE_C bitset,
X_BINNED_DTYPE_C val) noexcept nogil:
return (bitset[val // 32] >> (val % 32)) & 1
cpdef inline uint8_t in_bitset_memoryview(const BITSET_INNER_DTYPE_C[:] bitset,
X_BINNED_DTYPE_C val) noexcept nogil:
return (bitset[val // 32] >> (val % 32)) & 1
cdef inline uint8_t in_bitset_2d_memoryview(const BITSET_INNER_DTYPE_C[:, :] bitset,
X_BINNED_DTYPE_C val,
unsigned int row) noexcept nogil:
# Same as above but works on 2d memory views to avoid the creation of 1d
# memory views. See https://github.com/scikit-learn/scikit-learn/issues/17299
return (bitset[row, val // 32] >> (val % 32)) & 1
cpdef inline void set_bitset_memoryview(BITSET_INNER_DTYPE_C[:] bitset, # OUT
X_BINNED_DTYPE_C val):
bitset[val // 32] |= (1 << (val % 32))
def set_raw_bitset_from_binned_bitset(BITSET_INNER_DTYPE_C[:] raw_bitset, # OUT
BITSET_INNER_DTYPE_C[:] binned_bitset,
X_DTYPE_C[:] categories):
"""Set the raw_bitset from the values of the binned bitset
categories is a mapping from binned category value to raw category value.
"""
cdef:
int binned_cat_value
X_DTYPE_C raw_cat_value
for binned_cat_value, raw_cat_value in enumerate(categories):
if in_bitset_memoryview(binned_bitset, binned_cat_value):
set_bitset_memoryview(raw_bitset, <X_BINNED_DTYPE_C>raw_cat_value)
from .common cimport BITSET_INNER_DTYPE_C
from .common cimport BITSET_DTYPE_C
from .common cimport X_DTYPE_C
from .common cimport X_BINNED_DTYPE_C
from ...utils._typedefs cimport uint8_t
# A bitset is a data structure used to represent sets of integers in [0, n]. We
# use them to represent sets of features indices (e.g. features that go to the
# left child, or features that are categorical). For familiarity with bitsets
# and bitwise operations:
# https://en.wikipedia.org/wiki/Bit_array
# https://en.wikipedia.org/wiki/Bitwise_operation
cdef inline void init_bitset(BITSET_DTYPE_C bitset) noexcept nogil: # OUT
cdef:
unsigned int i
for i in range(8):
bitset[i] = 0
cdef inline void set_bitset(BITSET_DTYPE_C bitset, # OUT
X_BINNED_DTYPE_C val) noexcept nogil:
bitset[val // 32] |= (1 << (val % 32))
cdef inline uint8_t in_bitset(BITSET_DTYPE_C bitset,
X_BINNED_DTYPE_C val) noexcept nogil:
return (bitset[val // 32] >> (val % 32)) & 1
cpdef inline uint8_t in_bitset_memoryview(const BITSET_INNER_DTYPE_C[:] bitset,
X_BINNED_DTYPE_C val) noexcept nogil:
return (bitset[val // 32] >> (val % 32)) & 1
cdef inline uint8_t in_bitset_2d_memoryview(const BITSET_INNER_DTYPE_C[:, :] bitset,
X_BINNED_DTYPE_C val,
unsigned int row) noexcept nogil:
# Same as above but works on 2d memory views to avoid the creation of 1d
# memory views. See https://github.com/scikit-learn/scikit-learn/issues/17299
return (bitset[row, val // 32] >> (val % 32)) & 1
cpdef inline void set_bitset_memoryview(BITSET_INNER_DTYPE_C[:] bitset, # OUT
X_BINNED_DTYPE_C val):
bitset[val // 32] |= (1 << (val % 32))
def set_raw_bitset_from_binned_bitset(BITSET_INNER_DTYPE_C[:] raw_bitset, # OUT
BITSET_INNER_DTYPE_C[:] binned_bitset,
X_DTYPE_C[:] categories):
"""Set the raw_bitset from the values of the binned bitset
categories is a mapping from binned category value to raw category value.
"""
cdef:
int binned_cat_value
X_DTYPE_C raw_cat_value
for binned_cat_value, raw_cat_value in enumerate(categories):
if in_bitset_memoryview(binned_bitset, binned_cat_value):
set_bitset_memoryview(raw_bitset, <X_BINNED_DTYPE_C>raw_cat_value)
@@ -1,59 +1,59 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython.parallel import prange
import numpy as np
from .common import Y_DTYPE
from .common cimport Y_DTYPE_C
def _update_raw_predictions(
Y_DTYPE_C [::1] raw_predictions, # OUT
grower,
n_threads,
):
"""Update raw_predictions with the predictions of the newest tree.
This is equivalent to (and much faster than):
raw_predictions += last_estimator.predict(X_train)
It's only possible for data X_train that is used to train the trees (it
isn't usable for e.g. X_val).
"""
cdef:
unsigned int [::1] starts # start of each leaf in partition
unsigned int [::1] stops # end of each leaf in partition
Y_DTYPE_C [::1] values # value of each leaf
const unsigned int [::1] partition = grower.splitter.partition
list leaves
leaves = grower.finalized_leaves
starts = np.array([leaf.partition_start for leaf in leaves],
dtype=np.uint32)
stops = np.array([leaf.partition_stop for leaf in leaves],
dtype=np.uint32)
values = np.array([leaf.value for leaf in leaves], dtype=Y_DTYPE)
_update_raw_predictions_helper(raw_predictions, starts, stops, partition,
values, n_threads)
cdef inline void _update_raw_predictions_helper(
Y_DTYPE_C [::1] raw_predictions, # OUT
const unsigned int [::1] starts,
const unsigned int [::1] stops,
const unsigned int [::1] partition,
const Y_DTYPE_C [::1] values,
int n_threads,
):
cdef:
unsigned int position
int leaf_idx
int n_leaves = starts.shape[0]
for leaf_idx in prange(n_leaves, schedule='static', nogil=True,
num_threads=n_threads):
for position in range(starts[leaf_idx], stops[leaf_idx]):
raw_predictions[partition[position]] += values[leaf_idx]
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython.parallel import prange
import numpy as np
from .common import Y_DTYPE
from .common cimport Y_DTYPE_C
def _update_raw_predictions(
Y_DTYPE_C [::1] raw_predictions, # OUT
grower,
n_threads,
):
"""Update raw_predictions with the predictions of the newest tree.
This is equivalent to (and much faster than):
raw_predictions += last_estimator.predict(X_train)
It's only possible for data X_train that is used to train the trees (it
isn't usable for e.g. X_val).
"""
cdef:
unsigned int [::1] starts # start of each leaf in partition
unsigned int [::1] stops # end of each leaf in partition
Y_DTYPE_C [::1] values # value of each leaf
const unsigned int [::1] partition = grower.splitter.partition
list leaves
leaves = grower.finalized_leaves
starts = np.array([leaf.partition_start for leaf in leaves],
dtype=np.uint32)
stops = np.array([leaf.partition_stop for leaf in leaves],
dtype=np.uint32)
values = np.array([leaf.value for leaf in leaves], dtype=Y_DTYPE)
_update_raw_predictions_helper(raw_predictions, starts, stops, partition,
values, n_threads)
cdef inline void _update_raw_predictions_helper(
Y_DTYPE_C [::1] raw_predictions, # OUT
const unsigned int [::1] starts,
const unsigned int [::1] stops,
const unsigned int [::1] partition,
const Y_DTYPE_C [::1] values,
int n_threads,
):
cdef:
unsigned int position
int leaf_idx
int n_leaves = starts.shape[0]
for leaf_idx in prange(n_leaves, schedule='static', nogil=True,
num_threads=n_threads):
for position in range(starts[leaf_idx], stops[leaf_idx]):
raw_predictions[partition[position]] += values[leaf_idx]
@@ -1,256 +1,256 @@
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython.parallel import prange
from libc.math cimport isnan
import numpy as np
from ...utils._typedefs cimport intp_t, uint8_t
from .common cimport X_DTYPE_C
from .common cimport Y_DTYPE_C
from .common import Y_DTYPE
from .common cimport X_BINNED_DTYPE_C
from .common cimport BITSET_INNER_DTYPE_C
from .common cimport node_struct
from ._bitset cimport in_bitset_2d_memoryview
def _predict_from_raw_data( # raw data = non-binned data
const node_struct [:] nodes,
const X_DTYPE_C [:, :] numeric_data,
const BITSET_INNER_DTYPE_C [:, ::1] raw_left_cat_bitsets,
const BITSET_INNER_DTYPE_C [:, ::1] known_cat_bitsets,
const unsigned int [::1] f_idx_map,
int n_threads,
Y_DTYPE_C [:] out):
cdef:
int i
for i in prange(numeric_data.shape[0], schedule='static', nogil=True,
num_threads=n_threads):
out[i] = _predict_one_from_raw_data(
nodes, numeric_data, raw_left_cat_bitsets,
known_cat_bitsets,
f_idx_map, i)
cdef inline Y_DTYPE_C _predict_one_from_raw_data(
const node_struct [:] nodes,
const X_DTYPE_C [:, :] numeric_data,
const BITSET_INNER_DTYPE_C [:, ::1] raw_left_cat_bitsets,
const BITSET_INNER_DTYPE_C [:, ::1] known_cat_bitsets,
const unsigned int [::1] f_idx_map,
const int row) noexcept nogil:
# Need to pass the whole array and the row index, else prange won't work.
# See issue Cython #2798
cdef:
node_struct node = nodes[0]
unsigned int node_idx = 0
X_DTYPE_C data_val
while True:
if node.is_leaf:
return node.value
data_val = numeric_data[row, node.feature_idx]
if isnan(data_val):
if node.missing_go_to_left:
node_idx = node.left
else:
node_idx = node.right
elif node.is_categorical:
if data_val < 0:
# data_val is not in the accepted range, so it is treated as missing value
node_idx = node.left if node.missing_go_to_left else node.right
elif in_bitset_2d_memoryview(
raw_left_cat_bitsets,
<X_BINNED_DTYPE_C>data_val,
node.bitset_idx):
node_idx = node.left
elif in_bitset_2d_memoryview(
known_cat_bitsets,
<X_BINNED_DTYPE_C>data_val,
f_idx_map[node.feature_idx]):
node_idx = node.right
else:
# Treat unknown categories as missing.
node_idx = node.left if node.missing_go_to_left else node.right
else:
if data_val <= node.num_threshold:
node_idx = node.left
else:
node_idx = node.right
node = nodes[node_idx]
def _predict_from_binned_data(
node_struct [:] nodes,
const X_BINNED_DTYPE_C [:, :] binned_data,
BITSET_INNER_DTYPE_C [:, :] binned_left_cat_bitsets,
const uint8_t missing_values_bin_idx,
int n_threads,
Y_DTYPE_C [:] out):
cdef:
int i
for i in prange(binned_data.shape[0], schedule='static', nogil=True,
num_threads=n_threads):
out[i] = _predict_one_from_binned_data(nodes,
binned_data,
binned_left_cat_bitsets, i,
missing_values_bin_idx)
cdef inline Y_DTYPE_C _predict_one_from_binned_data(
node_struct [:] nodes,
const X_BINNED_DTYPE_C [:, :] binned_data,
const BITSET_INNER_DTYPE_C [:, :] binned_left_cat_bitsets,
const int row,
const uint8_t missing_values_bin_idx) noexcept nogil:
# Need to pass the whole array and the row index, else prange won't work.
# See issue Cython #2798
cdef:
node_struct node = nodes[0]
unsigned int node_idx = 0
X_BINNED_DTYPE_C data_val
while True:
if node.is_leaf:
return node.value
data_val = binned_data[row, node.feature_idx]
if data_val == missing_values_bin_idx:
if node.missing_go_to_left:
node_idx = node.left
else:
node_idx = node.right
elif node.is_categorical:
if in_bitset_2d_memoryview(
binned_left_cat_bitsets,
data_val,
node.bitset_idx):
node_idx = node.left
else:
node_idx = node.right
else:
if data_val <= node.bin_threshold:
node_idx = node.left
else:
node_idx = node.right
node = nodes[node_idx]
def _compute_partial_dependence(
node_struct [:] nodes,
const X_DTYPE_C [:, ::1] X,
const intp_t [:] target_features,
Y_DTYPE_C [:] out
):
"""Partial dependence of the response on the ``target_features`` set.
For each sample in ``X`` a tree traversal is performed.
Each traversal starts from the root with weight 1.0.
At each non-leaf node that splits on a target feature, either
the left child or the right child is visited based on the feature
value of the current sample, and the weight is not modified.
At each non-leaf node that splits on a complementary feature,
both children are visited and the weight is multiplied by the fraction
of training samples which went to each child.
At each leaf, the value of the node is multiplied by the current
weight (weights sum to 1 for all visited terminal nodes).
Parameters
----------
nodes : view on array of PREDICTOR_RECORD_DTYPE, shape (n_nodes)
The array representing the predictor tree.
X : view on 2d ndarray, shape (n_samples, n_target_features)
The grid points on which the partial dependence should be
evaluated.
target_features : view on 1d ndarray of intp_t, shape (n_target_features)
The set of target features for which the partial dependence
should be evaluated.
out : view on 1d ndarray, shape (n_samples)
The value of the partial dependence function on each grid
point.
"""
cdef:
unsigned int current_node_idx
unsigned int [:] node_idx_stack = np.zeros(shape=nodes.shape[0],
dtype=np.uint32)
Y_DTYPE_C [::1] weight_stack = np.zeros(shape=nodes.shape[0],
dtype=Y_DTYPE)
node_struct * current_node # pointer to avoid copying attributes
unsigned int sample_idx
intp_t feature_idx
unsigned stack_size
Y_DTYPE_C left_sample_frac
Y_DTYPE_C current_weight
Y_DTYPE_C total_weight # used for sanity check only
bint is_target_feature
for sample_idx in range(X.shape[0]):
# init stacks for current sample
stack_size = 1
node_idx_stack[0] = 0 # root node
weight_stack[0] = 1 # all the samples are in the root node
total_weight = 0
while stack_size > 0:
# pop the stack
stack_size -= 1
current_node_idx = node_idx_stack[stack_size]
current_node = &nodes[current_node_idx]
if current_node.is_leaf:
out[sample_idx] += (weight_stack[stack_size] *
current_node.value)
total_weight += weight_stack[stack_size]
else:
# determine if the split feature is a target feature
is_target_feature = False
for feature_idx in range(target_features.shape[0]):
if target_features[feature_idx] == current_node.feature_idx:
is_target_feature = True
break
if is_target_feature:
# In this case, we push left or right child on stack
if X[sample_idx, feature_idx] <= current_node.num_threshold:
node_idx_stack[stack_size] = current_node.left
else:
node_idx_stack[stack_size] = current_node.right
stack_size += 1
else:
# In this case, we push both children onto the stack,
# and give a weight proportional to the number of
# samples going through each branch.
# push left child
node_idx_stack[stack_size] = current_node.left
left_sample_frac = (
<Y_DTYPE_C> nodes[current_node.left].count /
current_node.count)
current_weight = weight_stack[stack_size]
weight_stack[stack_size] = current_weight * left_sample_frac
stack_size += 1
# push right child
node_idx_stack[stack_size] = current_node.right
weight_stack[stack_size] = (
current_weight * (1 - left_sample_frac))
stack_size += 1
# Sanity check. Should never happen.
if not (0.999 < total_weight < 1.001):
raise ValueError("Total weight should be 1.0 but was %.9f" %total_weight)
# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause
from cython.parallel import prange
from libc.math cimport isnan
import numpy as np
from ...utils._typedefs cimport intp_t, uint8_t
from .common cimport X_DTYPE_C
from .common cimport Y_DTYPE_C
from .common import Y_DTYPE
from .common cimport X_BINNED_DTYPE_C
from .common cimport BITSET_INNER_DTYPE_C
from .common cimport node_struct
from ._bitset cimport in_bitset_2d_memoryview
def _predict_from_raw_data( # raw data = non-binned data
const node_struct [:] nodes,
const X_DTYPE_C [:, :] numeric_data,
const BITSET_INNER_DTYPE_C [:, ::1] raw_left_cat_bitsets,
const BITSET_INNER_DTYPE_C [:, ::1] known_cat_bitsets,
const unsigned int [::1] f_idx_map,
int n_threads,
Y_DTYPE_C [:] out):
cdef:
int i
for i in prange(numeric_data.shape[0], schedule='static', nogil=True,
num_threads=n_threads):
out[i] = _predict_one_from_raw_data(
nodes, numeric_data, raw_left_cat_bitsets,
known_cat_bitsets,
f_idx_map, i)
cdef inline Y_DTYPE_C _predict_one_from_raw_data(
const node_struct [:] nodes,
const X_DTYPE_C [:, :] numeric_data,
const BITSET_INNER_DTYPE_C [:, ::1] raw_left_cat_bitsets,
const BITSET_INNER_DTYPE_C [:, ::1] known_cat_bitsets,
const unsigned int [::1] f_idx_map,
const int row) noexcept nogil:
# Need to pass the whole array and the row index, else prange won't work.
# See issue Cython #2798
cdef:
node_struct node = nodes[0]
unsigned int node_idx = 0
X_DTYPE_C data_val
while True:
if node.is_leaf:
return node.value
data_val = numeric_data[row, node.feature_idx]
if isnan(data_val):
if node.missing_go_to_left:
node_idx = node.left
else:
node_idx = node.right
elif node.is_categorical:
if data_val < 0:
# data_val is not in the accepted range, so it is treated as missing value
node_idx = node.left if node.missing_go_to_left else node.right
elif in_bitset_2d_memoryview(
raw_left_cat_bitsets,
<X_BINNED_DTYPE_C>data_val,
node.bitset_idx):
node_idx = node.left
elif in_bitset_2d_memoryview(
known_cat_bitsets,
<X_BINNED_DTYPE_C>data_val,
f_idx_map[node.feature_idx]):
node_idx = node.right
else:
# Treat unknown categories as missing.
node_idx = node.left if node.missing_go_to_left else node.right
else:
if data_val <= node.num_threshold:
node_idx = node.left
else:
node_idx = node.right
node = nodes[node_idx]
def _predict_from_binned_data(
node_struct [:] nodes,
const X_BINNED_DTYPE_C [:, :] binned_data,
BITSET_INNER_DTYPE_C [:, :] binned_left_cat_bitsets,
const uint8_t missing_values_bin_idx,
int n_threads,
Y_DTYPE_C [:] out):
cdef:
int i
for i in prange(binned_data.shape[0], schedule='static', nogil=True,
num_threads=n_threads):
out[i] = _predict_one_from_binned_data(nodes,
binned_data,
binned_left_cat_bitsets, i,
missing_values_bin_idx)
cdef inline Y_DTYPE_C _predict_one_from_binned_data(
node_struct [:] nodes,
const X_BINNED_DTYPE_C [:, :] binned_data,
const BITSET_INNER_DTYPE_C [:, :] binned_left_cat_bitsets,
const int row,
const uint8_t missing_values_bin_idx) noexcept nogil:
# Need to pass the whole array and the row index, else prange won't work.
# See issue Cython #2798
cdef:
node_struct node = nodes[0]
unsigned int node_idx = 0
X_BINNED_DTYPE_C data_val
while True:
if node.is_leaf:
return node.value
data_val = binned_data[row, node.feature_idx]
if data_val == missing_values_bin_idx:
if node.missing_go_to_left:
node_idx = node.left
else:
node_idx = node.right
elif node.is_categorical:
if in_bitset_2d_memoryview(
binned_left_cat_bitsets,
data_val,
node.bitset_idx):
node_idx = node.left
else:
node_idx = node.right
else:
if data_val <= node.bin_threshold:
node_idx = node.left
else:
node_idx = node.right
node = nodes[node_idx]
def _compute_partial_dependence(
node_struct [:] nodes,
const X_DTYPE_C [:, ::1] X,
const intp_t [:] target_features,
Y_DTYPE_C [:] out
):
"""Partial dependence of the response on the ``target_features`` set.
For each sample in ``X`` a tree traversal is performed.
Each traversal starts from the root with weight 1.0.
At each non-leaf node that splits on a target feature, either
the left child or the right child is visited based on the feature
value of the current sample, and the weight is not modified.
At each non-leaf node that splits on a complementary feature,
both children are visited and the weight is multiplied by the fraction
of training samples which went to each child.
At each leaf, the value of the node is multiplied by the current
weight (weights sum to 1 for all visited terminal nodes).
Parameters
----------
nodes : view on array of PREDICTOR_RECORD_DTYPE, shape (n_nodes)
The array representing the predictor tree.
X : view on 2d ndarray, shape (n_samples, n_target_features)
The grid points on which the partial dependence should be
evaluated.
target_features : view on 1d ndarray of intp_t, shape (n_target_features)
The set of target features for which the partial dependence
should be evaluated.
out : view on 1d ndarray, shape (n_samples)
The value of the partial dependence function on each grid
point.
"""
cdef:
unsigned int current_node_idx
unsigned int [:] node_idx_stack = np.zeros(shape=nodes.shape[0],
dtype=np.uint32)
Y_DTYPE_C [::1] weight_stack = np.zeros(shape=nodes.shape[0],
dtype=Y_DTYPE)
node_struct * current_node # pointer to avoid copying attributes
unsigned int sample_idx
intp_t feature_idx
unsigned stack_size
Y_DTYPE_C left_sample_frac
Y_DTYPE_C current_weight
Y_DTYPE_C total_weight # used for sanity check only
bint is_target_feature
for sample_idx in range(X.shape[0]):
# init stacks for current sample
stack_size = 1
node_idx_stack[0] = 0 # root node
weight_stack[0] = 1 # all the samples are in the root node
total_weight = 0
while stack_size > 0:
# pop the stack
stack_size -= 1
current_node_idx = node_idx_stack[stack_size]
current_node = &nodes[current_node_idx]
if current_node.is_leaf:
out[sample_idx] += (weight_stack[stack_size] *
current_node.value)
total_weight += weight_stack[stack_size]
else:
# determine if the split feature is a target feature
is_target_feature = False
for feature_idx in range(target_features.shape[0]):
if target_features[feature_idx] == current_node.feature_idx:
is_target_feature = True
break
if is_target_feature:
# In this case, we push left or right child on stack
if X[sample_idx, feature_idx] <= current_node.num_threshold:
node_idx_stack[stack_size] = current_node.left
else:
node_idx_stack[stack_size] = current_node.right
stack_size += 1
else:
# In this case, we push both children onto the stack,
# and give a weight proportional to the number of
# samples going through each branch.
# push left child
node_idx_stack[stack_size] = current_node.left
left_sample_frac = (
<Y_DTYPE_C> nodes[current_node.left].count /
current_node.count)
current_weight = weight_stack[stack_size]
weight_stack[stack_size] = current_weight * left_sample_frac
stack_size += 1
# push right child
node_idx_stack[stack_size] = current_node.right
weight_stack[stack_size] = (
current_weight * (1 - left_sample_frac))
stack_size += 1
# Sanity check. Should never happen.
if not (0.999 < total_weight < 1.001):
raise ValueError("Total weight should be 1.0 but was %.9f" %total_weight)
@@ -1,43 +1,43 @@
from ...utils._typedefs cimport float32_t, float64_t, intp_t, uint8_t, uint32_t
ctypedef float64_t X_DTYPE_C
ctypedef uint8_t X_BINNED_DTYPE_C
ctypedef float64_t Y_DTYPE_C
ctypedef float32_t G_H_DTYPE_C
ctypedef uint32_t BITSET_INNER_DTYPE_C
ctypedef BITSET_INNER_DTYPE_C[8] BITSET_DTYPE_C
cdef packed struct hist_struct:
# Same as histogram dtype but we need a struct to declare views. It needs
# to be packed since by default numpy dtypes aren't aligned
Y_DTYPE_C sum_gradients
Y_DTYPE_C sum_hessians
unsigned int count
cdef packed struct node_struct:
# Equivalent struct to PREDICTOR_RECORD_DTYPE to use in memory views. It
# needs to be packed since by default numpy dtypes aren't aligned
Y_DTYPE_C value
unsigned int count
intp_t feature_idx
X_DTYPE_C num_threshold
uint8_t missing_go_to_left
unsigned int left
unsigned int right
Y_DTYPE_C gain
unsigned int depth
uint8_t is_leaf
X_BINNED_DTYPE_C bin_threshold
uint8_t is_categorical
# The index of the corresponding bitsets in the Predictor's bitset arrays.
# Only used if is_categorical is True
unsigned int bitset_idx
cpdef enum MonotonicConstraint:
NO_CST = 0
POS = 1
NEG = -1
from ...utils._typedefs cimport float32_t, float64_t, intp_t, uint8_t, uint32_t
ctypedef float64_t X_DTYPE_C
ctypedef uint8_t X_BINNED_DTYPE_C
ctypedef float64_t Y_DTYPE_C
ctypedef float32_t G_H_DTYPE_C
ctypedef uint32_t BITSET_INNER_DTYPE_C
ctypedef BITSET_INNER_DTYPE_C[8] BITSET_DTYPE_C
cdef packed struct hist_struct:
# Same as histogram dtype but we need a struct to declare views. It needs
# to be packed since by default numpy dtypes aren't aligned
Y_DTYPE_C sum_gradients
Y_DTYPE_C sum_hessians
unsigned int count
cdef packed struct node_struct:
# Equivalent struct to PREDICTOR_RECORD_DTYPE to use in memory views. It
# needs to be packed since by default numpy dtypes aren't aligned
Y_DTYPE_C value
unsigned int count
intp_t feature_idx
X_DTYPE_C num_threshold
uint8_t missing_go_to_left
unsigned int left
unsigned int right
Y_DTYPE_C gain
unsigned int depth
uint8_t is_leaf
X_BINNED_DTYPE_C bin_threshold
uint8_t is_categorical
# The index of the corresponding bitsets in the Predictor's bitset arrays.
# Only used if is_categorical is True
unsigned int bitset_idx
cpdef enum MonotonicConstraint:
NO_CST = 0
POS = 1
NEG = -1
@@ -1,44 +1,44 @@
import numpy as np
# Y_DYTPE is the dtype to which the targets y are converted to. This is also
# dtype for leaf values, gains, and sums of gradients / hessians. The gradients
# and hessians arrays are stored as floats to avoid using too much memory.
Y_DTYPE = np.float64
X_DTYPE = np.float64
X_BINNED_DTYPE = np.uint8 # hence max_bins == 256
# dtype for gradients and hessians arrays
G_H_DTYPE = np.float32
X_BITSET_INNER_DTYPE = np.uint32
# Note that we use Y_DTYPE=float64 to avoid issues with floating point precision when
# summing gradients and hessians (both float32). Those are difficult to protect via
# tools like (Kahan-) Neumaier summation as in CPython, see
# https://github.com/python/cpython/issues/100425, or pairwise summation as numpy, see
# https://github.com/numpy/numpy/pull/3685, due to the way histograms are summed
# (number of additions per bin is not known in advance). See also comment in
# _subtract_histograms.
HISTOGRAM_DTYPE = np.dtype([
('sum_gradients', Y_DTYPE), # sum of sample gradients in bin
('sum_hessians', Y_DTYPE), # sum of sample hessians in bin
('count', np.uint32), # number of samples in bin
])
PREDICTOR_RECORD_DTYPE = np.dtype([
('value', Y_DTYPE),
('count', np.uint32),
('feature_idx', np.intp),
('num_threshold', X_DTYPE),
('missing_go_to_left', np.uint8),
('left', np.uint32),
('right', np.uint32),
('gain', Y_DTYPE),
('depth', np.uint32),
('is_leaf', np.uint8),
('bin_threshold', X_BINNED_DTYPE),
('is_categorical', np.uint8),
# The index of the corresponding bitsets in the Predictor's bitset arrays.
# Only used if is_categorical is True
('bitset_idx', np.uint32)
])
ALMOST_INF = 1e300 # see LightGBM AvoidInf()
import numpy as np
# Y_DYTPE is the dtype to which the targets y are converted to. This is also
# dtype for leaf values, gains, and sums of gradients / hessians. The gradients
# and hessians arrays are stored as floats to avoid using too much memory.
Y_DTYPE = np.float64
X_DTYPE = np.float64
X_BINNED_DTYPE = np.uint8 # hence max_bins == 256
# dtype for gradients and hessians arrays
G_H_DTYPE = np.float32
X_BITSET_INNER_DTYPE = np.uint32
# Note that we use Y_DTYPE=float64 to avoid issues with floating point precision when
# summing gradients and hessians (both float32). Those are difficult to protect via
# tools like (Kahan-) Neumaier summation as in CPython, see
# https://github.com/python/cpython/issues/100425, or pairwise summation as numpy, see
# https://github.com/numpy/numpy/pull/3685, due to the way histograms are summed
# (number of additions per bin is not known in advance). See also comment in
# _subtract_histograms.
HISTOGRAM_DTYPE = np.dtype([
('sum_gradients', Y_DTYPE), # sum of sample gradients in bin
('sum_hessians', Y_DTYPE), # sum of sample hessians in bin
('count', np.uint32), # number of samples in bin
])
PREDICTOR_RECORD_DTYPE = np.dtype([
('value', Y_DTYPE),
('count', np.uint32),
('feature_idx', np.intp),
('num_threshold', X_DTYPE),
('missing_go_to_left', np.uint8),
('left', np.uint32),
('right', np.uint32),
('gain', Y_DTYPE),
('depth', np.uint32),
('is_leaf', np.uint8),
('bin_threshold', X_BINNED_DTYPE),
('is_categorical', np.uint8),
# The index of the corresponding bitsets in the Predictor's bitset arrays.
# Only used if is_categorical is True
('bitset_idx', np.uint32)
])
ALMOST_INF = 1e300 # see LightGBM AvoidInf()
@@ -1,20 +1,20 @@
hist_gradient_boosting_extension_metadata = {
'_gradient_boosting': {'sources': [cython_gen.process('_gradient_boosting.pyx')],
'dependencies': [openmp_dep]},
'histogram': {'sources': [cython_gen.process('histogram.pyx')], 'dependencies': [openmp_dep]},
'splitting': {'sources': [cython_gen.process('splitting.pyx')], 'dependencies': [openmp_dep]},
'_binning': {'sources': [cython_gen.process('_binning.pyx')], 'dependencies': [openmp_dep]},
'_predictor': {'sources': [cython_gen.process('_predictor.pyx')], 'dependencies': [openmp_dep]},
'_bitset': {'sources': [cython_gen.process('_bitset.pyx')]},
'common': {'sources': [cython_gen.process('common.pyx')]},
}
foreach ext_name, ext_dict : hist_gradient_boosting_extension_metadata
py.extension_module(
ext_name,
ext_dict.get('sources'),
dependencies: ext_dict.get('dependencies', []),
subdir: 'sklearn/ensemble/_hist_gradient_boosting',
install: true
)
endforeach
hist_gradient_boosting_extension_metadata = {
'_gradient_boosting': {'sources': [cython_gen.process('_gradient_boosting.pyx')],
'dependencies': [openmp_dep]},
'histogram': {'sources': [cython_gen.process('histogram.pyx')], 'dependencies': [openmp_dep]},
'splitting': {'sources': [cython_gen.process('splitting.pyx')], 'dependencies': [openmp_dep]},
'_binning': {'sources': [cython_gen.process('_binning.pyx')], 'dependencies': [openmp_dep]},
'_predictor': {'sources': [cython_gen.process('_predictor.pyx')], 'dependencies': [openmp_dep]},
'_bitset': {'sources': [cython_gen.process('_bitset.pyx')]},
'common': {'sources': [cython_gen.process('common.pyx')]},
}
foreach ext_name, ext_dict : hist_gradient_boosting_extension_metadata
py.extension_module(
ext_name,
ext_dict.get('sources'),
dependencies: ext_dict.get('dependencies', []),
subdir: 'sklearn/ensemble/_hist_gradient_boosting',
install: true
)
endforeach
@@ -1,9 +1,9 @@
py.extension_module(
'_gradient_boosting',
[cython_gen.process('_gradient_boosting.pyx')] + utils_cython_tree,
dependencies: [np_dep],
subdir: 'sklearn/ensemble',
install: true
)
subdir('_hist_gradient_boosting')
py.extension_module(
'_gradient_boosting',
[cython_gen.process('_gradient_boosting.pyx')] + utils_cython_tree,
dependencies: [np_dep],
subdir: 'sklearn/ensemble',
install: true
)
subdir('_hist_gradient_boosting')