release(camera-ai): publish 0.4.12 catalog portability fix
This commit is contained in:
@@ -1,109 +1,109 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _criterion.pyx for implementation details.
|
||||
from ..utils._typedefs cimport float64_t, int8_t, intp_t
|
||||
|
||||
|
||||
cdef class Criterion:
|
||||
# The criterion computes the impurity of a node and the reduction of
|
||||
# impurity of a split on that node. It also computes the output statistics
|
||||
# such as the mean in regression and class probabilities in classification.
|
||||
|
||||
# Internal structures
|
||||
cdef const float64_t[:, ::1] y # Values of y
|
||||
cdef const float64_t[:] sample_weight # Sample weights
|
||||
|
||||
cdef const intp_t[:] sample_indices # Sample indices in X, y
|
||||
cdef intp_t start # samples[start:pos] are the samples in the left node
|
||||
cdef intp_t pos # samples[pos:end] are the samples in the right node
|
||||
cdef intp_t end
|
||||
cdef intp_t n_missing # Number of missing values for the feature being evaluated
|
||||
cdef bint missing_go_to_left # Whether missing values go to the left node
|
||||
|
||||
cdef intp_t n_outputs # Number of outputs
|
||||
cdef intp_t n_samples # Number of samples
|
||||
cdef intp_t n_node_samples # Number of samples in the node (end-start)
|
||||
cdef float64_t weighted_n_samples # Weighted number of samples (in total)
|
||||
cdef float64_t weighted_n_node_samples # Weighted number of samples in the node
|
||||
cdef float64_t weighted_n_left # Weighted number of samples in the left node
|
||||
cdef float64_t weighted_n_right # Weighted number of samples in the right node
|
||||
cdef float64_t weighted_n_missing # Weighted number of samples that are missing
|
||||
|
||||
# The criterion object is maintained such that left and right collected
|
||||
# statistics correspond to samples[start:pos] and samples[pos:end].
|
||||
|
||||
# Methods
|
||||
cdef int init(
|
||||
self,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight,
|
||||
float64_t weighted_n_samples,
|
||||
const intp_t[:] sample_indices,
|
||||
intp_t start,
|
||||
intp_t end
|
||||
) except -1 nogil
|
||||
cdef void init_sum_missing(self)
|
||||
cdef void init_missing(self, intp_t n_missing) noexcept nogil
|
||||
cdef int reset(self) except -1 nogil
|
||||
cdef int reverse_reset(self) except -1 nogil
|
||||
cdef int update(self, intp_t new_pos) except -1 nogil
|
||||
cdef float64_t node_impurity(self) noexcept nogil
|
||||
cdef void children_impurity(
|
||||
self,
|
||||
float64_t* impurity_left,
|
||||
float64_t* impurity_right
|
||||
) noexcept nogil
|
||||
cdef void node_value(
|
||||
self,
|
||||
float64_t* dest
|
||||
) noexcept nogil
|
||||
cdef void clip_node_value(
|
||||
self,
|
||||
float64_t* dest,
|
||||
float64_t lower_bound,
|
||||
float64_t upper_bound
|
||||
) noexcept nogil
|
||||
cdef float64_t middle_value(self) noexcept nogil
|
||||
cdef float64_t impurity_improvement(
|
||||
self,
|
||||
float64_t impurity_parent,
|
||||
float64_t impurity_left,
|
||||
float64_t impurity_right
|
||||
) noexcept nogil
|
||||
cdef float64_t proxy_impurity_improvement(self) noexcept nogil
|
||||
cdef bint check_monotonicity(
|
||||
self,
|
||||
int8_t monotonic_cst,
|
||||
float64_t lower_bound,
|
||||
float64_t upper_bound,
|
||||
) noexcept nogil
|
||||
cdef inline bint _check_monotonicity(
|
||||
self,
|
||||
int8_t monotonic_cst,
|
||||
float64_t lower_bound,
|
||||
float64_t upper_bound,
|
||||
float64_t sum_left,
|
||||
float64_t sum_right,
|
||||
) noexcept nogil
|
||||
|
||||
cdef class ClassificationCriterion(Criterion):
|
||||
"""Abstract criterion for classification."""
|
||||
|
||||
cdef intp_t[::1] n_classes
|
||||
cdef intp_t max_n_classes
|
||||
|
||||
cdef float64_t[:, ::1] sum_total # The sum of the weighted count of each label.
|
||||
cdef float64_t[:, ::1] sum_left # Same as above, but for the left side of the split
|
||||
cdef float64_t[:, ::1] sum_right # Same as above, but for the right side of the split
|
||||
cdef float64_t[:, ::1] sum_missing # Same as above, but for missing values in X
|
||||
|
||||
cdef class RegressionCriterion(Criterion):
|
||||
"""Abstract regression criterion."""
|
||||
|
||||
cdef float64_t sq_sum_total
|
||||
|
||||
cdef float64_t[::1] sum_total # The sum of w*y.
|
||||
cdef float64_t[::1] sum_left # Same as above, but for the left side of the split
|
||||
cdef float64_t[::1] sum_right # Same as above, but for the right side of the split
|
||||
cdef float64_t[::1] sum_missing # Same as above, but for missing values in X
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _criterion.pyx for implementation details.
|
||||
from ..utils._typedefs cimport float64_t, int8_t, intp_t
|
||||
|
||||
|
||||
cdef class Criterion:
|
||||
# The criterion computes the impurity of a node and the reduction of
|
||||
# impurity of a split on that node. It also computes the output statistics
|
||||
# such as the mean in regression and class probabilities in classification.
|
||||
|
||||
# Internal structures
|
||||
cdef const float64_t[:, ::1] y # Values of y
|
||||
cdef const float64_t[:] sample_weight # Sample weights
|
||||
|
||||
cdef const intp_t[:] sample_indices # Sample indices in X, y
|
||||
cdef intp_t start # samples[start:pos] are the samples in the left node
|
||||
cdef intp_t pos # samples[pos:end] are the samples in the right node
|
||||
cdef intp_t end
|
||||
cdef intp_t n_missing # Number of missing values for the feature being evaluated
|
||||
cdef bint missing_go_to_left # Whether missing values go to the left node
|
||||
|
||||
cdef intp_t n_outputs # Number of outputs
|
||||
cdef intp_t n_samples # Number of samples
|
||||
cdef intp_t n_node_samples # Number of samples in the node (end-start)
|
||||
cdef float64_t weighted_n_samples # Weighted number of samples (in total)
|
||||
cdef float64_t weighted_n_node_samples # Weighted number of samples in the node
|
||||
cdef float64_t weighted_n_left # Weighted number of samples in the left node
|
||||
cdef float64_t weighted_n_right # Weighted number of samples in the right node
|
||||
cdef float64_t weighted_n_missing # Weighted number of samples that are missing
|
||||
|
||||
# The criterion object is maintained such that left and right collected
|
||||
# statistics correspond to samples[start:pos] and samples[pos:end].
|
||||
|
||||
# Methods
|
||||
cdef int init(
|
||||
self,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight,
|
||||
float64_t weighted_n_samples,
|
||||
const intp_t[:] sample_indices,
|
||||
intp_t start,
|
||||
intp_t end
|
||||
) except -1 nogil
|
||||
cdef void init_sum_missing(self)
|
||||
cdef void init_missing(self, intp_t n_missing) noexcept nogil
|
||||
cdef int reset(self) except -1 nogil
|
||||
cdef int reverse_reset(self) except -1 nogil
|
||||
cdef int update(self, intp_t new_pos) except -1 nogil
|
||||
cdef float64_t node_impurity(self) noexcept nogil
|
||||
cdef void children_impurity(
|
||||
self,
|
||||
float64_t* impurity_left,
|
||||
float64_t* impurity_right
|
||||
) noexcept nogil
|
||||
cdef void node_value(
|
||||
self,
|
||||
float64_t* dest
|
||||
) noexcept nogil
|
||||
cdef void clip_node_value(
|
||||
self,
|
||||
float64_t* dest,
|
||||
float64_t lower_bound,
|
||||
float64_t upper_bound
|
||||
) noexcept nogil
|
||||
cdef float64_t middle_value(self) noexcept nogil
|
||||
cdef float64_t impurity_improvement(
|
||||
self,
|
||||
float64_t impurity_parent,
|
||||
float64_t impurity_left,
|
||||
float64_t impurity_right
|
||||
) noexcept nogil
|
||||
cdef float64_t proxy_impurity_improvement(self) noexcept nogil
|
||||
cdef bint check_monotonicity(
|
||||
self,
|
||||
int8_t monotonic_cst,
|
||||
float64_t lower_bound,
|
||||
float64_t upper_bound,
|
||||
) noexcept nogil
|
||||
cdef inline bint _check_monotonicity(
|
||||
self,
|
||||
int8_t monotonic_cst,
|
||||
float64_t lower_bound,
|
||||
float64_t upper_bound,
|
||||
float64_t sum_left,
|
||||
float64_t sum_right,
|
||||
) noexcept nogil
|
||||
|
||||
cdef class ClassificationCriterion(Criterion):
|
||||
"""Abstract criterion for classification."""
|
||||
|
||||
cdef intp_t[::1] n_classes
|
||||
cdef intp_t max_n_classes
|
||||
|
||||
cdef float64_t[:, ::1] sum_total # The sum of the weighted count of each label.
|
||||
cdef float64_t[:, ::1] sum_left # Same as above, but for the left side of the split
|
||||
cdef float64_t[:, ::1] sum_right # Same as above, but for the right side of the split
|
||||
cdef float64_t[:, ::1] sum_missing # Same as above, but for missing values in X
|
||||
|
||||
cdef class RegressionCriterion(Criterion):
|
||||
"""Abstract regression criterion."""
|
||||
|
||||
cdef float64_t sq_sum_total
|
||||
|
||||
cdef float64_t[::1] sum_total # The sum of w*y.
|
||||
cdef float64_t[::1] sum_left # Same as above, but for the left side of the split
|
||||
cdef float64_t[::1] sum_right # Same as above, but for the right side of the split
|
||||
cdef float64_t[::1] sum_missing # Same as above, but for missing values in X
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,178 +1,178 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _partitioner.pyx for details.
|
||||
|
||||
from ..utils._typedefs cimport (
|
||||
float32_t, float64_t, int8_t, int32_t, intp_t, uint8_t, uint32_t
|
||||
)
|
||||
from ._splitter cimport SplitRecord
|
||||
|
||||
|
||||
# Mitigate precision differences between 32 bit and 64 bit
|
||||
cdef float32_t FEATURE_THRESHOLD = 1e-7
|
||||
|
||||
|
||||
# We provide here the abstract interface for a Partitioner that would be
|
||||
# theoretically shared between the Dense and Sparse partitioners. However,
|
||||
# we leave it commented out for now as it is not used in the current
|
||||
# implementation due to the performance hit from vtable lookups when using
|
||||
# inheritance based polymorphism. It is left here for future reference.
|
||||
#
|
||||
# Note: Instead, in `_splitter.pyx`, we define a fused type that can be used
|
||||
# to represent both the dense and sparse partitioners.
|
||||
#
|
||||
# cdef class BasePartitioner:
|
||||
# cdef intp_t[::1] samples
|
||||
# cdef float32_t[::1] feature_values
|
||||
# cdef intp_t start
|
||||
# cdef intp_t end
|
||||
# cdef intp_t n_missing
|
||||
# cdef const uint8_t[::1] missing_values_in_feature_mask
|
||||
|
||||
# cdef void sort_samples_and_feature_values(
|
||||
# self, intp_t current_feature
|
||||
# ) noexcept nogil
|
||||
# cdef void init_node_split(
|
||||
# self,
|
||||
# intp_t start,
|
||||
# intp_t end
|
||||
# ) noexcept nogil
|
||||
# cdef void find_min_max(
|
||||
# self,
|
||||
# intp_t current_feature,
|
||||
# float32_t* min_feature_value_out,
|
||||
# float32_t* max_feature_value_out,
|
||||
# ) noexcept nogil
|
||||
# cdef void next_p(
|
||||
# self,
|
||||
# intp_t* p_prev,
|
||||
# intp_t* p
|
||||
# ) noexcept nogil
|
||||
# cdef intp_t partition_samples(
|
||||
# self,
|
||||
# float64_t current_threshold
|
||||
# ) noexcept nogil
|
||||
# cdef void partition_samples_final(
|
||||
# self,
|
||||
# intp_t best_pos,
|
||||
# float64_t best_threshold,
|
||||
# intp_t best_feature,
|
||||
# intp_t n_missing,
|
||||
# ) noexcept nogil
|
||||
|
||||
|
||||
cdef class DensePartitioner:
|
||||
"""Partitioner specialized for dense data.
|
||||
|
||||
Note that this partitioner is agnostic to the splitting strategy (best vs. random).
|
||||
"""
|
||||
cdef const float32_t[:, :] X
|
||||
cdef intp_t[::1] samples
|
||||
cdef float32_t[::1] feature_values
|
||||
cdef intp_t start
|
||||
cdef intp_t end
|
||||
cdef intp_t n_missing
|
||||
cdef const uint8_t[::1] missing_values_in_feature_mask
|
||||
|
||||
cdef void sort_samples_and_feature_values(
|
||||
self, intp_t current_feature
|
||||
) noexcept nogil
|
||||
cdef void init_node_split(
|
||||
self,
|
||||
intp_t start,
|
||||
intp_t end
|
||||
) noexcept nogil
|
||||
cdef void find_min_max(
|
||||
self,
|
||||
intp_t current_feature,
|
||||
float32_t* min_feature_value_out,
|
||||
float32_t* max_feature_value_out,
|
||||
) noexcept nogil
|
||||
cdef void next_p(
|
||||
self,
|
||||
intp_t* p_prev,
|
||||
intp_t* p
|
||||
) noexcept nogil
|
||||
cdef intp_t partition_samples(
|
||||
self,
|
||||
float64_t current_threshold
|
||||
) noexcept nogil
|
||||
cdef void partition_samples_final(
|
||||
self,
|
||||
intp_t best_pos,
|
||||
float64_t best_threshold,
|
||||
intp_t best_feature,
|
||||
intp_t n_missing,
|
||||
) noexcept nogil
|
||||
|
||||
|
||||
cdef class SparsePartitioner:
|
||||
"""Partitioner specialized for sparse CSC data.
|
||||
|
||||
Note that this partitioner is agnostic to the splitting strategy (best vs. random).
|
||||
"""
|
||||
cdef const float32_t[::1] X_data
|
||||
cdef const int32_t[::1] X_indices
|
||||
cdef const int32_t[::1] X_indptr
|
||||
cdef intp_t n_total_samples
|
||||
cdef intp_t[::1] index_to_samples
|
||||
cdef intp_t[::1] sorted_samples
|
||||
cdef intp_t start_positive
|
||||
cdef intp_t end_negative
|
||||
cdef bint is_samples_sorted
|
||||
|
||||
cdef intp_t[::1] samples
|
||||
cdef float32_t[::1] feature_values
|
||||
cdef intp_t start
|
||||
cdef intp_t end
|
||||
cdef intp_t n_missing
|
||||
cdef const uint8_t[::1] missing_values_in_feature_mask
|
||||
|
||||
cdef void sort_samples_and_feature_values(
|
||||
self, intp_t current_feature
|
||||
) noexcept nogil
|
||||
cdef void init_node_split(
|
||||
self,
|
||||
intp_t start,
|
||||
intp_t end
|
||||
) noexcept nogil
|
||||
cdef void find_min_max(
|
||||
self,
|
||||
intp_t current_feature,
|
||||
float32_t* min_feature_value_out,
|
||||
float32_t* max_feature_value_out,
|
||||
) noexcept nogil
|
||||
cdef void next_p(
|
||||
self,
|
||||
intp_t* p_prev,
|
||||
intp_t* p
|
||||
) noexcept nogil
|
||||
cdef intp_t partition_samples(
|
||||
self,
|
||||
float64_t current_threshold
|
||||
) noexcept nogil
|
||||
cdef void partition_samples_final(
|
||||
self,
|
||||
intp_t best_pos,
|
||||
float64_t best_threshold,
|
||||
intp_t best_feature,
|
||||
intp_t n_missing,
|
||||
) noexcept nogil
|
||||
|
||||
cdef void extract_nnz(
|
||||
self,
|
||||
intp_t feature
|
||||
) noexcept nogil
|
||||
cdef intp_t _partition(
|
||||
self,
|
||||
float64_t threshold,
|
||||
intp_t zero_pos
|
||||
) noexcept nogil
|
||||
|
||||
|
||||
cdef void shift_missing_values_to_left_if_required(
|
||||
SplitRecord* best,
|
||||
intp_t[::1] samples,
|
||||
intp_t end,
|
||||
) noexcept nogil
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _partitioner.pyx for details.
|
||||
|
||||
from ..utils._typedefs cimport (
|
||||
float32_t, float64_t, int8_t, int32_t, intp_t, uint8_t, uint32_t
|
||||
)
|
||||
from ._splitter cimport SplitRecord
|
||||
|
||||
|
||||
# Mitigate precision differences between 32 bit and 64 bit
|
||||
cdef float32_t FEATURE_THRESHOLD = 1e-7
|
||||
|
||||
|
||||
# We provide here the abstract interface for a Partitioner that would be
|
||||
# theoretically shared between the Dense and Sparse partitioners. However,
|
||||
# we leave it commented out for now as it is not used in the current
|
||||
# implementation due to the performance hit from vtable lookups when using
|
||||
# inheritance based polymorphism. It is left here for future reference.
|
||||
#
|
||||
# Note: Instead, in `_splitter.pyx`, we define a fused type that can be used
|
||||
# to represent both the dense and sparse partitioners.
|
||||
#
|
||||
# cdef class BasePartitioner:
|
||||
# cdef intp_t[::1] samples
|
||||
# cdef float32_t[::1] feature_values
|
||||
# cdef intp_t start
|
||||
# cdef intp_t end
|
||||
# cdef intp_t n_missing
|
||||
# cdef const uint8_t[::1] missing_values_in_feature_mask
|
||||
|
||||
# cdef void sort_samples_and_feature_values(
|
||||
# self, intp_t current_feature
|
||||
# ) noexcept nogil
|
||||
# cdef void init_node_split(
|
||||
# self,
|
||||
# intp_t start,
|
||||
# intp_t end
|
||||
# ) noexcept nogil
|
||||
# cdef void find_min_max(
|
||||
# self,
|
||||
# intp_t current_feature,
|
||||
# float32_t* min_feature_value_out,
|
||||
# float32_t* max_feature_value_out,
|
||||
# ) noexcept nogil
|
||||
# cdef void next_p(
|
||||
# self,
|
||||
# intp_t* p_prev,
|
||||
# intp_t* p
|
||||
# ) noexcept nogil
|
||||
# cdef intp_t partition_samples(
|
||||
# self,
|
||||
# float64_t current_threshold
|
||||
# ) noexcept nogil
|
||||
# cdef void partition_samples_final(
|
||||
# self,
|
||||
# intp_t best_pos,
|
||||
# float64_t best_threshold,
|
||||
# intp_t best_feature,
|
||||
# intp_t n_missing,
|
||||
# ) noexcept nogil
|
||||
|
||||
|
||||
cdef class DensePartitioner:
|
||||
"""Partitioner specialized for dense data.
|
||||
|
||||
Note that this partitioner is agnostic to the splitting strategy (best vs. random).
|
||||
"""
|
||||
cdef const float32_t[:, :] X
|
||||
cdef intp_t[::1] samples
|
||||
cdef float32_t[::1] feature_values
|
||||
cdef intp_t start
|
||||
cdef intp_t end
|
||||
cdef intp_t n_missing
|
||||
cdef const uint8_t[::1] missing_values_in_feature_mask
|
||||
|
||||
cdef void sort_samples_and_feature_values(
|
||||
self, intp_t current_feature
|
||||
) noexcept nogil
|
||||
cdef void init_node_split(
|
||||
self,
|
||||
intp_t start,
|
||||
intp_t end
|
||||
) noexcept nogil
|
||||
cdef void find_min_max(
|
||||
self,
|
||||
intp_t current_feature,
|
||||
float32_t* min_feature_value_out,
|
||||
float32_t* max_feature_value_out,
|
||||
) noexcept nogil
|
||||
cdef void next_p(
|
||||
self,
|
||||
intp_t* p_prev,
|
||||
intp_t* p
|
||||
) noexcept nogil
|
||||
cdef intp_t partition_samples(
|
||||
self,
|
||||
float64_t current_threshold
|
||||
) noexcept nogil
|
||||
cdef void partition_samples_final(
|
||||
self,
|
||||
intp_t best_pos,
|
||||
float64_t best_threshold,
|
||||
intp_t best_feature,
|
||||
intp_t n_missing,
|
||||
) noexcept nogil
|
||||
|
||||
|
||||
cdef class SparsePartitioner:
|
||||
"""Partitioner specialized for sparse CSC data.
|
||||
|
||||
Note that this partitioner is agnostic to the splitting strategy (best vs. random).
|
||||
"""
|
||||
cdef const float32_t[::1] X_data
|
||||
cdef const int32_t[::1] X_indices
|
||||
cdef const int32_t[::1] X_indptr
|
||||
cdef intp_t n_total_samples
|
||||
cdef intp_t[::1] index_to_samples
|
||||
cdef intp_t[::1] sorted_samples
|
||||
cdef intp_t start_positive
|
||||
cdef intp_t end_negative
|
||||
cdef bint is_samples_sorted
|
||||
|
||||
cdef intp_t[::1] samples
|
||||
cdef float32_t[::1] feature_values
|
||||
cdef intp_t start
|
||||
cdef intp_t end
|
||||
cdef intp_t n_missing
|
||||
cdef const uint8_t[::1] missing_values_in_feature_mask
|
||||
|
||||
cdef void sort_samples_and_feature_values(
|
||||
self, intp_t current_feature
|
||||
) noexcept nogil
|
||||
cdef void init_node_split(
|
||||
self,
|
||||
intp_t start,
|
||||
intp_t end
|
||||
) noexcept nogil
|
||||
cdef void find_min_max(
|
||||
self,
|
||||
intp_t current_feature,
|
||||
float32_t* min_feature_value_out,
|
||||
float32_t* max_feature_value_out,
|
||||
) noexcept nogil
|
||||
cdef void next_p(
|
||||
self,
|
||||
intp_t* p_prev,
|
||||
intp_t* p
|
||||
) noexcept nogil
|
||||
cdef intp_t partition_samples(
|
||||
self,
|
||||
float64_t current_threshold
|
||||
) noexcept nogil
|
||||
cdef void partition_samples_final(
|
||||
self,
|
||||
intp_t best_pos,
|
||||
float64_t best_threshold,
|
||||
intp_t best_feature,
|
||||
intp_t n_missing,
|
||||
) noexcept nogil
|
||||
|
||||
cdef void extract_nnz(
|
||||
self,
|
||||
intp_t feature
|
||||
) noexcept nogil
|
||||
cdef intp_t _partition(
|
||||
self,
|
||||
float64_t threshold,
|
||||
intp_t zero_pos
|
||||
) noexcept nogil
|
||||
|
||||
|
||||
cdef void shift_missing_values_to_left_if_required(
|
||||
SplitRecord* best,
|
||||
intp_t[::1] samples,
|
||||
intp_t end,
|
||||
) noexcept nogil
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,106 +1,106 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _splitter.pyx for details.
|
||||
|
||||
from ..utils._typedefs cimport (
|
||||
float32_t, float64_t, int8_t, int32_t, intp_t, uint8_t, uint32_t
|
||||
)
|
||||
from ._criterion cimport Criterion
|
||||
from ._tree cimport ParentInfo
|
||||
|
||||
|
||||
cdef struct SplitRecord:
|
||||
# Data to track sample split
|
||||
intp_t feature # Which feature to split on.
|
||||
intp_t pos # Split samples array at the given position,
|
||||
# # i.e. count of samples below threshold for feature.
|
||||
# # pos is >= end if the node is a leaf.
|
||||
float64_t threshold # Threshold to split at.
|
||||
float64_t improvement # Impurity improvement given parent node.
|
||||
float64_t impurity_left # Impurity of the left split.
|
||||
float64_t impurity_right # Impurity of the right split.
|
||||
float64_t lower_bound # Lower bound on value of both children for monotonicity
|
||||
float64_t upper_bound # Upper bound on value of both children for monotonicity
|
||||
uint8_t missing_go_to_left # Controls if missing values go to the left node.
|
||||
intp_t n_missing # Number of missing values for the feature being split on
|
||||
|
||||
cdef class Splitter:
|
||||
# The splitter searches in the input space for a feature and a threshold
|
||||
# to split the samples samples[start:end].
|
||||
#
|
||||
# The impurity computations are delegated to a criterion object.
|
||||
|
||||
# Internal structures
|
||||
cdef public Criterion criterion # Impurity criterion
|
||||
cdef public intp_t max_features # Number of features to test
|
||||
cdef public intp_t min_samples_leaf # Min samples in a leaf
|
||||
cdef public float64_t min_weight_leaf # Minimum weight in a leaf
|
||||
|
||||
cdef object random_state # Random state
|
||||
cdef uint32_t rand_r_state # sklearn_rand_r random number state
|
||||
|
||||
cdef intp_t[::1] samples # Sample indices in X, y
|
||||
cdef intp_t n_samples # X.shape[0]
|
||||
cdef float64_t weighted_n_samples # Weighted number of samples
|
||||
cdef intp_t[::1] features # Feature indices in X
|
||||
cdef intp_t[::1] constant_features # Constant features indices
|
||||
cdef intp_t n_features # X.shape[1]
|
||||
cdef float32_t[::1] feature_values # temp. array holding feature values
|
||||
|
||||
cdef intp_t start # Start position for the current node
|
||||
cdef intp_t end # End position for the current node
|
||||
|
||||
cdef const float64_t[:, ::1] y
|
||||
# Monotonicity constraints for each feature.
|
||||
# The encoding is as follows:
|
||||
# -1: monotonic decrease
|
||||
# 0: no constraint
|
||||
# +1: monotonic increase
|
||||
cdef const int8_t[:] monotonic_cst
|
||||
cdef bint with_monotonic_cst
|
||||
cdef const float64_t[:] sample_weight
|
||||
|
||||
# The samples vector `samples` is maintained by the Splitter object such
|
||||
# that the samples contained in a node are contiguous. With this setting,
|
||||
# `node_split` reorganizes the node samples `samples[start:end]` in two
|
||||
# subsets `samples[start:pos]` and `samples[pos:end]`.
|
||||
|
||||
# The 1-d `features` array of size n_features contains the features
|
||||
# indices and allows fast sampling without replacement of features.
|
||||
|
||||
# The 1-d `constant_features` array of size n_features holds in
|
||||
# `constant_features[:n_constant_features]` the feature ids with
|
||||
# constant values for all the samples that reached a specific node.
|
||||
# The value `n_constant_features` is given by the parent node to its
|
||||
# child nodes. The content of the range `[n_constant_features:]` is left
|
||||
# undefined, but preallocated for performance reasons
|
||||
# This allows optimization with depth-based tree building.
|
||||
|
||||
# Methods
|
||||
cdef int init(
|
||||
self,
|
||||
object X,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight,
|
||||
const uint8_t[::1] missing_values_in_feature_mask,
|
||||
) except -1
|
||||
|
||||
cdef int node_reset(
|
||||
self,
|
||||
intp_t start,
|
||||
intp_t end,
|
||||
float64_t* weighted_n_node_samples
|
||||
) except -1 nogil
|
||||
|
||||
cdef int node_split(
|
||||
self,
|
||||
ParentInfo* parent,
|
||||
SplitRecord* split,
|
||||
) except -1 nogil
|
||||
|
||||
cdef void node_value(self, float64_t* dest) noexcept nogil
|
||||
|
||||
cdef void clip_node_value(self, float64_t* dest, float64_t lower_bound, float64_t upper_bound) noexcept nogil
|
||||
|
||||
cdef float64_t node_impurity(self) noexcept nogil
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _splitter.pyx for details.
|
||||
|
||||
from ..utils._typedefs cimport (
|
||||
float32_t, float64_t, int8_t, int32_t, intp_t, uint8_t, uint32_t
|
||||
)
|
||||
from ._criterion cimport Criterion
|
||||
from ._tree cimport ParentInfo
|
||||
|
||||
|
||||
cdef struct SplitRecord:
|
||||
# Data to track sample split
|
||||
intp_t feature # Which feature to split on.
|
||||
intp_t pos # Split samples array at the given position,
|
||||
# # i.e. count of samples below threshold for feature.
|
||||
# # pos is >= end if the node is a leaf.
|
||||
float64_t threshold # Threshold to split at.
|
||||
float64_t improvement # Impurity improvement given parent node.
|
||||
float64_t impurity_left # Impurity of the left split.
|
||||
float64_t impurity_right # Impurity of the right split.
|
||||
float64_t lower_bound # Lower bound on value of both children for monotonicity
|
||||
float64_t upper_bound # Upper bound on value of both children for monotonicity
|
||||
uint8_t missing_go_to_left # Controls if missing values go to the left node.
|
||||
intp_t n_missing # Number of missing values for the feature being split on
|
||||
|
||||
cdef class Splitter:
|
||||
# The splitter searches in the input space for a feature and a threshold
|
||||
# to split the samples samples[start:end].
|
||||
#
|
||||
# The impurity computations are delegated to a criterion object.
|
||||
|
||||
# Internal structures
|
||||
cdef public Criterion criterion # Impurity criterion
|
||||
cdef public intp_t max_features # Number of features to test
|
||||
cdef public intp_t min_samples_leaf # Min samples in a leaf
|
||||
cdef public float64_t min_weight_leaf # Minimum weight in a leaf
|
||||
|
||||
cdef object random_state # Random state
|
||||
cdef uint32_t rand_r_state # sklearn_rand_r random number state
|
||||
|
||||
cdef intp_t[::1] samples # Sample indices in X, y
|
||||
cdef intp_t n_samples # X.shape[0]
|
||||
cdef float64_t weighted_n_samples # Weighted number of samples
|
||||
cdef intp_t[::1] features # Feature indices in X
|
||||
cdef intp_t[::1] constant_features # Constant features indices
|
||||
cdef intp_t n_features # X.shape[1]
|
||||
cdef float32_t[::1] feature_values # temp. array holding feature values
|
||||
|
||||
cdef intp_t start # Start position for the current node
|
||||
cdef intp_t end # End position for the current node
|
||||
|
||||
cdef const float64_t[:, ::1] y
|
||||
# Monotonicity constraints for each feature.
|
||||
# The encoding is as follows:
|
||||
# -1: monotonic decrease
|
||||
# 0: no constraint
|
||||
# +1: monotonic increase
|
||||
cdef const int8_t[:] monotonic_cst
|
||||
cdef bint with_monotonic_cst
|
||||
cdef const float64_t[:] sample_weight
|
||||
|
||||
# The samples vector `samples` is maintained by the Splitter object such
|
||||
# that the samples contained in a node are contiguous. With this setting,
|
||||
# `node_split` reorganizes the node samples `samples[start:end]` in two
|
||||
# subsets `samples[start:pos]` and `samples[pos:end]`.
|
||||
|
||||
# The 1-d `features` array of size n_features contains the features
|
||||
# indices and allows fast sampling without replacement of features.
|
||||
|
||||
# The 1-d `constant_features` array of size n_features holds in
|
||||
# `constant_features[:n_constant_features]` the feature ids with
|
||||
# constant values for all the samples that reached a specific node.
|
||||
# The value `n_constant_features` is given by the parent node to its
|
||||
# child nodes. The content of the range `[n_constant_features:]` is left
|
||||
# undefined, but preallocated for performance reasons
|
||||
# This allows optimization with depth-based tree building.
|
||||
|
||||
# Methods
|
||||
cdef int init(
|
||||
self,
|
||||
object X,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight,
|
||||
const uint8_t[::1] missing_values_in_feature_mask,
|
||||
) except -1
|
||||
|
||||
cdef int node_reset(
|
||||
self,
|
||||
intp_t start,
|
||||
intp_t end,
|
||||
float64_t* weighted_n_node_samples
|
||||
) except -1 nogil
|
||||
|
||||
cdef int node_split(
|
||||
self,
|
||||
ParentInfo* parent,
|
||||
SplitRecord* split,
|
||||
) except -1 nogil
|
||||
|
||||
cdef void node_value(self, float64_t* dest) noexcept nogil
|
||||
|
||||
cdef void clip_node_value(self, float64_t* dest, float64_t lower_bound, float64_t upper_bound) noexcept nogil
|
||||
|
||||
cdef float64_t node_impurity(self) noexcept nogil
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,133 +1,133 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _tree.pyx for details.
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as cnp
|
||||
|
||||
from ..utils._typedefs cimport float32_t, float64_t, intp_t, int32_t, uint8_t, uint32_t
|
||||
|
||||
from ._splitter cimport Splitter
|
||||
from ._splitter cimport SplitRecord
|
||||
|
||||
cdef struct Node:
|
||||
# Base storage structure for the nodes in a Tree object
|
||||
|
||||
intp_t left_child # id of the left child of the node
|
||||
intp_t right_child # id of the right child of the node
|
||||
intp_t feature # Feature used for splitting the node
|
||||
float64_t threshold # Threshold value at the node
|
||||
float64_t impurity # Impurity of the node (i.e., the value of the criterion)
|
||||
intp_t n_node_samples # Number of samples at the node
|
||||
float64_t weighted_n_node_samples # Weighted number of samples at the node
|
||||
uint8_t missing_go_to_left # Whether features have missing values
|
||||
|
||||
|
||||
cdef struct ParentInfo:
|
||||
# Structure to store information about the parent of a node
|
||||
# This is passed to the splitter, to provide information about the previous split
|
||||
|
||||
float64_t lower_bound # the lower bound of the parent's impurity
|
||||
float64_t upper_bound # the upper bound of the parent's impurity
|
||||
float64_t impurity # the impurity of the parent
|
||||
intp_t n_constant_features # the number of constant features found in parent
|
||||
|
||||
cdef class Tree:
|
||||
# The Tree object is a binary tree structure constructed by the
|
||||
# TreeBuilder. The tree structure is used for predictions and
|
||||
# feature importances.
|
||||
|
||||
# Input/Output layout
|
||||
cdef public intp_t n_features # Number of features in X
|
||||
cdef intp_t* n_classes # Number of classes in y[:, k]
|
||||
cdef public intp_t n_outputs # Number of outputs in y
|
||||
cdef public intp_t max_n_classes # max(n_classes)
|
||||
|
||||
# Inner structures: values are stored separately from node structure,
|
||||
# since size is determined at runtime.
|
||||
cdef public intp_t max_depth # Max depth of the tree
|
||||
cdef public intp_t node_count # Counter for node IDs
|
||||
cdef public intp_t capacity # Capacity of tree, in terms of nodes
|
||||
cdef Node* nodes # Array of nodes
|
||||
cdef float64_t* value # (capacity, n_outputs, max_n_classes) array of values
|
||||
cdef intp_t value_stride # = n_outputs * max_n_classes
|
||||
|
||||
# Methods
|
||||
cdef intp_t _add_node(self, intp_t parent, bint is_left, bint is_leaf,
|
||||
intp_t feature, float64_t threshold, float64_t impurity,
|
||||
intp_t n_node_samples,
|
||||
float64_t weighted_n_node_samples,
|
||||
uint8_t missing_go_to_left) except -1 nogil
|
||||
cdef int _resize(self, intp_t capacity) except -1 nogil
|
||||
cdef int _resize_c(self, intp_t capacity=*) except -1 nogil
|
||||
|
||||
cdef cnp.ndarray _get_value_ndarray(self)
|
||||
cdef cnp.ndarray _get_node_ndarray(self)
|
||||
|
||||
cpdef cnp.ndarray predict(self, object X)
|
||||
|
||||
cpdef cnp.ndarray apply(self, object X)
|
||||
cdef cnp.ndarray _apply_dense(self, object X)
|
||||
cdef cnp.ndarray _apply_sparse_csr(self, object X)
|
||||
|
||||
cpdef object decision_path(self, object X)
|
||||
cdef object _decision_path_dense(self, object X)
|
||||
cdef object _decision_path_sparse_csr(self, object X)
|
||||
|
||||
cpdef compute_node_depths(self)
|
||||
cpdef compute_feature_importances(self, normalize=*)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tree builder
|
||||
# =============================================================================
|
||||
|
||||
cdef class TreeBuilder:
|
||||
# The TreeBuilder recursively builds a Tree object from training samples,
|
||||
# using a Splitter object for splitting internal nodes and assigning
|
||||
# values to leaves.
|
||||
#
|
||||
# This class controls the various stopping criteria and the node splitting
|
||||
# evaluation order, e.g. depth-first or best-first.
|
||||
|
||||
cdef Splitter splitter # Splitting algorithm
|
||||
|
||||
cdef intp_t min_samples_split # Minimum number of samples in an internal node
|
||||
cdef intp_t min_samples_leaf # Minimum number of samples in a leaf
|
||||
cdef float64_t min_weight_leaf # Minimum weight in a leaf
|
||||
cdef intp_t max_depth # Maximal tree depth
|
||||
cdef float64_t min_impurity_decrease # Impurity threshold for early stopping
|
||||
|
||||
cpdef build(
|
||||
self,
|
||||
Tree tree,
|
||||
object X,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight=*,
|
||||
const uint8_t[::1] missing_values_in_feature_mask=*,
|
||||
)
|
||||
|
||||
cdef _check_input(
|
||||
self,
|
||||
object X,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tree pruning
|
||||
# =============================================================================
|
||||
|
||||
# The private function allows any external caller to prune the tree and return
|
||||
# a new tree with the pruned nodes. The pruned tree is a new tree object.
|
||||
#
|
||||
# .. warning:: this function is not backwards compatible and may change without
|
||||
# notice.
|
||||
cdef void _build_pruned_tree(
|
||||
Tree tree, # OUT
|
||||
Tree orig_tree,
|
||||
const uint8_t[:] leaves_in_subtree,
|
||||
intp_t capacity
|
||||
)
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _tree.pyx for details.
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as cnp
|
||||
|
||||
from ..utils._typedefs cimport float32_t, float64_t, intp_t, int32_t, uint8_t, uint32_t
|
||||
|
||||
from ._splitter cimport Splitter
|
||||
from ._splitter cimport SplitRecord
|
||||
|
||||
cdef struct Node:
|
||||
# Base storage structure for the nodes in a Tree object
|
||||
|
||||
intp_t left_child # id of the left child of the node
|
||||
intp_t right_child # id of the right child of the node
|
||||
intp_t feature # Feature used for splitting the node
|
||||
float64_t threshold # Threshold value at the node
|
||||
float64_t impurity # Impurity of the node (i.e., the value of the criterion)
|
||||
intp_t n_node_samples # Number of samples at the node
|
||||
float64_t weighted_n_node_samples # Weighted number of samples at the node
|
||||
uint8_t missing_go_to_left # Whether features have missing values
|
||||
|
||||
|
||||
cdef struct ParentInfo:
|
||||
# Structure to store information about the parent of a node
|
||||
# This is passed to the splitter, to provide information about the previous split
|
||||
|
||||
float64_t lower_bound # the lower bound of the parent's impurity
|
||||
float64_t upper_bound # the upper bound of the parent's impurity
|
||||
float64_t impurity # the impurity of the parent
|
||||
intp_t n_constant_features # the number of constant features found in parent
|
||||
|
||||
cdef class Tree:
|
||||
# The Tree object is a binary tree structure constructed by the
|
||||
# TreeBuilder. The tree structure is used for predictions and
|
||||
# feature importances.
|
||||
|
||||
# Input/Output layout
|
||||
cdef public intp_t n_features # Number of features in X
|
||||
cdef intp_t* n_classes # Number of classes in y[:, k]
|
||||
cdef public intp_t n_outputs # Number of outputs in y
|
||||
cdef public intp_t max_n_classes # max(n_classes)
|
||||
|
||||
# Inner structures: values are stored separately from node structure,
|
||||
# since size is determined at runtime.
|
||||
cdef public intp_t max_depth # Max depth of the tree
|
||||
cdef public intp_t node_count # Counter for node IDs
|
||||
cdef public intp_t capacity # Capacity of tree, in terms of nodes
|
||||
cdef Node* nodes # Array of nodes
|
||||
cdef float64_t* value # (capacity, n_outputs, max_n_classes) array of values
|
||||
cdef intp_t value_stride # = n_outputs * max_n_classes
|
||||
|
||||
# Methods
|
||||
cdef intp_t _add_node(self, intp_t parent, bint is_left, bint is_leaf,
|
||||
intp_t feature, float64_t threshold, float64_t impurity,
|
||||
intp_t n_node_samples,
|
||||
float64_t weighted_n_node_samples,
|
||||
uint8_t missing_go_to_left) except -1 nogil
|
||||
cdef int _resize(self, intp_t capacity) except -1 nogil
|
||||
cdef int _resize_c(self, intp_t capacity=*) except -1 nogil
|
||||
|
||||
cdef cnp.ndarray _get_value_ndarray(self)
|
||||
cdef cnp.ndarray _get_node_ndarray(self)
|
||||
|
||||
cpdef cnp.ndarray predict(self, object X)
|
||||
|
||||
cpdef cnp.ndarray apply(self, object X)
|
||||
cdef cnp.ndarray _apply_dense(self, object X)
|
||||
cdef cnp.ndarray _apply_sparse_csr(self, object X)
|
||||
|
||||
cpdef object decision_path(self, object X)
|
||||
cdef object _decision_path_dense(self, object X)
|
||||
cdef object _decision_path_sparse_csr(self, object X)
|
||||
|
||||
cpdef compute_node_depths(self)
|
||||
cpdef compute_feature_importances(self, normalize=*)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tree builder
|
||||
# =============================================================================
|
||||
|
||||
cdef class TreeBuilder:
|
||||
# The TreeBuilder recursively builds a Tree object from training samples,
|
||||
# using a Splitter object for splitting internal nodes and assigning
|
||||
# values to leaves.
|
||||
#
|
||||
# This class controls the various stopping criteria and the node splitting
|
||||
# evaluation order, e.g. depth-first or best-first.
|
||||
|
||||
cdef Splitter splitter # Splitting algorithm
|
||||
|
||||
cdef intp_t min_samples_split # Minimum number of samples in an internal node
|
||||
cdef intp_t min_samples_leaf # Minimum number of samples in a leaf
|
||||
cdef float64_t min_weight_leaf # Minimum weight in a leaf
|
||||
cdef intp_t max_depth # Maximal tree depth
|
||||
cdef float64_t min_impurity_decrease # Impurity threshold for early stopping
|
||||
|
||||
cpdef build(
|
||||
self,
|
||||
Tree tree,
|
||||
object X,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight=*,
|
||||
const uint8_t[::1] missing_values_in_feature_mask=*,
|
||||
)
|
||||
|
||||
cdef _check_input(
|
||||
self,
|
||||
object X,
|
||||
const float64_t[:, ::1] y,
|
||||
const float64_t[:] sample_weight,
|
||||
)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tree pruning
|
||||
# =============================================================================
|
||||
|
||||
# The private function allows any external caller to prune the tree and return
|
||||
# a new tree with the pruned nodes. The pruned tree is a new tree object.
|
||||
#
|
||||
# .. warning:: this function is not backwards compatible and may change without
|
||||
# notice.
|
||||
cdef void _build_pruned_tree(
|
||||
Tree tree, # OUT
|
||||
Tree orig_tree,
|
||||
const uint8_t[:] leaves_in_subtree,
|
||||
intp_t capacity
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,100 +1,100 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _utils.pyx for details.
|
||||
|
||||
cimport numpy as cnp
|
||||
from ._tree cimport Node
|
||||
from ..neighbors._quad_tree cimport Cell
|
||||
from ..utils._typedefs cimport float32_t, float64_t, intp_t, uint8_t, int32_t, uint32_t
|
||||
|
||||
|
||||
cdef enum:
|
||||
# Max value for our rand_r replacement (near the bottom).
|
||||
# We don't use RAND_MAX because it's different across platforms and
|
||||
# particularly tiny on Windows/MSVC.
|
||||
# It corresponds to the maximum representable value for
|
||||
# 32-bit signed integers (i.e. 2^31 - 1).
|
||||
RAND_R_MAX = 2147483647
|
||||
|
||||
|
||||
# safe_realloc(&p, n) resizes the allocation of p to n * sizeof(*p) bytes or
|
||||
# raises a MemoryError. It never calls free, since that's __dealloc__'s job.
|
||||
# cdef float32_t *p = NULL
|
||||
# safe_realloc(&p, n)
|
||||
# is equivalent to p = malloc(n * sizeof(*p)) with error checking.
|
||||
ctypedef fused realloc_ptr:
|
||||
# Add pointer types here as needed.
|
||||
(float32_t*)
|
||||
(intp_t*)
|
||||
(uint8_t*)
|
||||
(WeightedPQueueRecord*)
|
||||
(float64_t*)
|
||||
(float64_t**)
|
||||
(Node*)
|
||||
(Cell*)
|
||||
(Node**)
|
||||
|
||||
cdef int safe_realloc(realloc_ptr* p, size_t nelems) except -1 nogil
|
||||
|
||||
|
||||
cdef cnp.ndarray sizet_ptr_to_ndarray(intp_t* data, intp_t size)
|
||||
|
||||
|
||||
cdef intp_t rand_int(intp_t low, intp_t high,
|
||||
uint32_t* random_state) noexcept nogil
|
||||
|
||||
|
||||
cdef float64_t rand_uniform(float64_t low, float64_t high,
|
||||
uint32_t* random_state) noexcept nogil
|
||||
|
||||
|
||||
cdef float64_t log(float64_t x) noexcept nogil
|
||||
|
||||
# =============================================================================
|
||||
# WeightedPQueue data structure
|
||||
# =============================================================================
|
||||
|
||||
# A record stored in the WeightedPQueue
|
||||
cdef struct WeightedPQueueRecord:
|
||||
float64_t data
|
||||
float64_t weight
|
||||
|
||||
cdef class WeightedPQueue:
|
||||
cdef intp_t capacity
|
||||
cdef intp_t array_ptr
|
||||
cdef WeightedPQueueRecord* array_
|
||||
|
||||
cdef bint is_empty(self) noexcept nogil
|
||||
cdef int reset(self) except -1 nogil
|
||||
cdef intp_t size(self) noexcept nogil
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil
|
||||
cdef int peek(self, float64_t* data, float64_t* weight) noexcept nogil
|
||||
cdef float64_t get_weight_from_index(self, intp_t index) noexcept nogil
|
||||
cdef float64_t get_value_from_index(self, intp_t index) noexcept nogil
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# WeightedMedianCalculator data structure
|
||||
# =============================================================================
|
||||
|
||||
cdef class WeightedMedianCalculator:
|
||||
cdef intp_t initial_capacity
|
||||
cdef WeightedPQueue samples
|
||||
cdef float64_t total_weight
|
||||
cdef intp_t k
|
||||
cdef float64_t sum_w_0_k # represents sum(weights[0:k]) = w[0] + w[1] + ... + w[k-1]
|
||||
cdef intp_t size(self) noexcept nogil
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil
|
||||
cdef int reset(self) except -1 nogil
|
||||
cdef int update_median_parameters_post_push(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil
|
||||
cdef int update_median_parameters_post_remove(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil
|
||||
cdef float64_t get_median(self) noexcept nogil
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
# See _utils.pyx for details.
|
||||
|
||||
cimport numpy as cnp
|
||||
from ._tree cimport Node
|
||||
from ..neighbors._quad_tree cimport Cell
|
||||
from ..utils._typedefs cimport float32_t, float64_t, intp_t, uint8_t, int32_t, uint32_t
|
||||
|
||||
|
||||
cdef enum:
|
||||
# Max value for our rand_r replacement (near the bottom).
|
||||
# We don't use RAND_MAX because it's different across platforms and
|
||||
# particularly tiny on Windows/MSVC.
|
||||
# It corresponds to the maximum representable value for
|
||||
# 32-bit signed integers (i.e. 2^31 - 1).
|
||||
RAND_R_MAX = 2147483647
|
||||
|
||||
|
||||
# safe_realloc(&p, n) resizes the allocation of p to n * sizeof(*p) bytes or
|
||||
# raises a MemoryError. It never calls free, since that's __dealloc__'s job.
|
||||
# cdef float32_t *p = NULL
|
||||
# safe_realloc(&p, n)
|
||||
# is equivalent to p = malloc(n * sizeof(*p)) with error checking.
|
||||
ctypedef fused realloc_ptr:
|
||||
# Add pointer types here as needed.
|
||||
(float32_t*)
|
||||
(intp_t*)
|
||||
(uint8_t*)
|
||||
(WeightedPQueueRecord*)
|
||||
(float64_t*)
|
||||
(float64_t**)
|
||||
(Node*)
|
||||
(Cell*)
|
||||
(Node**)
|
||||
|
||||
cdef int safe_realloc(realloc_ptr* p, size_t nelems) except -1 nogil
|
||||
|
||||
|
||||
cdef cnp.ndarray sizet_ptr_to_ndarray(intp_t* data, intp_t size)
|
||||
|
||||
|
||||
cdef intp_t rand_int(intp_t low, intp_t high,
|
||||
uint32_t* random_state) noexcept nogil
|
||||
|
||||
|
||||
cdef float64_t rand_uniform(float64_t low, float64_t high,
|
||||
uint32_t* random_state) noexcept nogil
|
||||
|
||||
|
||||
cdef float64_t log(float64_t x) noexcept nogil
|
||||
|
||||
# =============================================================================
|
||||
# WeightedPQueue data structure
|
||||
# =============================================================================
|
||||
|
||||
# A record stored in the WeightedPQueue
|
||||
cdef struct WeightedPQueueRecord:
|
||||
float64_t data
|
||||
float64_t weight
|
||||
|
||||
cdef class WeightedPQueue:
|
||||
cdef intp_t capacity
|
||||
cdef intp_t array_ptr
|
||||
cdef WeightedPQueueRecord* array_
|
||||
|
||||
cdef bint is_empty(self) noexcept nogil
|
||||
cdef int reset(self) except -1 nogil
|
||||
cdef intp_t size(self) noexcept nogil
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil
|
||||
cdef int peek(self, float64_t* data, float64_t* weight) noexcept nogil
|
||||
cdef float64_t get_weight_from_index(self, intp_t index) noexcept nogil
|
||||
cdef float64_t get_value_from_index(self, intp_t index) noexcept nogil
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# WeightedMedianCalculator data structure
|
||||
# =============================================================================
|
||||
|
||||
cdef class WeightedMedianCalculator:
|
||||
cdef intp_t initial_capacity
|
||||
cdef WeightedPQueue samples
|
||||
cdef float64_t total_weight
|
||||
cdef intp_t k
|
||||
cdef float64_t sum_w_0_k # represents sum(weights[0:k]) = w[0] + w[1] + ... + w[k-1]
|
||||
cdef intp_t size(self) noexcept nogil
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil
|
||||
cdef int reset(self) except -1 nogil
|
||||
cdef int update_median_parameters_post_push(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil
|
||||
cdef int update_median_parameters_post_remove(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil
|
||||
cdef float64_t get_median(self) noexcept nogil
|
||||
|
||||
@@ -1,460 +1,460 @@
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from libc.stdlib cimport free
|
||||
from libc.stdlib cimport realloc
|
||||
from libc.math cimport log as ln
|
||||
from libc.math cimport isnan
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as cnp
|
||||
cnp.import_array()
|
||||
|
||||
from ..utils._random cimport our_rand_r
|
||||
|
||||
# =============================================================================
|
||||
# Helper functions
|
||||
# =============================================================================
|
||||
|
||||
cdef int safe_realloc(realloc_ptr* p, size_t nelems) except -1 nogil:
|
||||
# sizeof(realloc_ptr[0]) would be more like idiomatic C, but causes Cython
|
||||
# 0.20.1 to crash.
|
||||
cdef size_t nbytes = nelems * sizeof(p[0][0])
|
||||
if nbytes / sizeof(p[0][0]) != nelems:
|
||||
# Overflow in the multiplication
|
||||
raise MemoryError(f"could not allocate ({nelems} * {sizeof(p[0][0])}) bytes")
|
||||
|
||||
cdef realloc_ptr tmp = <realloc_ptr>realloc(p[0], nbytes)
|
||||
if tmp == NULL:
|
||||
raise MemoryError(f"could not allocate {nbytes} bytes")
|
||||
|
||||
p[0] = tmp
|
||||
return 0
|
||||
|
||||
|
||||
def _realloc_test():
|
||||
# Helper for tests. Tries to allocate <size_t>(-1) / 2 * sizeof(size_t)
|
||||
# bytes, which will always overflow.
|
||||
cdef intp_t* p = NULL
|
||||
safe_realloc(&p, <size_t>(-1) / 2)
|
||||
if p != NULL:
|
||||
free(p)
|
||||
assert False
|
||||
|
||||
|
||||
cdef inline cnp.ndarray sizet_ptr_to_ndarray(intp_t* data, intp_t size):
|
||||
"""Return copied data as 1D numpy array of intp's."""
|
||||
cdef cnp.npy_intp shape[1]
|
||||
shape[0] = <cnp.npy_intp> size
|
||||
return cnp.PyArray_SimpleNewFromData(1, shape, cnp.NPY_INTP, data).copy()
|
||||
|
||||
|
||||
cdef inline intp_t rand_int(intp_t low, intp_t high,
|
||||
uint32_t* random_state) noexcept nogil:
|
||||
"""Generate a random integer in [low; end)."""
|
||||
return low + our_rand_r(random_state) % (high - low)
|
||||
|
||||
|
||||
cdef inline float64_t rand_uniform(float64_t low, float64_t high,
|
||||
uint32_t* random_state) noexcept nogil:
|
||||
"""Generate a random float64_t in [low; high)."""
|
||||
return ((high - low) * <float64_t> our_rand_r(random_state) /
|
||||
<float64_t> RAND_R_MAX) + low
|
||||
|
||||
|
||||
cdef inline float64_t log(float64_t x) noexcept nogil:
|
||||
return ln(x) / ln(2.0)
|
||||
|
||||
# =============================================================================
|
||||
# WeightedPQueue data structure
|
||||
# =============================================================================
|
||||
|
||||
cdef class WeightedPQueue:
|
||||
"""A priority queue class, always sorted in increasing order.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
capacity : intp_t
|
||||
The capacity of the priority queue.
|
||||
|
||||
array_ptr : intp_t
|
||||
The water mark of the priority queue; the priority queue grows from
|
||||
left to right in the array ``array_``. ``array_ptr`` is always
|
||||
less than ``capacity``.
|
||||
|
||||
array_ : WeightedPQueueRecord*
|
||||
The array of priority queue records. The minimum element is on the
|
||||
left at index 0, and the maximum element is on the right at index
|
||||
``array_ptr-1``.
|
||||
"""
|
||||
|
||||
def __cinit__(self, intp_t capacity):
|
||||
self.capacity = capacity
|
||||
self.array_ptr = 0
|
||||
safe_realloc(&self.array_, capacity)
|
||||
|
||||
def __dealloc__(self):
|
||||
free(self.array_)
|
||||
|
||||
cdef int reset(self) except -1 nogil:
|
||||
"""Reset the WeightedPQueue to its state at construction
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
self.array_ptr = 0
|
||||
# Since safe_realloc can raise MemoryError, use `except -1`
|
||||
safe_realloc(&self.array_, self.capacity)
|
||||
return 0
|
||||
|
||||
cdef bint is_empty(self) noexcept nogil:
|
||||
return self.array_ptr <= 0
|
||||
|
||||
cdef intp_t size(self) noexcept nogil:
|
||||
return self.array_ptr
|
||||
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil:
|
||||
"""Push record on the array.
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
cdef intp_t array_ptr = self.array_ptr
|
||||
cdef WeightedPQueueRecord* array = NULL
|
||||
cdef intp_t i
|
||||
|
||||
# Resize if capacity not sufficient
|
||||
if array_ptr >= self.capacity:
|
||||
self.capacity *= 2
|
||||
# Since safe_realloc can raise MemoryError, use `except -1`
|
||||
safe_realloc(&self.array_, self.capacity)
|
||||
|
||||
# Put element as last element of array
|
||||
array = self.array_
|
||||
array[array_ptr].data = data
|
||||
array[array_ptr].weight = weight
|
||||
|
||||
# bubble last element up according until it is sorted
|
||||
# in ascending order
|
||||
i = array_ptr
|
||||
while(i != 0 and array[i].data < array[i-1].data):
|
||||
array[i], array[i-1] = array[i-1], array[i]
|
||||
i -= 1
|
||||
|
||||
# Increase element count
|
||||
self.array_ptr = array_ptr + 1
|
||||
return 0
|
||||
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil:
|
||||
"""Remove a specific value/weight record from the array.
|
||||
Returns 0 if successful, -1 if record not found."""
|
||||
cdef intp_t array_ptr = self.array_ptr
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
cdef intp_t idx_to_remove = -1
|
||||
cdef intp_t i
|
||||
|
||||
if array_ptr <= 0:
|
||||
return -1
|
||||
|
||||
# find element to remove
|
||||
for i in range(array_ptr):
|
||||
if array[i].data == data and array[i].weight == weight:
|
||||
idx_to_remove = i
|
||||
break
|
||||
|
||||
if idx_to_remove == -1:
|
||||
return -1
|
||||
|
||||
# shift the elements after the removed element
|
||||
# to the left.
|
||||
for i in range(idx_to_remove, array_ptr-1):
|
||||
array[i] = array[i+1]
|
||||
|
||||
self.array_ptr = array_ptr - 1
|
||||
return 0
|
||||
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil:
|
||||
"""Remove the top (minimum) element from array.
|
||||
Returns 0 if successful, -1 if nothing to remove."""
|
||||
cdef intp_t array_ptr = self.array_ptr
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
cdef intp_t i
|
||||
|
||||
if array_ptr <= 0:
|
||||
return -1
|
||||
|
||||
data[0] = array[0].data
|
||||
weight[0] = array[0].weight
|
||||
|
||||
# shift the elements after the removed element
|
||||
# to the left.
|
||||
for i in range(0, array_ptr-1):
|
||||
array[i] = array[i+1]
|
||||
|
||||
self.array_ptr = array_ptr - 1
|
||||
return 0
|
||||
|
||||
cdef int peek(self, float64_t* data, float64_t* weight) noexcept nogil:
|
||||
"""Write the top element from array to a pointer.
|
||||
Returns 0 if successful, -1 if nothing to write."""
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
if self.array_ptr <= 0:
|
||||
return -1
|
||||
# Take first value
|
||||
data[0] = array[0].data
|
||||
weight[0] = array[0].weight
|
||||
return 0
|
||||
|
||||
cdef float64_t get_weight_from_index(self, intp_t index) noexcept nogil:
|
||||
"""Given an index between [0,self.current_capacity], access
|
||||
the appropriate heap and return the requested weight"""
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
|
||||
# get weight at index
|
||||
return array[index].weight
|
||||
|
||||
cdef float64_t get_value_from_index(self, intp_t index) noexcept nogil:
|
||||
"""Given an index between [0,self.current_capacity], access
|
||||
the appropriate heap and return the requested value"""
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
|
||||
# get value at index
|
||||
return array[index].data
|
||||
|
||||
# =============================================================================
|
||||
# WeightedMedianCalculator data structure
|
||||
# =============================================================================
|
||||
|
||||
cdef class WeightedMedianCalculator:
|
||||
"""A class to handle calculation of the weighted median from streams of
|
||||
data. To do so, it maintains a parameter ``k`` such that the sum of the
|
||||
weights in the range [0,k) is greater than or equal to half of the total
|
||||
weight. By minimizing the value of ``k`` that fulfills this constraint,
|
||||
calculating the median is done by either taking the value of the sample
|
||||
at index ``k-1`` of ``samples`` (samples[k-1].data) or the average of
|
||||
the samples at index ``k-1`` and ``k`` of ``samples``
|
||||
((samples[k-1] + samples[k]) / 2).
|
||||
|
||||
Attributes
|
||||
----------
|
||||
initial_capacity : intp_t
|
||||
The initial capacity of the WeightedMedianCalculator.
|
||||
|
||||
samples : WeightedPQueue
|
||||
Holds the samples (consisting of values and their weights) used in the
|
||||
weighted median calculation.
|
||||
|
||||
total_weight : float64_t
|
||||
The sum of the weights of items in ``samples``. Represents the total
|
||||
weight of all samples used in the median calculation.
|
||||
|
||||
k : intp_t
|
||||
Index used to calculate the median.
|
||||
|
||||
sum_w_0_k : float64_t
|
||||
The sum of the weights from samples[0:k]. Used in the weighted
|
||||
median calculation; minimizing the value of ``k`` such that
|
||||
``sum_w_0_k`` >= ``total_weight / 2`` provides a mechanism for
|
||||
calculating the median in constant time.
|
||||
|
||||
"""
|
||||
|
||||
def __cinit__(self, intp_t initial_capacity):
|
||||
self.initial_capacity = initial_capacity
|
||||
self.samples = WeightedPQueue(initial_capacity)
|
||||
self.total_weight = 0
|
||||
self.k = 0
|
||||
self.sum_w_0_k = 0
|
||||
|
||||
cdef intp_t size(self) noexcept nogil:
|
||||
"""Return the number of samples in the
|
||||
WeightedMedianCalculator"""
|
||||
return self.samples.size()
|
||||
|
||||
cdef int reset(self) except -1 nogil:
|
||||
"""Reset the WeightedMedianCalculator to its state at construction
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
# samples.reset (WeightedPQueue.reset) uses safe_realloc, hence
|
||||
# except -1
|
||||
self.samples.reset()
|
||||
self.total_weight = 0
|
||||
self.k = 0
|
||||
self.sum_w_0_k = 0
|
||||
return 0
|
||||
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil:
|
||||
"""Push a value and its associated weight to the WeightedMedianCalculator
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
cdef int return_value
|
||||
cdef float64_t original_median = 0.0
|
||||
|
||||
if self.size() != 0:
|
||||
original_median = self.get_median()
|
||||
# samples.push (WeightedPQueue.push) uses safe_realloc, hence except -1
|
||||
return_value = self.samples.push(data, weight)
|
||||
self.update_median_parameters_post_push(data, weight,
|
||||
original_median)
|
||||
return return_value
|
||||
|
||||
cdef int update_median_parameters_post_push(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil:
|
||||
"""Update the parameters used in the median calculation,
|
||||
namely `k` and `sum_w_0_k` after an insertion"""
|
||||
|
||||
# trivial case of one element.
|
||||
if self.size() == 1:
|
||||
self.k = 1
|
||||
self.total_weight = weight
|
||||
self.sum_w_0_k = self.total_weight
|
||||
return 0
|
||||
|
||||
# get the original weighted median
|
||||
self.total_weight += weight
|
||||
|
||||
if data < original_median:
|
||||
# inserting below the median, so increment k and
|
||||
# then update self.sum_w_0_k accordingly by adding
|
||||
# the weight that was added.
|
||||
self.k += 1
|
||||
# update sum_w_0_k by adding the weight added
|
||||
self.sum_w_0_k += weight
|
||||
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
# minimum value of k is 1
|
||||
while(self.k > 1 and ((self.sum_w_0_k -
|
||||
self.samples.get_weight_from_index(self.k-1))
|
||||
>= self.total_weight / 2.0)):
|
||||
self.k -= 1
|
||||
self.sum_w_0_k -= self.samples.get_weight_from_index(self.k)
|
||||
return 0
|
||||
|
||||
if data >= original_median:
|
||||
# inserting above or at the median
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
while(self.k < self.samples.size() and
|
||||
(self.sum_w_0_k < self.total_weight / 2.0)):
|
||||
self.k += 1
|
||||
self.sum_w_0_k += self.samples.get_weight_from_index(self.k-1)
|
||||
return 0
|
||||
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil:
|
||||
"""Remove a value from the MedianHeap, removing it
|
||||
from consideration in the median calculation
|
||||
"""
|
||||
cdef int return_value
|
||||
cdef float64_t original_median = 0.0
|
||||
|
||||
if self.size() != 0:
|
||||
original_median = self.get_median()
|
||||
|
||||
return_value = self.samples.remove(data, weight)
|
||||
self.update_median_parameters_post_remove(data, weight,
|
||||
original_median)
|
||||
return return_value
|
||||
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil:
|
||||
"""Pop a value from the MedianHeap, starting from the
|
||||
left and moving to the right.
|
||||
"""
|
||||
cdef int return_value
|
||||
cdef float64_t original_median = 0.0
|
||||
|
||||
if self.size() != 0:
|
||||
original_median = self.get_median()
|
||||
|
||||
# no elements to pop
|
||||
if self.samples.size() == 0:
|
||||
return -1
|
||||
|
||||
return_value = self.samples.pop(data, weight)
|
||||
self.update_median_parameters_post_remove(data[0],
|
||||
weight[0],
|
||||
original_median)
|
||||
return return_value
|
||||
|
||||
cdef int update_median_parameters_post_remove(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil:
|
||||
"""Update the parameters used in the median calculation,
|
||||
namely `k` and `sum_w_0_k` after a removal"""
|
||||
# reset parameters because it there are no elements
|
||||
if self.samples.size() == 0:
|
||||
self.k = 0
|
||||
self.total_weight = 0
|
||||
self.sum_w_0_k = 0
|
||||
return 0
|
||||
|
||||
# trivial case of one element.
|
||||
if self.samples.size() == 1:
|
||||
self.k = 1
|
||||
self.total_weight -= weight
|
||||
self.sum_w_0_k = self.total_weight
|
||||
return 0
|
||||
|
||||
# get the current weighted median
|
||||
self.total_weight -= weight
|
||||
|
||||
if data < original_median:
|
||||
# removing below the median, so decrement k and
|
||||
# then update self.sum_w_0_k accordingly by subtracting
|
||||
# the removed weight
|
||||
|
||||
self.k -= 1
|
||||
# update sum_w_0_k by removing the weight at index k
|
||||
self.sum_w_0_k -= weight
|
||||
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
# by incrementing k and updating sum_w_0_k accordingly
|
||||
# until the condition is met.
|
||||
while(self.k < self.samples.size() and
|
||||
(self.sum_w_0_k < self.total_weight / 2.0)):
|
||||
self.k += 1
|
||||
self.sum_w_0_k += self.samples.get_weight_from_index(self.k-1)
|
||||
return 0
|
||||
|
||||
if data >= original_median:
|
||||
# removing above the median
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
while(self.k > 1 and ((self.sum_w_0_k -
|
||||
self.samples.get_weight_from_index(self.k-1))
|
||||
>= self.total_weight / 2.0)):
|
||||
self.k -= 1
|
||||
self.sum_w_0_k -= self.samples.get_weight_from_index(self.k)
|
||||
return 0
|
||||
|
||||
cdef float64_t get_median(self) noexcept nogil:
|
||||
"""Write the median to a pointer, taking into account
|
||||
sample weights."""
|
||||
if self.sum_w_0_k == (self.total_weight / 2.0):
|
||||
# split median
|
||||
return (self.samples.get_value_from_index(self.k) +
|
||||
self.samples.get_value_from_index(self.k-1)) / 2.0
|
||||
if self.sum_w_0_k > (self.total_weight / 2.0):
|
||||
# whole median
|
||||
return self.samples.get_value_from_index(self.k-1)
|
||||
|
||||
|
||||
def _any_isnan_axis0(const float32_t[:, :] X):
|
||||
"""Same as np.any(np.isnan(X), axis=0)"""
|
||||
cdef:
|
||||
intp_t i, j
|
||||
intp_t n_samples = X.shape[0]
|
||||
intp_t n_features = X.shape[1]
|
||||
uint8_t[::1] isnan_out = np.zeros(X.shape[1], dtype=np.bool_)
|
||||
|
||||
with nogil:
|
||||
for i in range(n_samples):
|
||||
for j in range(n_features):
|
||||
if isnan_out[j]:
|
||||
continue
|
||||
if isnan(X[i, j]):
|
||||
isnan_out[j] = True
|
||||
break
|
||||
return np.asarray(isnan_out)
|
||||
# Authors: The scikit-learn developers
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
from libc.stdlib cimport free
|
||||
from libc.stdlib cimport realloc
|
||||
from libc.math cimport log as ln
|
||||
from libc.math cimport isnan
|
||||
|
||||
import numpy as np
|
||||
cimport numpy as cnp
|
||||
cnp.import_array()
|
||||
|
||||
from ..utils._random cimport our_rand_r
|
||||
|
||||
# =============================================================================
|
||||
# Helper functions
|
||||
# =============================================================================
|
||||
|
||||
cdef int safe_realloc(realloc_ptr* p, size_t nelems) except -1 nogil:
|
||||
# sizeof(realloc_ptr[0]) would be more like idiomatic C, but causes Cython
|
||||
# 0.20.1 to crash.
|
||||
cdef size_t nbytes = nelems * sizeof(p[0][0])
|
||||
if nbytes / sizeof(p[0][0]) != nelems:
|
||||
# Overflow in the multiplication
|
||||
raise MemoryError(f"could not allocate ({nelems} * {sizeof(p[0][0])}) bytes")
|
||||
|
||||
cdef realloc_ptr tmp = <realloc_ptr>realloc(p[0], nbytes)
|
||||
if tmp == NULL:
|
||||
raise MemoryError(f"could not allocate {nbytes} bytes")
|
||||
|
||||
p[0] = tmp
|
||||
return 0
|
||||
|
||||
|
||||
def _realloc_test():
|
||||
# Helper for tests. Tries to allocate <size_t>(-1) / 2 * sizeof(size_t)
|
||||
# bytes, which will always overflow.
|
||||
cdef intp_t* p = NULL
|
||||
safe_realloc(&p, <size_t>(-1) / 2)
|
||||
if p != NULL:
|
||||
free(p)
|
||||
assert False
|
||||
|
||||
|
||||
cdef inline cnp.ndarray sizet_ptr_to_ndarray(intp_t* data, intp_t size):
|
||||
"""Return copied data as 1D numpy array of intp's."""
|
||||
cdef cnp.npy_intp shape[1]
|
||||
shape[0] = <cnp.npy_intp> size
|
||||
return cnp.PyArray_SimpleNewFromData(1, shape, cnp.NPY_INTP, data).copy()
|
||||
|
||||
|
||||
cdef inline intp_t rand_int(intp_t low, intp_t high,
|
||||
uint32_t* random_state) noexcept nogil:
|
||||
"""Generate a random integer in [low; end)."""
|
||||
return low + our_rand_r(random_state) % (high - low)
|
||||
|
||||
|
||||
cdef inline float64_t rand_uniform(float64_t low, float64_t high,
|
||||
uint32_t* random_state) noexcept nogil:
|
||||
"""Generate a random float64_t in [low; high)."""
|
||||
return ((high - low) * <float64_t> our_rand_r(random_state) /
|
||||
<float64_t> RAND_R_MAX) + low
|
||||
|
||||
|
||||
cdef inline float64_t log(float64_t x) noexcept nogil:
|
||||
return ln(x) / ln(2.0)
|
||||
|
||||
# =============================================================================
|
||||
# WeightedPQueue data structure
|
||||
# =============================================================================
|
||||
|
||||
cdef class WeightedPQueue:
|
||||
"""A priority queue class, always sorted in increasing order.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
capacity : intp_t
|
||||
The capacity of the priority queue.
|
||||
|
||||
array_ptr : intp_t
|
||||
The water mark of the priority queue; the priority queue grows from
|
||||
left to right in the array ``array_``. ``array_ptr`` is always
|
||||
less than ``capacity``.
|
||||
|
||||
array_ : WeightedPQueueRecord*
|
||||
The array of priority queue records. The minimum element is on the
|
||||
left at index 0, and the maximum element is on the right at index
|
||||
``array_ptr-1``.
|
||||
"""
|
||||
|
||||
def __cinit__(self, intp_t capacity):
|
||||
self.capacity = capacity
|
||||
self.array_ptr = 0
|
||||
safe_realloc(&self.array_, capacity)
|
||||
|
||||
def __dealloc__(self):
|
||||
free(self.array_)
|
||||
|
||||
cdef int reset(self) except -1 nogil:
|
||||
"""Reset the WeightedPQueue to its state at construction
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
self.array_ptr = 0
|
||||
# Since safe_realloc can raise MemoryError, use `except -1`
|
||||
safe_realloc(&self.array_, self.capacity)
|
||||
return 0
|
||||
|
||||
cdef bint is_empty(self) noexcept nogil:
|
||||
return self.array_ptr <= 0
|
||||
|
||||
cdef intp_t size(self) noexcept nogil:
|
||||
return self.array_ptr
|
||||
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil:
|
||||
"""Push record on the array.
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
cdef intp_t array_ptr = self.array_ptr
|
||||
cdef WeightedPQueueRecord* array = NULL
|
||||
cdef intp_t i
|
||||
|
||||
# Resize if capacity not sufficient
|
||||
if array_ptr >= self.capacity:
|
||||
self.capacity *= 2
|
||||
# Since safe_realloc can raise MemoryError, use `except -1`
|
||||
safe_realloc(&self.array_, self.capacity)
|
||||
|
||||
# Put element as last element of array
|
||||
array = self.array_
|
||||
array[array_ptr].data = data
|
||||
array[array_ptr].weight = weight
|
||||
|
||||
# bubble last element up according until it is sorted
|
||||
# in ascending order
|
||||
i = array_ptr
|
||||
while(i != 0 and array[i].data < array[i-1].data):
|
||||
array[i], array[i-1] = array[i-1], array[i]
|
||||
i -= 1
|
||||
|
||||
# Increase element count
|
||||
self.array_ptr = array_ptr + 1
|
||||
return 0
|
||||
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil:
|
||||
"""Remove a specific value/weight record from the array.
|
||||
Returns 0 if successful, -1 if record not found."""
|
||||
cdef intp_t array_ptr = self.array_ptr
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
cdef intp_t idx_to_remove = -1
|
||||
cdef intp_t i
|
||||
|
||||
if array_ptr <= 0:
|
||||
return -1
|
||||
|
||||
# find element to remove
|
||||
for i in range(array_ptr):
|
||||
if array[i].data == data and array[i].weight == weight:
|
||||
idx_to_remove = i
|
||||
break
|
||||
|
||||
if idx_to_remove == -1:
|
||||
return -1
|
||||
|
||||
# shift the elements after the removed element
|
||||
# to the left.
|
||||
for i in range(idx_to_remove, array_ptr-1):
|
||||
array[i] = array[i+1]
|
||||
|
||||
self.array_ptr = array_ptr - 1
|
||||
return 0
|
||||
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil:
|
||||
"""Remove the top (minimum) element from array.
|
||||
Returns 0 if successful, -1 if nothing to remove."""
|
||||
cdef intp_t array_ptr = self.array_ptr
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
cdef intp_t i
|
||||
|
||||
if array_ptr <= 0:
|
||||
return -1
|
||||
|
||||
data[0] = array[0].data
|
||||
weight[0] = array[0].weight
|
||||
|
||||
# shift the elements after the removed element
|
||||
# to the left.
|
||||
for i in range(0, array_ptr-1):
|
||||
array[i] = array[i+1]
|
||||
|
||||
self.array_ptr = array_ptr - 1
|
||||
return 0
|
||||
|
||||
cdef int peek(self, float64_t* data, float64_t* weight) noexcept nogil:
|
||||
"""Write the top element from array to a pointer.
|
||||
Returns 0 if successful, -1 if nothing to write."""
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
if self.array_ptr <= 0:
|
||||
return -1
|
||||
# Take first value
|
||||
data[0] = array[0].data
|
||||
weight[0] = array[0].weight
|
||||
return 0
|
||||
|
||||
cdef float64_t get_weight_from_index(self, intp_t index) noexcept nogil:
|
||||
"""Given an index between [0,self.current_capacity], access
|
||||
the appropriate heap and return the requested weight"""
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
|
||||
# get weight at index
|
||||
return array[index].weight
|
||||
|
||||
cdef float64_t get_value_from_index(self, intp_t index) noexcept nogil:
|
||||
"""Given an index between [0,self.current_capacity], access
|
||||
the appropriate heap and return the requested value"""
|
||||
cdef WeightedPQueueRecord* array = self.array_
|
||||
|
||||
# get value at index
|
||||
return array[index].data
|
||||
|
||||
# =============================================================================
|
||||
# WeightedMedianCalculator data structure
|
||||
# =============================================================================
|
||||
|
||||
cdef class WeightedMedianCalculator:
|
||||
"""A class to handle calculation of the weighted median from streams of
|
||||
data. To do so, it maintains a parameter ``k`` such that the sum of the
|
||||
weights in the range [0,k) is greater than or equal to half of the total
|
||||
weight. By minimizing the value of ``k`` that fulfills this constraint,
|
||||
calculating the median is done by either taking the value of the sample
|
||||
at index ``k-1`` of ``samples`` (samples[k-1].data) or the average of
|
||||
the samples at index ``k-1`` and ``k`` of ``samples``
|
||||
((samples[k-1] + samples[k]) / 2).
|
||||
|
||||
Attributes
|
||||
----------
|
||||
initial_capacity : intp_t
|
||||
The initial capacity of the WeightedMedianCalculator.
|
||||
|
||||
samples : WeightedPQueue
|
||||
Holds the samples (consisting of values and their weights) used in the
|
||||
weighted median calculation.
|
||||
|
||||
total_weight : float64_t
|
||||
The sum of the weights of items in ``samples``. Represents the total
|
||||
weight of all samples used in the median calculation.
|
||||
|
||||
k : intp_t
|
||||
Index used to calculate the median.
|
||||
|
||||
sum_w_0_k : float64_t
|
||||
The sum of the weights from samples[0:k]. Used in the weighted
|
||||
median calculation; minimizing the value of ``k`` such that
|
||||
``sum_w_0_k`` >= ``total_weight / 2`` provides a mechanism for
|
||||
calculating the median in constant time.
|
||||
|
||||
"""
|
||||
|
||||
def __cinit__(self, intp_t initial_capacity):
|
||||
self.initial_capacity = initial_capacity
|
||||
self.samples = WeightedPQueue(initial_capacity)
|
||||
self.total_weight = 0
|
||||
self.k = 0
|
||||
self.sum_w_0_k = 0
|
||||
|
||||
cdef intp_t size(self) noexcept nogil:
|
||||
"""Return the number of samples in the
|
||||
WeightedMedianCalculator"""
|
||||
return self.samples.size()
|
||||
|
||||
cdef int reset(self) except -1 nogil:
|
||||
"""Reset the WeightedMedianCalculator to its state at construction
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
# samples.reset (WeightedPQueue.reset) uses safe_realloc, hence
|
||||
# except -1
|
||||
self.samples.reset()
|
||||
self.total_weight = 0
|
||||
self.k = 0
|
||||
self.sum_w_0_k = 0
|
||||
return 0
|
||||
|
||||
cdef int push(self, float64_t data, float64_t weight) except -1 nogil:
|
||||
"""Push a value and its associated weight to the WeightedMedianCalculator
|
||||
|
||||
Return -1 in case of failure to allocate memory (and raise MemoryError)
|
||||
or 0 otherwise.
|
||||
"""
|
||||
cdef int return_value
|
||||
cdef float64_t original_median = 0.0
|
||||
|
||||
if self.size() != 0:
|
||||
original_median = self.get_median()
|
||||
# samples.push (WeightedPQueue.push) uses safe_realloc, hence except -1
|
||||
return_value = self.samples.push(data, weight)
|
||||
self.update_median_parameters_post_push(data, weight,
|
||||
original_median)
|
||||
return return_value
|
||||
|
||||
cdef int update_median_parameters_post_push(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil:
|
||||
"""Update the parameters used in the median calculation,
|
||||
namely `k` and `sum_w_0_k` after an insertion"""
|
||||
|
||||
# trivial case of one element.
|
||||
if self.size() == 1:
|
||||
self.k = 1
|
||||
self.total_weight = weight
|
||||
self.sum_w_0_k = self.total_weight
|
||||
return 0
|
||||
|
||||
# get the original weighted median
|
||||
self.total_weight += weight
|
||||
|
||||
if data < original_median:
|
||||
# inserting below the median, so increment k and
|
||||
# then update self.sum_w_0_k accordingly by adding
|
||||
# the weight that was added.
|
||||
self.k += 1
|
||||
# update sum_w_0_k by adding the weight added
|
||||
self.sum_w_0_k += weight
|
||||
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
# minimum value of k is 1
|
||||
while(self.k > 1 and ((self.sum_w_0_k -
|
||||
self.samples.get_weight_from_index(self.k-1))
|
||||
>= self.total_weight / 2.0)):
|
||||
self.k -= 1
|
||||
self.sum_w_0_k -= self.samples.get_weight_from_index(self.k)
|
||||
return 0
|
||||
|
||||
if data >= original_median:
|
||||
# inserting above or at the median
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
while(self.k < self.samples.size() and
|
||||
(self.sum_w_0_k < self.total_weight / 2.0)):
|
||||
self.k += 1
|
||||
self.sum_w_0_k += self.samples.get_weight_from_index(self.k-1)
|
||||
return 0
|
||||
|
||||
cdef int remove(self, float64_t data, float64_t weight) noexcept nogil:
|
||||
"""Remove a value from the MedianHeap, removing it
|
||||
from consideration in the median calculation
|
||||
"""
|
||||
cdef int return_value
|
||||
cdef float64_t original_median = 0.0
|
||||
|
||||
if self.size() != 0:
|
||||
original_median = self.get_median()
|
||||
|
||||
return_value = self.samples.remove(data, weight)
|
||||
self.update_median_parameters_post_remove(data, weight,
|
||||
original_median)
|
||||
return return_value
|
||||
|
||||
cdef int pop(self, float64_t* data, float64_t* weight) noexcept nogil:
|
||||
"""Pop a value from the MedianHeap, starting from the
|
||||
left and moving to the right.
|
||||
"""
|
||||
cdef int return_value
|
||||
cdef float64_t original_median = 0.0
|
||||
|
||||
if self.size() != 0:
|
||||
original_median = self.get_median()
|
||||
|
||||
# no elements to pop
|
||||
if self.samples.size() == 0:
|
||||
return -1
|
||||
|
||||
return_value = self.samples.pop(data, weight)
|
||||
self.update_median_parameters_post_remove(data[0],
|
||||
weight[0],
|
||||
original_median)
|
||||
return return_value
|
||||
|
||||
cdef int update_median_parameters_post_remove(
|
||||
self, float64_t data, float64_t weight,
|
||||
float64_t original_median) noexcept nogil:
|
||||
"""Update the parameters used in the median calculation,
|
||||
namely `k` and `sum_w_0_k` after a removal"""
|
||||
# reset parameters because it there are no elements
|
||||
if self.samples.size() == 0:
|
||||
self.k = 0
|
||||
self.total_weight = 0
|
||||
self.sum_w_0_k = 0
|
||||
return 0
|
||||
|
||||
# trivial case of one element.
|
||||
if self.samples.size() == 1:
|
||||
self.k = 1
|
||||
self.total_weight -= weight
|
||||
self.sum_w_0_k = self.total_weight
|
||||
return 0
|
||||
|
||||
# get the current weighted median
|
||||
self.total_weight -= weight
|
||||
|
||||
if data < original_median:
|
||||
# removing below the median, so decrement k and
|
||||
# then update self.sum_w_0_k accordingly by subtracting
|
||||
# the removed weight
|
||||
|
||||
self.k -= 1
|
||||
# update sum_w_0_k by removing the weight at index k
|
||||
self.sum_w_0_k -= weight
|
||||
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
# by incrementing k and updating sum_w_0_k accordingly
|
||||
# until the condition is met.
|
||||
while(self.k < self.samples.size() and
|
||||
(self.sum_w_0_k < self.total_weight / 2.0)):
|
||||
self.k += 1
|
||||
self.sum_w_0_k += self.samples.get_weight_from_index(self.k-1)
|
||||
return 0
|
||||
|
||||
if data >= original_median:
|
||||
# removing above the median
|
||||
# minimize k such that sum(W[0:k]) >= total_weight / 2
|
||||
while(self.k > 1 and ((self.sum_w_0_k -
|
||||
self.samples.get_weight_from_index(self.k-1))
|
||||
>= self.total_weight / 2.0)):
|
||||
self.k -= 1
|
||||
self.sum_w_0_k -= self.samples.get_weight_from_index(self.k)
|
||||
return 0
|
||||
|
||||
cdef float64_t get_median(self) noexcept nogil:
|
||||
"""Write the median to a pointer, taking into account
|
||||
sample weights."""
|
||||
if self.sum_w_0_k == (self.total_weight / 2.0):
|
||||
# split median
|
||||
return (self.samples.get_value_from_index(self.k) +
|
||||
self.samples.get_value_from_index(self.k-1)) / 2.0
|
||||
if self.sum_w_0_k > (self.total_weight / 2.0):
|
||||
# whole median
|
||||
return self.samples.get_value_from_index(self.k-1)
|
||||
|
||||
|
||||
def _any_isnan_axis0(const float32_t[:, :] X):
|
||||
"""Same as np.any(np.isnan(X), axis=0)"""
|
||||
cdef:
|
||||
intp_t i, j
|
||||
intp_t n_samples = X.shape[0]
|
||||
intp_t n_features = X.shape[1]
|
||||
uint8_t[::1] isnan_out = np.zeros(X.shape[1], dtype=np.bool_)
|
||||
|
||||
with nogil:
|
||||
for i in range(n_samples):
|
||||
for j in range(n_features):
|
||||
if isnan_out[j]:
|
||||
continue
|
||||
if isnan(X[i, j]):
|
||||
isnan_out[j] = True
|
||||
break
|
||||
return np.asarray(isnan_out)
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
tree_extension_metadata = {
|
||||
'_tree':
|
||||
{'sources': [cython_gen_cpp.process('_tree.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_splitter':
|
||||
{'sources': [cython_gen.process('_splitter.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_partitioner':
|
||||
{'sources': [cython_gen.process('_partitioner.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_criterion':
|
||||
{'sources': [cython_gen.process('_criterion.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_utils':
|
||||
{'sources': [cython_gen.process('_utils.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
}
|
||||
|
||||
foreach ext_name, ext_dict : tree_extension_metadata
|
||||
py.extension_module(
|
||||
ext_name,
|
||||
[ext_dict.get('sources'), utils_cython_tree],
|
||||
dependencies: [np_dep],
|
||||
override_options : ext_dict.get('override_options', []),
|
||||
subdir: 'sklearn/tree',
|
||||
install: true
|
||||
)
|
||||
endforeach
|
||||
tree_extension_metadata = {
|
||||
'_tree':
|
||||
{'sources': [cython_gen_cpp.process('_tree.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_splitter':
|
||||
{'sources': [cython_gen.process('_splitter.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_partitioner':
|
||||
{'sources': [cython_gen.process('_partitioner.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_criterion':
|
||||
{'sources': [cython_gen.process('_criterion.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
'_utils':
|
||||
{'sources': [cython_gen.process('_utils.pyx')],
|
||||
'override_options': ['optimization=3']},
|
||||
}
|
||||
|
||||
foreach ext_name, ext_dict : tree_extension_metadata
|
||||
py.extension_module(
|
||||
ext_name,
|
||||
[ext_dict.get('sources'), utils_cython_tree],
|
||||
dependencies: [np_dep],
|
||||
override_options : ext_dict.get('override_options', []),
|
||||
subdir: 'sklearn/tree',
|
||||
install: true
|
||||
)
|
||||
endforeach
|
||||
|
||||
Reference in New Issue
Block a user